Cómo probar correos en tests automatizados

Cómo comprobar registros, restablecimientos de contraseña y códigos de confirmación en tests end-to-end con buzones reales, sin tests inestables.

Por Umut TufanogluActualizado el 20 de octubre de 20266 min de lectura

Registro, restablecimiento de contraseña, invitaciones, códigos de dos factores: muchos procesos importantes de una aplicación pasan por un correo. Aun así, la mayoría de los tests end-to-end terminan justo donde se envía el mensaje. Nadie comprueba si llega, si el enlace apunta al entorno correcto o si el código está en el texto. Esta guía muestra cómo cerrar ese hueco sin que tus tests se vuelvan inestables.

Tres niveles, tres herramientas

No todos los tests necesitan un buzón real. Tiene sentido dividirlos por niveles:

Nivel Qué compruebas Herramienta típica
Tests unitarios y de funcionalidad ¿Se lanza el mensaje correcto con los datos correctos? los fakes de correo del framework, como Mail::fake() en Laravel
Desarrollo local ¿Cómo se ve el mensaje, funcionan los enlaces? un capturador SMTP local como Mailpit
End-to-end en staging y CI ¿Llega el mensaje por la vía de envío real? un buzón real con API

Los dos primeros niveles son rápidos y deberían ser la mayoría de tus tests. Un buzón real solo lo necesitas para los pocos tests que cubren todo el recorrido.

Cuándo hace falta un buzón real

Los fakes de correo solo comprueban que tu aplicación quiso crear un mensaje. Lo que pasa después queda oculto. Un buzón real encuentra errores como estos:

  • El entorno de staging sigue usando el mailer de log y ningún mensaje sale del servidor.
  • El enlace de confirmación apunta a localhost o a producción en lugar de a staging.
  • El servicio de envío rechaza los mensajes porque el dominio del remitente no está verificado.
  • Un error en la plantilla rompe el código y ya no aparece en el texto.
  • La cola que envía los correos no está funcionando en ese entorno.

El patrón: crear, lanzar, esperar, leer, limpiar

Casi todos los tests de correo siguen los mismos cinco pasos:

  1. Crear: el test crea una dirección nueva y única.
  2. Lanzar: rellena el formulario de tu aplicación con esa dirección.
  3. Esperar: espera a que llegue el mensaje, con un límite fijo.
  4. Leer: saca el código o el enlace del mensaje y lo usa.
  5. Limpiar: vuelve a borrar la dirección.

Con la API de TossPost, en Playwright queda así:

import { test, expect } from '@playwright/test';

const api = 'https://tosspost.com/api/v1';
const headers = {
    Authorization: `Bearer ${process.env.TOSSPOST_TOKEN}`,
    Accept: 'application/json',
};

test('el registro envía un código de confirmación', async ({
    page,
    request,
}) => {
    const created = await request.post(`${api}/mailboxes`, {
        headers,
        data: { lifetime: 10 },
    });
    const { data: inbox } = await created.json();

    await page.goto('https://staging.example.com/registro');
    await page.getByLabel('Correo').fill(inbox.address);
    await page.getByRole('button', { name: 'Registrarse' }).click();

    const waited = await request.get(
        `${api}/mailboxes/${inbox.id}/messages/wait?timeout=30&since=${encodeURIComponent(inbox.created_at)}`,
        { headers, timeout: 35_000 },
    );
    expect(waited.status()).toBe(200);

    const { data: message } = await waited.json();
    await page.getByLabel('Código').fill(message.verification_code);

    await request.delete(`${api}/mailboxes/${inbox.id}`, { headers });
});

Esperar en lugar de dormir

El motivo más común de los tests de correo inestables es un sleep(5000) fijo. A veces no basta y a veces hace perder tiempo. Funciona mejor una petición que se queda abierta justo hasta que llega el mensaje.

El endpoint messages/wait hace eso: mantiene la petición abierta hasta 30 segundos y responde con 200 y el mensaje en cuanto llega. Si en ese tiempo no llega nada, responde con 204. El test falla entonces con un mensaje claro, en lugar de tropezar más adelante.

Atención al parámetro since. Sin él solo cuenta lo que llega después de que empiece la petición de espera. Si tu aplicación es más rápida que el test, el mensaje ya habría llegado y se perdería. Pasa como since el momento en que se creó el buzón (created_at) y no se te escapará ningún mensaje. Como esa hora viene del servidor, tampoco importa que el reloj de la máquina de CI vaya desajustado.

Leer códigos y enlaces

Cada mensaje trae los campos verification_code y verification_url. TossPost detecta los códigos y enlaces de confirmación en el texto al recibir el mensaje, así que no tienes que mantener tus propias expresiones regulares. Si necesitas más, por ejemplo comprobar una cabecera concreta o la estructura HTML, carga el mensaje completo con messages/{id} o el original con messages/{id}/raw.

Tests en paralelo

Nunca compartas una dirección entre tests. Si dos tests se ejecutan a la vez, uno leería el código del otro. Cada test crea su propio buzón; cuesta una sola petición.

Las peticiones en espera están limitadas por cuenta, porque cada una mantiene abierta una conexión. Con muchos tests en paralelo, la API responde 429 cuando todos los huecos están ocupados. Para suites grandes encajan mejor los webhooks: TossPost llama a una URL que elijas por cada mensaje nuevo, firmada con un HMAC-SHA256 que verificas antes de procesarla.

Consejos para tests de correo estables

  • Guarda el token como secreto: pon el token de la API en los secretos de tu CI, nunca en el repositorio.
  • Limita los permisos: da al token solo los permisos que necesita el test, como crear buzones y leer mensajes.
  • Elige una vida corta: un buzón que caduca a los diez minutos se limpia solo si el test se interrumpe.
  • Límites de tiempo claros: pon el timeout del cliente HTTP unos segundos por encima del límite de espera, para que la API responda primero.
  • Solo los procesos clave: prueba de extremo a extremo el registro, el restablecimiento de contraseña y las invitaciones. Los niveles más rápidos cubren todo lo demás.

Empezar

Para usar la API necesitas una cuenta de TossPost. Crea un token en Ajustes → API; la documentación de la API describe todos los endpoints, los webhooks y cómo verificar la firma.

Abre la documentación de la API: con ejemplos para curl y Playwright.

Escrito por Umut Tufanoglu, que desarrolla y gestiona TossPost. Acerca de TossPost

Pruébalo tú mismo