Los flujos de email son una de las partes más difíciles de probar correctamente en una aplicación web. Las confirmaciones de registro, los restablecimientos de contraseña, los emails transaccionales, las secuencias de incorporación — todos requieren una bandeja de entrada funcional para verificarse.
Usar direcciones de email reales para esto es una mala idea: satura las bandejas de entrada, arriesga enviar emails de prueba a usuarios reales y no escala para pruebas automatizadas. Las direcciones de email desechables y temporales resuelven los tres problemas.
Esta guía cubre el conjunto completo de herramientas — desde pruebas manuales rápidas con una bandeja temporal hasta la verificación de emails completamente automatizada en pipelines CI/CD.
El problema fundamental con las pruebas de email
Cuando construyes un flujo de email, necesitas verificar:
- El email fue realmente enviado
- Fue entregado (sin rebotes ni filtros de spam)
- El contenido es correcto (asunto, cuerpo, enlaces)
- Los enlaces del email funcionan correctamente (enlaces de confirmación, tokens de restablecimiento de contraseña)
- El flujo se completa correctamente después de hacer clic en el enlace
Hacer esto con direcciones de email reales crea varios problemas:
- Contaminación de bandejas de entrada — Las bandejas de tu equipo se llenan de cientos de emails de prueba
- Envíos accidentales a usuarios reales — Una variable de entorno mal configurada y tus emails de prueba van a usuarios de producción
- Sin acceso programático — No puedes leer una bandeja real desde un pipeline CI/CD sin OAuth o configuración IMAP
- Ciclos de retroalimentación lentos — Revisar manualmente una bandeja real para cada ejecución de prueba no escala
Categorías de herramientas para pruebas de email
Hay cuatro enfoques principales, cada uno adecuado para diferentes situaciones:
1. Bandejas de entrada desechables públicas (pruebas manuales)
Servicios como InstantTempEmail, Guerrilla Mail o 10 Minute Mail te dan una bandeja funcional al instante. Úsalos cuando estés probando manualmente un flujo y solo necesites recibir rápidamente un email de verificación.
Cuándo usarlos:
- Pruebas de desarrollo local de un nuevo flujo de email
- Verificaciones QA puntuales durante el desarrollo
- Probar cómo se renderiza tu email en una bandeja real
Cuándo no usarlos:
- Pruebas automatizadas — no puedes leer estas bandejas programáticamente en un conjunto de pruebas
2. Interceptores SMTP de desarrollo (automatizados, aislados)
Estas herramientas interceptan todos los emails salientes en tu entorno de desarrollo o staging — nada se entrega realmente a direcciones reales. Inspeccionas los emails capturados a través de una interfaz web o API.
Mailtrap es el más utilizado. Configuras tu aplicación para enviar a través de su servidor SMTP en entornos que no son de producción. Todos los emails van a una bandeja sandbox.
MailHog es una alternativa autoalojada — una aplicación Go ligera que ejecuta un servidor SMTP y una interfaz web localmente. Cero coste, cero dependencia externa.
Mailpit es un reemplazo más reciente de MailHog con mejor interfaz y desarrollo activo.
3. Bandejas de entrada API públicas (automatizadas, con acceso a la bandeja)
Servicios como Mailinator proporcionan bandejas accesibles a través de la API HTTP. Envías email a testuser@mailinator.com desde tu aplicación, luego verificas la bandeja a través de su API en tu conjunto de pruebas.
Cuándo usarlos:
- Pruebas de extremo a extremo que necesitan verificar el contenido del email y hacer clic en enlaces
- Pipelines CI/CD donde necesitas acceso a la bandeja sin OAuth
- Pruebas de flujos activados por email (confirmación → redirección → incorporación)
4. Servicios de prueba de email transaccional
Servicios como Mailtrap (su producto de prueba, separado del sandbox), el modo de prueba de Postmark o el modo sandbox de SendGrid prueban el pipeline completo de envío de email — llamadas API, renderizado, entregabilidad — sin enviar realmente.
Configurar MailHog para desarrollo local
MailHog es la forma más fácil de configurar pruebas de email localmente. Ejecuta un servidor SMTP que captura todo el correo saliente y lo muestra en una interfaz web.
Instalación con Docker Compose
Añade MailHog a tu docker-compose.yml:
services:
mailhog:
image: mailhog/mailhog
ports:
- "1025:1025" # Puerto SMTP
- "8025:8025" # Puerto interfaz web
restart: unless-stopped
Configura tu aplicación para usar MailHog
En tu .env para desarrollo:
MAIL_HOST=localhost
MAIL_PORT=1025
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_ENCRYPTION=null
Todos los emails que tu aplicación envía en desarrollo ahora van a MailHog. Accede a la bandeja en http://localhost:8025.
Por qué esto es mejor que el email real en desarrollo
- Los emails nunca salen de tu máquina — sin envíos accidentales a usuarios reales
- Entrega instantánea — sin esperar la entrega SMTP real
- Contenido completo del email visible en el navegador — cabeceras, HTML, partes de texto, adjuntos
- No requiere autenticación
- Fácil de limpiar entre ejecuciones de prueba
Pruebas de email automatizadas con la API de Mailinator
Para pruebas de extremo a extremo que necesitan leer emails programáticamente, la API de Mailinator es la opción más directa.
El nivel gratuito tiene límites, pero su API de pago es asequible para la mayoría de los equipos.
Verificación básica de bandeja con fetch
async function getLatestEmail(inboxName) {
const response = await fetch(
`https://mailinator.com/api/v2/domains/mailinator.com/inboxes/${inboxName}`,
{
headers: {
Authorization: process.env.MAILINATOR_API_KEY
}
}
)
const data = await response.json()
if (!data.msgs || data.msgs.length === 0) {
throw new Error(`No se encontraron emails en la bandeja: ${inboxName}`)
}
return data.msgs[0] // Email más reciente
}
async function getEmailBody(messageId) {
const response = await fetch(
`https://mailinator.com/api/v2/domains/mailinator.com/inboxes/test/messages/${messageId}`,
{
headers: {
Authorization: process.env.MAILINATOR_API_KEY
}
}
)
return response.json()
}
Prueba de registro de extremo a extremo completa con Playwright
import { test, expect } from '@playwright/test'
const TEST_EMAIL = `e2e-test-${Date.now()}@mailinator.com`
test('registro de usuario y confirmación por email', async ({ page }) => {
// Paso 1: Registrarse en el sitio
await page.goto('https://staging.tuapp.com/register')
await page.fill('[name="email"]', TEST_EMAIL)
await page.fill('[name="password"]', 'TestPassword123!')
await page.click('[type="submit"]')
// Esperar redirección a la página "revisa tu email"
await expect(page).toHaveURL(/check-email/)
// Paso 2: Esperar y obtener el email de confirmación
const email = await waitForEmail(TEST_EMAIL.split('@')[0], {
timeout: 30000,
subject: 'Confirma tu email'
})
// Paso 3: Extraer el enlace de confirmación del cuerpo del email
const confirmUrl = extractConfirmationLink(email.body)
expect(confirmUrl).toBeTruthy()
// Paso 4: Visitar el enlace de confirmación
await page.goto(confirmUrl)
// Paso 5: Verificar confirmación exitosa
await expect(page).toHaveURL(/dashboard/)
await expect(page.locator('h1')).toContainText('Bienvenido')
})
async function waitForEmail(inbox, options = {}) {
const { timeout = 15000, subject } = options
const startTime = Date.now()
while (Date.now() - startTime < timeout) {
const response = await fetch(
`https://mailinator.com/api/v2/domains/mailinator.com/inboxes/${inbox}`,
{ headers: { Authorization: process.env.MAILINATOR_API_KEY } }
)
const data = await response.json()
const emails = data.msgs || []
const match = subject
? emails.find(e => e.subject?.includes(subject))
: emails[0]
if (match) {
// Obtener el cuerpo completo del email
const bodyResponse = await fetch(
`https://mailinator.com/api/v2/domains/mailinator.com/inboxes/${inbox}/messages/${match.id}`,
{ headers: { Authorization: process.env.MAILINATOR_API_KEY } }
)
return bodyResponse.json()
}
await new Promise(resolve => setTimeout(resolve, 1000))
}
throw new Error(`Email no recibido en ${timeout}ms`)
}
function extractConfirmationLink(emailBody) {
const htmlContent = emailBody.parts?.[0]?.body || ''
const urlPattern = /https?:\/\/[^\s"'<>]+confirm[^\s"'<>]*/i
const match = htmlContent.match(urlPattern)
return match?.[0] || null
}
Usar direcciones únicas por ejecución de prueba
Un error común es reutilizar la misma dirección de email de prueba entre ejecuciones. Los emails antiguos contaminan la bandeja y las pruebas pueden recoger el email equivocado.
Patrón: direcciones únicas basadas en timestamp
// Generar una bandeja única por ejecución de prueba
const testRunId = Date.now()
const email = `registration-test-${testRunId}@mailinator.com`
Patrón: basado en UUID por prueba
import { randomUUID } from 'crypto'
function testEmail(prefix = 'test') {
return `${prefix}-${randomUUID().slice(0, 8)}@mailinator.com`
}
// Uso
const email = testEmail('registration') // registration-a1b2c3d4@mailinator.com
Esto garantiza que cada ejecución de prueba tenga una bandeja nueva sin historial.
Configurar Mailpit (reemplazo moderno de MailHog)
Mailpit tiene mejor interfaz, desarrollo activo y más características que MailHog. Es la mejor opción para nuevas configuraciones.
# docker-compose.yml
services:
mailpit:
image: axllent/mailpit
ports:
- "1025:1025" # SMTP
- "8025:8025" # Interfaz web
environment:
MP_MAX_MESSAGES: 500
MP_DATABASE: /data/mailpit.db
MP_SMTP_AUTH_ACCEPT_ANY: 1
MP_SMTP_AUTH_ALLOW_INSECURE: 1
volumes:
- mailpit_data:/data
volumes:
mailpit_data:
Mailpit también tiene una API REST para acceso programático a la bandeja, lo que lo hace viable para pruebas automatizadas en un entorno Docker Compose:
// API de Mailpit — para uso en configuraciones Docker Compose locales/CI
async function getMailpitInbox() {
const response = await fetch('http://localhost:8025/api/v1/messages')
return response.json()
}
async function deleteAllMessages() {
await fetch('http://localhost:8025/api/v1/messages', { method: 'DELETE' })
}
Limpia la bandeja al inicio de cada ejecución de prueba con deleteAllMessages() para garantizar un estado limpio.
Prevenir envíos accidentales a producción
Esto es crítico. Una variable de entorno mal configurada que apunta el staging a las credenciales SMTP de producción enviará emails reales a usuarios reales.
Enfoque 1: Conmutación SMTP basada en el entorno
// email.config.js
const emailConfig = {
development: {
host: 'localhost',
port: 1025,
secure: false,
auth: null
},
test: {
host: 'localhost',
port: 1025,
secure: false,
auth: null
},
staging: {
host: process.env.MAILHOG_HOST || 'mailhog',
port: 1025,
secure: false
},
production: {
host: process.env.SMTP_HOST,
port: 587,
secure: true,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS
}
}
}
export const smtpConfig = emailConfig[process.env.NODE_ENV] || emailConfig.development
Enfoque 2: Lista blanca de dominios para entornos que no son de producción
function shouldSendEmail(toAddress) {
if (process.env.NODE_ENV === 'production') return true
const allowedTestDomains = [
'mailinator.com',
'guerrillamail.com',
'instanttempemail.com',
'tuempresa.com' // Direcciones de prueba internas
]
const domain = toAddress.split('@')[1]
return allowedTestDomains.includes(domain)
}
Probar el renderizado de emails en diferentes clientes
Las herramientas anteriores verifican la entrega y el contenido de los emails, pero no cómo se renderiza el email en diferentes clientes. El HTML de los emails se comporta de forma diferente en Gmail, Outlook, Apple Mail y clientes móviles.
Para pruebas de renderizado, usa Email on Acid o Litmus — generan capturas de pantalla de tu email en más de 90 clientes. Ambos tienen pruebas gratuitas.
Para verificaciones más simples, la función de vista previa de email de Mailtrap renderiza tu email en contextos de clientes comunes.
Preguntas frecuentes
¿Puedo usar Gmail o Outlook para pruebas automatizadas? Técnicamente sí, usando OAuth o acceso IMAP. En la práctica, es más lento, más complejo de configurar e introduce límites de tasa y gestión de autenticación. Las herramientas dedicadas son siempre más rápidas y fiables.
¿Cuál es la diferencia entre Mailtrap y MailHog? Mailtrap es un servicio SaaS alojado — tus emails se envían a sus servidores y son visibles en su interfaz web. MailHog está autoalojado — se ejecuta en tu máquina o en tu configuración Docker Compose. MailHog tiene cero coste y cero datos saliendo de tu entorno. Mailtrap es más fácil de compartir entre un equipo.
¿Es seguro usar Mailinator para datos de prueba sensibles? No. Las bandejas de Mailinator son públicas — cualquiera que conozca el nombre de la bandeja puede leer los emails. Usa solo datos de prueba. Nunca uses datos de usuarios reales en bandejas de Mailinator. Para pruebas automatizadas privadas, usa MailHog o Mailpit en tu configuración Docker Compose.
¿Cómo pruebo flujos de email en un pipeline CI de GitHub Actions?
Añade un servicio MailHog o Mailpit a tu workflow de GitHub Actions usando la clave services en tu YAML de workflow. Tu aplicación envía a localhost:1025, y tus pruebas leen a través de la API local.
jobs:
test:
services:
mailpit:
image: axllent/mailpit
ports:
- 1025:1025
- 8025:8025
¿Debo probar con proveedores de email reales en staging? Solo si específicamente necesitas probar la entregabilidad (puntuación de spam, DKIM/DMARC, colocación en bandeja). Para pruebas funcionales de flujos de email, un interceptor local como MailHog es más rápido y seguro. Realiza verificaciones de entregabilidad reales por separado, con menos frecuencia, usando cuentas de prueba en los principales proveedores.