DEV Community

Hannah
Hannah

Posted on

SaaS: una bandeja por ejecución para probar emails

Cuando un SaaS empieza a crecer, los emails de prueba suelen quedarse en una bandeja compartida. Parece práctico: todos miran el mismo lugar y nadie tiene que aprender otra herramienta. Pero despues de unos cuantos sprints aparecen mensajes mezclados, enlaces vencidos y pruebas que pasan solo porque encontraron el código de otra ejecución.

Una bandeja por ejecución cambia esa historia. Cada test recibe un identificador, una dirección temporal y una regla clara de limpieza. No es una arquitectura enorme; es un pequeño contrato de backend que hace que las pruebas sean más faciles de repetir y de explicar.

El problema de una bandeja compartida

Imagina un flujo de registro que crea una cuenta, envía un código y confirma el email. Tres ramas de CI empiezan casi al mismo tiempo. Si usan el mismo buzón, cualquiera puede leer el mensaje equivocado. Un test puede pasar con un código creado por otra rama, mientras otro falla porque su mensaje ya fue consumido.

La situación empeora con los reintentos. Una ejecución fallida deja tres mensajes con el mismo asunto y un desarrollador no sabe cual pertenece al primer intento. Buscar frases como dummy e mail o tempail mail durante una sesión de debugging tampoco ayuda: esas expresiones deben ser datos de prueba, nunca una regla escondida que el sistema use para decidir qué mensaje escoger.

En productos SaaS, el coste no es solamente la inestabilidad. También se acumulan cuentas, invitaciones y tokens que nadie limpia. La bandeja acaba siendo una base de datos informal, sin dueño y sin fecha de caducidad.

Diseñar una bandeja por ejecución

El primer paso es crear un run_id al comienzo de cada ejecución. Puede ser corto y legible, por ejemplo ci-1842-7f3a. Después, deriva de ese valor un identificador de fixture y una dirección de prueba:

run_id:      ci-1842-7f3a
fixture_id:  signup-ci-1842-7f3a
email:       signup-ci-1842-7f3a@example.test
Enter fullscreen mode Exit fullscreen mode

No hace falta que el usuario real tenga acceso a esa dirección. El adaptador de email de staging debe poder consultar mensajes por fixture_id, asunto o destinatario, y devolver solo los mensajes asociados a la ejecución actual.

El contrato minimo debería responder estas preguntas:

  • ¿Quién creó el fixture y en qué entorno?
  • ¿Qué ejecución es la propietaria?
  • ¿Cuánto tiempo puede vivir?
  • ¿Qué método lo elimina después del test?
  • ¿Qué ocurre si el test termina a mitad del registro?

Si el proveedor de email no permite un buzón nuevo por test, se puede mantener una bandeja controlada y usar destinatarios únicos. La separación lógica importa más que el número de buzones físicos, aunque el filtro debe comprobar siempre el destinatario exacto.

Un contrato sencillo para el backend

Una API pequeña puede exponer tres operaciones: reservar, leer y liberar. El código siguiente es solo un ejemplo de la forma del contrato, no una dependencia concreta:

{
  "fixture_id": "signup-ci-1842-7f3a",
  "recipient": "signup-ci-1842-7f3a@example.test",
  "expires_at": "2026-10-05T04:00:00Z",
  "status": "reserved"
}
Enter fullscreen mode Exit fullscreen mode

reserve debe ser idempotente para el mismo run_id: si el job se reintenta, devuelve el fixture existente en lugar de crear un segundo recurso. read debería aceptar un límite de tiempo y devolver una respuesta explícita cuando no hay mensaje. release tiene que poder ejecutarse dos veces sin romperse, porque los hooks de limpieza no siempre corren en el orden esperado.

Guarda el estado de la reserva, no el contenido completo del mensaje por defecto. Si necesitas conservar el cuerpo para investigar un fallo, redáctalo y asigna una retención corta. Esa frontera es parecida a usar contratos mínimos para revisar prompts: cada componente debe declarar qué recibe, qué devuelve y qué no debe filtrar.

Limpieza, reintentos y privacidad

La limpieza debe ocurrir en dos lugares: al terminar normalmente y mediante un proceso de expiración. El primer camino mantiene el entorno ordenado; el segundo cubre cancelaciones, timeouts y agentes que se quedan sin conexión. Una métrica útil es la edad de los fixtures activos. Si sube cada semana, el problema no es solo de testing, es de operación.

Los reintentos también necesitan límites. Reintenta una consulta cuando el mensaje todavía puede estar en tránsito, pero no repitas una operación de negocio que ya creó una cuenta sin comprobar su idempotencia. Conserva el primer error y marca el segundo intento como recuperación; así el panel no convierte una prueba inestable en una prueba saludable.

Para los equipos que ejecutan pruebas en Kubernetes, un runbook para correos de guardia puede complementar este diseño: el fixture debe tener un propietario claro cuando el problema ocurre fuera del horario normal.

Hay tambien una regla sencilla de privacidad: no pongas códigos de verificación, tokens ni contraseñas en los logs de CI. Guarda el fixture_id, el asunto, los tiempos y un enlace interno a los artefactos protegidos. Un log pequeño pero útil vale más que copiar una bandeja entera.

Preguntas frecuentes

¿Necesito un buzón físico para cada test?

No. Puedes usar un proveedor controlado con destinatarios únicos y una consulta filtrada por ejecución. La condición es que dos tests no puedan leer el mismo mensaje por accidente.

¿Qué pasa si el email tarda demasiado?

Registra el tiempo de cada consulta y termina con un diagnóstico claro. Un timeout debe decir si nunca llegó el mensaje, si llegó a otro destinatario o si la prueba perdió su fixture. Aumentar el timeout sin esa información solo oculta el problema.

¿Es seguro usar un servicio de correo temporal en producción?

No para cuentas reales, recuperación de acceso o información sensible. Este patrón está pensado para staging, demos y pruebas aisladas. En producción, usa proveedores y políticas de retención aprobados por tu equipo.

Checklist para el siguiente sprint

  • Genera un run_id único antes de crear datos de prueba.
  • Asocia cada dirección a un fixture_id y a un propietario.
  • Filtra los mensajes por destinatario exacto, no solo por asunto.
  • Haz idempotentes reserve y release.
  • Añade expiración para cubrir jobs cancelados.
  • Conserva un recibo redacted de cada fallo, sin tokens.
  • Mide cuántos fixtures siguen vivos después de cada ejecución.

El cambio no requiere rehacer todo el sistema de emails. Empieza con un solo flujo de registro y observa si cada ejecución puede explicar qué dirección usó, qué mensaje leyó y cuándo liberó sus datos. Cuando esa respuesta sea clara, tu SaaS tendrá pruebas más reproducibles y un backend mucho menos misterioso.

Top comments (0)