DEV Community

Hannah
Hannah

Posted on

SaaS: prueba emails con un contrato de ejecución

En un SaaS, los emails parecen una pieza pequeña hasta que el primer test falla por leer el mensaje equivocado. Una ejecución crea una cuenta, otra confirma el mismo buzón temporal y una tercera recibe el correo de la primera. El resultado es un test intermitente que cuesta mas tiempo investigar que arreglar.

En mis experimentos de producto he encontrado una regla simple: cada ejecución de prueba debe tener dueño, identidad y una forma clara de terminar. No hace falta crear una plataforma enorme. Hace falta acordar un contrato entre el test, el Backend y el proveedor de correo.

Este enfoque complementa ideas para aislar el correo de cada ejecución y también ayuda cuando un flujo incluye un signup accesible para pruebas reales.

El problema de compartir una bandeja

Una bandeja compartida rompe la relación entre causa y evidencia. Si el test solo pregunta “¿hay un email nuevo?”, no sabe si ese email pertenece a su propia ejecución. Un reintento puede consumir el mensaje correcto antes que el test original, o una ejecución lenta puede encontrar datos que dejó otra.

Esto ocurre mucho en CI porque varios jobs corren al mismo tiempo. También ocurre en local: alguien repite el test y el correo anterior sigue ahí. Las cadenas de prueba suelen tener nombres raros, como fake e mail com, y luego nadie recuerda cual job las creó.

La solución no es esperar más. Añadir sleep(10) solo hace que el problema sea mas caro. La solución es dar contexto al email y validar ese contexto antes de aceptarlo.

El contrato de ejecución

Un contrato pequeño puede tener cuatro campos:

  1. run_id: identificador único del job o de la ejecución.
  2. mailbox_id: buzón temporal reservado para ese run_id.
  3. created_at: momento en que el test comenzó a esperar mensajes.
  4. expires_at: límite después del cual los datos ya no sirven.

Por ejemplo, el backend puede crear este registro antes de abrir el formulario:

{
  "run_id": "ci-1842-signup-07",
  "mailbox_id": "qa-ci-1842-07",
  "purpose": "signup-verification",
  "expires_at": "2026-10-06T09:00:00Z"
}
Enter fullscreen mode Exit fullscreen mode

El nombre exacto puede cambiar. Lo importante es que el test conserve el run_id y lo use en cada consulta. Un correo temporal como tempmailso puede servir para pruebas controladas, siempre que el equipo trate el contenido como dato de test y no como una cuenta real.

Un diseño pequeño para el backend

El endpoint de creación debería devolver una identidad de ejecución, no solo una dirección de correo. Así el cliente de pruebas no necesita adivinar qué mensaje pertenece a qué job.

POST /test-fixtures/mailboxes
Idempotency-Key: ci-1842-signup-07

{
  "purpose": "signup-verification",
  "ttl_seconds": 900
}
Enter fullscreen mode Exit fullscreen mode

La respuesta puede incluir run_id, mailbox_id y una fecha de expiración. En la consulta, el backend debe filtrar por el buzón y por un cursor o timestamp. De esta forma un mensaje viejo no pasa solo porque contiene la palabra “verify”.

También conviene que la limpieza tenga un responsable. Un worker puede borrar fixtures expirados, pero el job debe intentar liberar su buzón en un bloque finally. Si el job muere antes, el TTL sigue siendo la red de seguridad.

Qué debe comprobar el test

Un test de verificación de email debería comprobar, en este orden:

  1. Que el mailbox_id corresponde a su propio run_id.
  2. Que el mensaje llegó después de iniciar la ejecución.
  3. Que el destinatario esperado coincide, sin comparar solo el asunto.
  4. Que el token no está expirado y puede usarse una sola vez.
  5. Que el mensaje queda marcado como consumido.

El cuarto punto suele olvidarse. Un correo correcto pero antiguo puede producir un falso positivo. La prueba pasa, aunque el flujo actual nunca haya enviado nada.

Para depurar, guarda un recibo pequeño: run_id, estado, timestamps, proveedor y razón del fallo. No guardes el cuerpo completo si no hace falta. El recibo permite saber si el problema fue creación, entrega, lectura o limpieza, y hace el resultado bastante mas útil para quien mantiene el SaaS.

Errores comunes

El primer error es usar una dirección fija para todos los tests. Es cómodo al principio, pero se vuelve una cola compartida. El segundo es usar el asunto como identificador único; dos mensajes pueden tener el mismo asunto.

El tercero es mezclar formato y entrega. Validar que la dirección tiene forma correcta no demuestra que el email llegó. Son comprobaciones diferentes y deben tener tiempos y mensajes distintos.

El cuarto es borrar evidencia demasiado pronto. Durante desarrollo, conserva el recibo hasta que el job termine y registra solo los datos necesarios. En producción, aplica una retención corta y documentada.

Por último, no escondas toda la lógica detrás de reintentos. Un reintento con el mismo contrato puede ser seguro; un reintento que crea otra identidad sin marcar la anterior deja basura y vuelve confuso el diagnóstico.

Recapitulación

Un contrato de ejecución convierte las pruebas de email de una búsqueda ambigua en una conversación clara entre el test y el Backend:

  • cada job tiene un run_id único;
  • cada buzón temporal tiene una fecha de caducidad;
  • cada lectura valida identidad, tiempo y destinatario;
  • cada fixture tiene limpieza y un recibo mínimo;
  • los reintentos conservan el contexto original.

Empieza con un solo flujo de signup. Añade el registro, filtra los mensajes por ejecución y mide los fallos durante una semana. Cuando el patrón sea estable, puedes llevarlo a otros emails del SaaS sin cambiar toda la arquitectura. Es un cambio pequeño, pero hace que los tests sean mucho mas fáciles de confiar.

Top comments (0)