Un servidor webhook para recibir y almacenar correos electrónicos enviados a través de ImprovMX, utilizando Flask y Gunicorn con MongoDB como base de datos.
- ✅ Recepción de correos vía webhook en el puerto 42010
- ✅ Almacenamiento automático en MongoDB
- ✅ API REST para consultar correos
- ✅ Soporte para adjuntos e imágenes inline
- ✅ Logs detallados de actividad
- ✅ Configuración optimizada con Gunicorn
- ✅ CORS habilitado para acceso web
- Python 3.8+
- MongoDB 4.0+
- Caddy 2.0+ (para SSL/TLS y reverse proxy)
- pip (gestor de paquetes de Python)
Este webhook está diseñado para funcionar con Caddy como reverse proxy:
- Caddy: Maneja SSL/TLS y actúa como reverse proxy (puerto 443/80)
- Gunicorn: Servidor WSGI Python (puerto 42010, solo localhost)
- Flask: Aplicación web que procesa los webhooks
- MongoDB: Base de datos para almacenar los correos
# Crear entorno virtual (opcional pero recomendado)
python3 -m venv venv
source venv/bin/activate # En Linux/Mac
# o
venv\Scripts\activate # En Windows
# Instalar dependencias
pip install -r requirements.txtEl archivo .env debe contener las siguientes variables:
# MongoDB Config
MONGO_USER=your_user
MONGO_PASS=your_password
MONGO_HOST=localhost
MONGO_DB=webmail_improvmx
# Dominio a escuchar
DOMINIO=your_domainEsta es la forma recomendada de ejecutar el webhook en producción.
Instalación automática:
# Ejecutar el instalador con permisos de root
sudo ./install_service.shEl instalador realizará:
- ✅ Verificar conexión a MongoDB
- ✅ Crear entorno virtual si no existe
- ✅ Instalar dependencias Python
- ✅ Configurar permisos de usuario
- ✅ Instalar servicio systemd
- ✅ Habilitar inicio automático
- ✅ Iniciar el servicio
- ✅ Verificar funcionamiento
Comandos de gestión del servicio:
# Ver estado del servicio
sudo systemctl status improvmx-webhook
# Iniciar servicio
sudo systemctl start improvmx-webhook
# Detener servicio
sudo systemctl stop improvmx-webhook
# Reiniciar servicio
sudo systemctl restart improvmx-webhook
# Recargar configuración (sin interrupción)
sudo systemctl reload improvmx-webhook
# Ver logs en tiempo real
sudo journalctl -u improvmx-webhook -f
# Ver últimos 50 líneas de logs
sudo journalctl -u improvmx-webhook -n 50Desinstalar el servicio:
# Ejecutar el desinstalador
sudo ./uninstall_service.sh# Cargar variables de entorno
export $(cat .env | grep -v '^#' | xargs)
# Iniciar Gunicorn
gunicorn -c gunicorn.conf.py app:apppython app.pyEl servidor escuchará en http://0.0.0.0:42010
Nota: El puerto 42010 es interno y debe ser accesible solo desde Caddy. El acceso público es mediante HTTPS gestionado por Caddy.
POST /webhook
Recibe correos desde ImprovMX y los almacena en MongoDB.
Ejemplo de Request:
{
"headers": {
"X-Forwarding-Service": "ImprovMX v3.0.0",
"Received-SPF": ["pass (improvmx.com: domain of example.com designates xxx.xxx.xxx.xxx as permitted sender)"]
},
"to": [{"name": "Usuario", "email": "usuario@puntoa.ar"}],
"from": {"name": "Remitente", "email": "remitente@example.com"},
"subject": "Asunto del correo",
"text": "Contenido en texto plano",
"html": "<p>Contenido HTML</p>",
"attachments": [...],
"inlines": [...]
}Response:
{
"success": true,
"message": "Email received and stored",
"email_id": "507f1f77bcf86cd799439011"
}GET /
Verifica que el servidor esté funcionando correctamente.
Response:
{
"status": "healthy",
"service": "ImprovMX Webhook",
"timestamp": "2024-01-15T10:30:00.000Z"
}GET /emails
Recupera correos almacenados con opciones de filtrado y paginación.
Parámetros Query:
limit: Número de correos a retornar (default: 10)skip: Número de correos a saltar (default: 0)from_email: Filtrar por email del remitentesubject: Filtrar por asunto (búsqueda parcial)
Ejemplos:
# Obtener los últimos 10 correos
curl http://localhost:42010/emails
# Obtener correos filtrados por remitente
curl http://localhost:42010/emails?from_email=test@example.com
# Buscar por asunto
curl http://localhost:42010/emails?subject=importante
# Paginación
curl http://localhost:42010/emails?limit=20&skip=10Response:
{
"success": true,
"count": 5,
"emails": [
{
"_id": "507f1f77bcf86cd799439011",
"subject": "Asunto del correo",
"from": {"name": "Remitente", "email": "remitente@example.com"},
"to": [{"name": "Usuario", "email": "usuario@puntoa.ar"}],
"received_at": "2024-01-15T10:30:00.000Z",
...
}
]
}GET /emails/<email_id>
Recupera un correo específico por su ID.
Ejemplo:
curl http://localhost:42010/emails/507f1f77bcf86cd799439011GET /emails/<email_id>/attachment/<attachment_name>
Descarga un adjunto específico de un correo.
Ejemplo:
curl -O -J http://localhost:42010/emails/507f1f77bcf86cd799439011/attachment/documento.pdf# Asegúrate de que el servidor esté corriendo
python test_webhook.pyEl script realizará las siguientes pruebas:
- ✅ Envío de un correo de prueba
- ✅ Verificación del health check
- ✅ Recuperación de lista de correos
- ✅ Obtención de un correo específico
# Health check
curl http://localhost:42010/
# Enviar correo de prueba
curl -X POST http://localhost:42010/webhook \
-H "Content-Type: application/json" \
-d '{
"to": [{"email": "test@puntoa.ar"}],
"from": {"email": "sender@example.com", "name": "Test Sender"},
"subject": "Test Email",
"text": "This is a test email",
"html": "<p>This is a test email</p>"
}'
# Obtener correos
curl http://localhost:42010/emailsCada correo se almacena en la colección emails con el siguiente esquema:
{
"_id": ObjectId,
"headers": {
"X-Forwarding-Service": String,
"Received-SPF": [String],
"Delivered-To": String,
"DKIM-Signature": [String],
"Authentication-Results": [String]
},
"to": [{ "name": String, "email": String }],
"from": { "name": String, "email": String },
"subject": String,
"message-id": String,
"date": String,
"return-path": { "name": String, "email": String },
"timestamp": Number,
"text": String,
"html": String,
"inlines": [{
"type": String,
"name": String,
"content": String (base64),
"cid": String
}],
"attachments": [{
"type": String,
"name": String,
"content": String (base64),
"encoding": String
}],
"received_at": ISODate,
"processed": Boolean
}-
Autenticación Bearer Token: Todos los endpoints protegidos requieren autenticación vía Bearer Token
-
Rate Limiting: Límites de velocidad para prevenir abuso y asegurar uso justo del API
-
Protección contra Fuerza Bruta: Bloqueo automático de IPs con múltiples intentos fallidos de autenticación
-
SSL/TLS: Importante - Este webhook NO configura SSL/TLS. La gestión de SSL se realiza exclusivamente mediante Caddy, que actúa como reverse proxy y maneja automáticamente los certificados HTTPS.
El archivo .env debe incluir la variable de autenticación:
# API Authentication
API_KEY=your_secure_256_bit_token_here
# MongoDB Config
MONGO_USER=your_user
MONGO_PASS=your_password
MONGO_HOST=localhost
MONGO_DB=webmail_improvmx
# Dominio a escuchar
DOMINIO=your_domain| Método | Endpoint | Descripción | Rate Limit |
|---|---|---|---|
| GET | /docs |
Documentación del API | 60/minuto |
| POST | /webhook |
Recepción de correos desde ImprovMX | 200/minuto |
| Método | Endpoint | Descripción | Rate Limit |
|---|---|---|---|
| GET | / |
Health check | 30/minuto |
| GET | /emails |
Listar correos | 20/minuto |
| GET | /emails/<email_id> |
Obtener correo específico | 30/minuto |
| GET | /emails/<email_id>/attachment/<name> |
Descargar adjunto | 10/minuto |
Para acceder a los endpoints protegidos, debes incluir el header de autorización:
curl -X GET http://localhost:42010/emails \
-H "Authorization: Bearer YOUR_API_KEY"Límites globales por IP:
- 200 solicitudes por día
- 50 solicitudes por hora
Respuesta cuando se excede el límite:
{
"error": "Rate limit exceeded: 30 per 1 minute"
}Código HTTP: 429 Too Many Requests
Reglas:
- Máximo 5 intentos fallidos de autenticación
- Intentos contados en ventana de 5 minutos
- Bloqueo de IP por 15 minutos tras exceder límite
- Limpieza automática de intentos antiguos
Respuesta cuando IP está bloqueada:
{
"success": false,
"error": "Too many failed attempts. Please try again later."
}Código HTTP: 429 Too Many Requests
Para más detalles sobre autenticación, rate limiting y seguridad, consulta:
API_AUTHENTICATION.md- Documentación completa de seguridad del API- Explicación detallada de autenticación Bearer Token
- Tablas de rate limiting por endpoint
- Ejemplos de uso en cURL, Python y JavaScript
- Guía de testing de características de seguridad
- Sección de troubleshooting
-
Rotación de API Keys: Rotar regularmente las claves API para mantener la seguridad
-
IP Whitelisting: Considera implementar whitelisting de IPs para endpoints sensibles
-
Monitoring: Implementar monitoreo de intentos fallidos y actividades sospechosas
-
Log Rotation: Configurar rotación de logs para mantener el sistema limpio
-
Firewall: Configura tu firewall para permitir tráfico solo en el puerto 42010 desde Caddy
El archivo gunicorn.conf.py contiene la configuración de producción:
- Bind: 0.0.0.0:42010
- Workers: (CPU cores × 2) + 1
- Timeout: 30 segundos
- Log Level: INFO
- Worker Class: sync
Para ajustar el número de workers según tu carga:
# En gunicorn.conf.py
workers = 4 # Ajusta según necesidad# Verificar que MongoDB esté corriendo
sudo systemctl status mongodb
# Iniciar MongoDB si no está corriendo
sudo systemctl start mongodb
# Verificar credenciales en .env# Encontrar el proceso usando el puerto 42010
lsof -i :42010
# Matar el proceso
kill -9 <PID># Asegúrate de tener permisos de escritura
chmod +x start.sh# Los logs se muestran en stdout/stderr
# Para guardar logs en archivo:
gunicorn -c gunicorn.conf.py app:app >> server.log 2>&1Síntoma: Al intentar enviar correos a través de smtp.improvmx.com, se experimentan tiempos de espera excesivos (timeouts) que pueden durar varios minutos antes de que la conexión falle o tenga éxito.
Causa: El problema ocurre porque el sistema intenta conectarse primero usando IPv6 antes de IPv4. Si la red o el servidor no tiene soporte IPv6 completo o si hay problemas de routing IPv6, el intento de conexión IPv6 falla después de un largo tiempo de espera antes de intentar con IPv4.
Diagnóstico:
# Verificar la resolución DNS de smtp.improvmx.com
nslookup smtp.improvmx.com
# o
dig smtp.improvmx.com
# Verificar cuál protocolo se está intentando usar
ping smtp.improvmx.com
ping6 smtp.improvmx.com
# Probar conexión al puerto SMTP (587) con timeout
timeout 5 nc -zv smtp.improvmx.com 587Solución - Forzar Conexión IPv4 mediante /etc/hosts:
- Obtener las direcciones IPv4 de smtp.improvmx.com:
# Obtener solo direcciones IPv4
dig smtp.improvmx.com +short A
# Ejemplo de salida (puede variar):
# 54.230.200.20
# 54.230.200.50- Editar el archivo /etc/hosts:
sudo nano /etc/hosts- Agregar las direcciones IPv4 de smtp.improvmx.com:
# Agregar al final del archivo /etc/hosts
54.230.200.20 smtp.improvmx.com
54.230.200.50 smtp.improvmx.com- Guardar el archivo y probar la conexión:
# Verificar que la resolución usa /etc/hosts
ping smtp.improvmx.com
# Probar conexión SMTP rápida
timeout 5 nc -zv smtp.improvmx.com 587Verificación de que la solución funcionó:
# La resolución DNS ahora debería mostrar solo las IPs de /etc/hosts
nslookup smtp.improvmx.com
# El ping debería responder inmediatamente (sin timeout)
ping -c 2 smtp.improvmx.com
# La conexión al puerto SMTP debería ser rápida
time nc -zv smtp.improvmx.com 587Mantenimiento de la solución:
Es importante mantener actualizadas las direcciones IPv4 en /etc/hosts ya que ImprovMX podría cambiarlas en el futuro. Se recomienda:
- Crear un script de verificación:
#!/bin/bash
# Script: check_smtp_ips.sh
echo "Direcciones IPv4 actuales de smtp.improvmx.com:"
dig smtp.improvmx.com +short A
echo ""
echo "Direcciones configuradas en /etc/hosts:"
grep smtp.improvmx.com /etc/hosts- Verificar periódicamente si las IPs han cambiado:
# Ejecutar el script
chmod +x check_smtp_ips.sh
./check_smtp_ips.sh- Actualización automatizada (opcional):
#!/bin/bash
# Script: update_smtp_hosts.sh
# Obtener nuevas IPs
NEW_IPS=$(dig smtp.improvmx.com +short A | grep -E '^[0-9]')
# Eliminar entradas antiguas
sudo sed -i '/smtp.improvmx.com/d' /etc/hosts
# Agregar nuevas IPs
for ip in $NEW_IPS; do
echo "$ip smtp.improvmx.com" | sudo tee -a /etc/hosts
done
echo "/etc/hosts actualizado con las IPs más recientes de smtp.improvmx.com"Solución alternativa - Deshabilitar IPv6:
Si prefieres una solución más permanente y no necesitas IPv6 en tu sistema:
# Verificar si IPv6 está habilitado
cat /proc/sys/net/ipv6/conf/all/disable_ipv6
# 0 = habilitado, 1 = deshabilitado
# Deshabilitar IPv6 temporalmente
sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1
sudo sysctl -w net.ipv6.conf.default.disable_ipv6=1
# Para hacer el cambio permanente, editar /etc/sysctl.conf
echo "net.ipv6.conf.all.disable_ipv6=1" | sudo tee -a /etc/sysctl.conf
echo "net.ipv6.conf.default.disable_ipv6=1" | sudo tee -a /etc/sysctl.conf
# Aplicar cambios
sudo sysctl -pPara un despliegue rápido y automatizado en producción:
# Ejecutar el instalador
sudo ./install_service.shEste método es recomendado porque:
- Automatiza todo el proceso de instalación
- Verifica dependencias y configuraciones
- Configura permisos y seguridad
- Habilita inicio automático en boot
- Incluye verificaciones post-instalación
Si prefieres una instalación manual más detallada:
- Crear entorno virtual:
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt- Configurar permisos:
sudo chown -R www-data:www-data /home/jose/webmail_improvmx- Instalar el servicio:
# Copiar el archivo de servicio
sudo cp improvmx-webhook.service /etc/systemd/system/
# Recargar systemd
sudo systemctl daemon-reload
# Habilitar inicio automático
sudo systemctl enable improvmx-webhook
# Iniciar el servicio
sudo systemctl start improvmx-webhook- Verificar instalación:
# Ver estado
sudo systemctl status improvmx-webhook
# Ver logs
sudo journalctl -u improvmx-webhook -fNota: Este webhook está diseñado para funcionar con Caddy como reverse proxy. Caddy maneja automáticamente:
- Terminación SSL/TLS
- Renovación automática de certificados
- Reverse proxy al puerto 42010
- Headers necesarios
Ejemplo de configuración de Caddy:
webhook.puntoa.ar {
reverse_proxy localhost:42010
# Caddy maneja SSL/TLS automáticamente con Let's Encrypt
# No se requiere configuración adicional de certificados
# Headers opcionales de seguridad
header {
X-Real-IP {remote_host}
X-Forwarded-For {remote_host}
X-Forwarded-Proto {scheme}
}
}Si prefieres usar Nginx en lugar de Caddy:
server {
listen 80;
server_name webhook.puntoa.ar;
location / {
proxy_pass http://localhost:42010;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}- Inicia sesión en tu cuenta de ImprovMX
- Ve a la configuración de tu dominio
- Configura el webhook URL:
https://tu-dominio.com/webhook- Importante: Usa HTTPS ya que Caddy maneja SSL automáticamente
- El puerto 42010 es interno y no debe incluirse en la URL pública
- Asegúrate de que el servidor sea accesible públicamente
- Verifica que el puerto 42010 esté abierto solo para conexiones locales desde Caddy
- Configura Caddy para hacer el reverse proxy del tráfico HTTPS al puerto 42010
Arquitectura de red:
ImprovMX → HTTPS → Caddy (SSL/TLS) → localhost:42010 → Flask App → MongoDB
Este proyecto es de código abierto y está disponible bajo la licencia MIT.
Las contribuciones son bienvenidas. Por favor, abre un issue o pull request para sugerencias.
Si encuentras algún problema o tienes preguntas, por favor abre un issue en el repositorio.