Por qué reemplazar MailHog por Mailpit
MailHog no se ha mantenido desde finales de 2022. Acumula dependencias obsoletas, su interfaz data de los primeros años de Bootstrap y su API nunca evolucionó. Mailpit aborda cada uno de estos puntos: está en desarrollo activo (última versión en 2024), ofrece una interfaz reactiva en tiempo real, y expone una API REST retrocompatible con la de MailHog para no romper los scripts existentes.
El cambio no requiere migración de datos: Mailpit no persiste nada por defecto. Cada reinicio comienza con un buzón vacío, que es exactamente el comportamiento deseado en un pipeline de pruebas.
Lo que Mailpit aporta en la práctica
- Interfaz moderna con búsqueda de texto completo, visualización HTML/texto, previsualización móvil e inspección de cabeceras
- Binario único de Go compilado estáticamente — sin dependencias del sistema, funciona en un contenedor de 20 MB
- Consumo de memoria inferior a 50 MB en reposo, estable incluso con miles de mensajes en sesión
- API REST retrocompatible con MailHog (
/api/v1/messages,/api/v1/search) para scripts existentes - Soporte SMTP autenticado, TLS opcional y control de acceso por red
- Webhook configurable para notificar a un endpoint externo en cada recepción de correo
Prerrequisitos
Un VPS con Debian 12 o Ubuntu 22.04/24.04 con Docker y Docker Compose v2 instalados es suficiente. Mailpit está entre las herramientas más ligeras del catálogo: planifica 1 vCPU compartida y 256 MB de RAM para uso en desarrollo o CI. Se recomienda un subdominio dedicado (mail.your-domain.com) para acceder a la interfaz desde el navegador vía HTTPS.
Puertos utilizados: 1025 (SMTP) y 8025 (interfaz web). Si un servicio de correo ya está funcionando en el puerto 1025, deberás cambiar el puerto de Mailpit (ver la sección de resolución de problemas).
Desplegar Mailpit en 5 pasos
Crear el directorio y el archivo Compose
mkdir -p /opt/mailpit && cd /opt/mailpitCrea el siguiente archivo
docker-compose.yml:services: mailpit: image: axllent/mailpit:latest container_name: mailpit restart: unless-stopped ports: - "127.0.0.1:1025:1025" - "127.0.0.1:8025:8025" environment: MP_MAX_MESSAGES: 500 MP_SMTP_AUTH_ACCEPT_ANY: "true" MP_SMTP_AUTH_ALLOW_INSECURE: "true" MP_UI_AUTH: "admin:changeme" volumes: - mailpit_data:/data volumes: mailpit_data:Los puertos están vinculados a
127.0.0.1únicamente: Mailpit nunca se expone directamente en la IP pública. La variableMP_UI_AUTHactiva la autenticación HTTP básica en la interfaz web — cambia la contraseña.MP_MAX_MESSAGESlimita el número de correos conservados en memoria por sesión.Iniciar el contenedor
docker compose up -d docker compose logs -f mailpitLa línea
starting on [::]:1025confirma que el servidor SMTP está escuchando. Verifica que los puertos estén correctamente vinculados a localhost:ss -tlnp | grep -E '1025|8025'Esperado:
127.0.0.1:1025y127.0.0.1:8025únicamente — nunca0.0.0.0.Configurar el proxy inverso Nginx
Crea
/etc/nginx/sites-available/mailpit:server { listen 443 ssl; server_name mail.your-domain.com; ssl_certificate /etc/letsencrypt/live/mail.your-domain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/mail.your-domain.com/privkey.pem; location / { proxy_pass http://127.0.0.1:8025; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_read_timeout 86400; } }Activa el vhost y recarga Nginx:
ln -s /etc/nginx/sites-available/mailpit /etc/nginx/sites-enabled/ nginx -t && systemctl reload nginxObtén un certificado Let's Encrypt si aún no lo has hecho:
certbot --nginx -d mail.your-domain.comProbar el envío con curl
Verifica que el servidor SMTP recibe un mensaje:
curl --url 'smtp://127.0.0.1:1025' \ --mail-from '[email protected]' \ --mail-rcpt '[email protected]' \ --upload-file - <<EOF From: [email protected] To: [email protected] Subject: Mailpit Test Cuerpo del mensaje de prueba. EOFAbre
https://mail.your-domain.comen tu navegador. El mensaje debe aparecer en la interfaz.Configurar tu aplicación para apuntar a Mailpit
En tu archivo
.env(Laravel, Symfony, n8n, Django…), reemplaza la configuración SMTP con:MAIL_HOST=127.0.0.1 MAIL_PORT=1025 MAIL_ENCRYPTION=null MAIL_USERNAME=null MAIL_PASSWORD=nullPara aplicaciones que corren en Docker en el mismo host, usa el nombre de red Docker o la IP de la pasarela (
172.17.0.1por defecto) en lugar de127.0.0.1.
Mailpit en CI/CD: probar correos en el pipeline
En entornos CI, Mailpit se integra como servicio sidecar. Arranca en segundos y no requiere configuración externa.
Integración CI/CD por framework
GitHub Actions — servicio Mailpit
services: mailpit: image: axllent/mailpit:latest ports: - 1025:1025 - 8025:8025En las variables de entorno de tu job, define
MAIL_HOST=127.0.0.1yMAIL_PORT=1025. Mailpit acepta cualquier correo sin autenticación por defecto.Laravel — PHPUnit
En
phpunit.xml, reemplaza el transporte con SMTP:<env name="MAIL_MAILER" value="smtp"/> <env name="MAIL_HOST" value="127.0.0.1"/> <env name="MAIL_PORT" value="1025"/>En tu test, envía el correo y consulta la API de Mailpit para verificar la recepción:
$response = Http::get('http://127.0.0.1:8025/api/v1/messages'); $this->assertSame($response->json('total'), 1); $this->assertStringContainsString( 'Bienvenido', $response->json('messages.0.Subject') );Django — pytest
En
settings.py(osettings_test.py):EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' EMAIL_HOST = '127.0.0.1' EMAIL_PORT = 1025En tu test pytest, verifica mediante la API de Mailpit:
import requests def test_welcome_email_sent(): r = requests.get('http://127.0.0.1:8025/api/v1/messages') assert r.json()['total'] == 1 assert 'Bienvenido' in r.json()['messages'][0]['Subject']n8n — pruebas de flujo
En el nodo Send Email de n8n, configura el servidor SMTP en
127.0.0.1:1025sin autenticación. Para verificar automáticamente que un flujo de trabajo disparó un envío, llama aGET http://127.0.0.1:8025/api/v1/messagesdesde un nodo HTTP Request y valida el total en un nodo IF.
Asegurar el acceso a Mailpit
Mailpit almacena los correos solo en memoria — sin archivos en disco, sin base de datos. Esta es una ventaja para la confidencialidad (sin rastros persistentes), pero también una limitación: un reinicio vacía el buzón sin previo aviso.
Restricción de red. Los puertos 1025 y 8025 están vinculados a 127.0.0.1 en la configuración de Docker Compose anterior. Nunca los expongas en 0.0.0.0 en producción o en un servidor compartido.
Autenticación HTTP básica en la interfaz. La variable MP_UI_AUTH=user:password activa autenticación básica en la interfaz web. Usa una contraseña fuerte.
SMTP autenticado. Para entornos multiequipo, activa MP_SMTP_AUTH con un archivo de cuentas en lugar de MP_SMTP_AUTH_ACCEPT_ANY.
Consejo: vaciar el buzón entre pruebas
La API de Mailpit expone un endpoint de purga: DELETE http://127.0.0.1:8025/api/v1/messages. Llámalo en tu setUp() de PHPUnit o fixture de pytest para comenzar cada prueba con un buzón vacío, sin necesidad de reiniciar el contenedor.
Resolución de problemas — 4 casos frecuentes
Puerto 1025 ya en uso. Un servidor Postfix o Sendmail local puede estar escuchando en este puerto. Verifica con ss -tlnp | grep 1025. Si es así, cambia el puerto del host en docker-compose.yml (ej. 1026:1025) y actualiza MAIL_PORT en tus aplicaciones.
Acceso denegado desde un contenedor CI. Los contenedores Docker en GitHub Actions pueden acceder al 127.0.0.1 del runner, pero no a otros servicios via localhost si la red no está correctamente compartida. Usa la red host del runner o referencia el servicio por su nombre (mailpit:1025) en el archivo de flujo.
Interfaz inaccesible detrás del proxy inverso. Las conexiones WebSocket de la interfaz en tiempo real pueden cortarse por un timeout agresivo de Nginx. Verifica que proxy_read_timeout esté a 86400 y que las cabeceras Upgrade y Connection se transmitan.
Rendimiento con miles de correos. Mailpit guarda todo en memoria. Si tu pipeline genera cientos de correos por ejecución, establece MP_MAX_MESSAGES en un valor razonable (100-500).
Mailpit vs MailHog vs Mailtrap vs Inbucket vs smtp4dev
Desplace la tabla
| Herramienta | Mantenimiento | RAM en reposo | REST API | Persistencia | Auth SMTP |
|---|---|---|---|---|---|
| **Mailpit** | Activo (2024) | < 50 MB | Sí (v1 + v2) | Solo memoria | Opcional |
| MailHog | Abandonado (2022) | ~30 MB | Sí (solo v1) | Solo memoria | No |
| Mailtrap (SaaS) | Activo | N/A (nube) | Sí | Nube (30 días) | Sí (nube) |
| Inbucket | Activo | ~20 MB | Sí | Memoria o disco | No |
| smtp4dev | Activo | ~80 MB (.NET) | Sí | SQLite opcional | No |
Casos de uso avanzados
Intercepción multi-proyecto. Una sola instancia de Mailpit puede recibir correos de múltiples proyectos simultáneamente. Usa la búsqueda de texto completo o filtra por destinatario en la interfaz para aislar mensajes de cada aplicación.
Webhook a Slack o n8n. La variable MP_WEBHOOK_URL dispara un POST JSON en cada recepción. Conéctala a un flujo n8n para enrutar ciertos correos de prueba a un canal Slack del equipo.
Exportar y reproducir. La API expone correos en formato EML (GET /api/v1/message/<id>.eml). Descarga un mensaje interceptado para reproducirlo en un cliente de correo real o para usarlo como fixture de prueba.
Mailpit en un stack de desarrollo completo. Añade Mailpit como servicio en el docker-compose.yml principal de tu proyecto. Se integra naturalmente con Laravel Sail, Symfony Docker o n8n autoalojado — un solo docker compose up inicia todo el entorno de desarrollo con intercepción SMTP incluida.