Alex CarterUn patrón SRE para aislar buzones temporales en Kubernetes, medir fallos de email y limpiar cada ejecución sin sorpresas.
En un pipeline de CI, un email de prueba parece un detalle pequeño. Hasta que una ejecución reutiliza un buzón temporal de ayer, recibe un mensaje atrasado y termina verde por casualidad. O hasta que cinco jobs comparten la misma dirección y uno consume el código de verificación del otro.
En equipos que operan Kubernetes, el problema no se resuelve solamente con generar correo desechable. La pregunta importante es: ¿qué contrato de fiabilidad tiene ese recurso dentro de una ejecución? Si no podemos responderlo, el test está observando un estado ambiguo.
Este es el patrón que uso para pensar estos incidentes: un buzón por ejecución, una identidad trazable, límites explícitos y una limpieza que tenga dueño.
Imaginemos un deployment efímero para probar un flujo de registro. El job crea un usuario, espera el email y confirma el enlace. En el dashboard solo vemos que el paso final falló después de 90 segundos. El pod sigue vivo, el buzón conserva mensajes antiguos y el namespace tiene varios secretos de pruebas.
La investigación suele descubrir una de estas causas:
El error no es solo de la aplicación. Es un error de contrato entre CI, Kubernetes y el proveedor de email. Un SRE necesita poder distinguir un fallo real del producto, un fallo de infraestructura y un dato de prueba que llegó tarde.
Antes de crear recursos, escribo cuatro promesas simples:
message_not_found, provider_error, test_assertion y cleanup_error.El run ID debe viajar por todos los límites: etiqueta del namespace, nombre de la fixture, metadato del mensaje y contexto del job. Una etiqueta como run=20261006-0315-a7f2 es mas útil que un nombre genérico como email-test.
También conviene definir qué significa “recibido”. No es lo mismo que el proveedor acepte el mensaje, que la API lo devuelva, o que el cuerpo contenga el enlace esperado. Esta distinción ahorra bastante tiempo cuando el incidente ocurre de madrugada.
El job de CI puede crear una fixture y pasar solo su referencia al pod de pruebas:
env:
TEST_RUN_ID: "20261006-0315-a7f2"
MAILBOX_REF: "ci/20261006-0315-a7f2"
El contenedor que consulta el email no debería conocer credenciales globales ni buscar por asunto sin más. Recibe un selector de ejecución y aplica una fecha mínima de creación. Si el proveedor soporta un identificador de mensaje, se guarda ese recibo antes de validar el contenido.
En Kubernetes, el namespace efímero puede tener un ResourceQuota pequeño y un ttlSecondsAfterFinished donde aplique. Para los recursos que no tienen TTL nativo, un cleanup job debe ejecutarse en una ruta finally, incluso si el test fue cancelado. La limpieza no deberia depender de que el runner conserve el workspace.
Un punto práctico: conserva un recibo mínimo, no el email completo. Por ejemplo, guarda el hash del mensaje, el timestamp, el run ID, el resultado y la latencia. Así el equipo puede reconstruir la secuencia sin convertir los logs en un archivo de datos personales.
Si el flujo también usa automatización con agentes LLM, aplica el mismo contrato: la herramienta recibe un alcance limitado y no puede leer bandejas de otras ejecuciones. Un agente que reintenta sin presupuesto puede convertir un test lento en una tormenta de consultas.
No hace falta empezar con veinte métricas. Estas cuatro suelen explicar el incidente:
email_fixture_create_seconds: cuánto tarda en estar disponible la fixture.email_poll_attempts: cuántas consultas necesita cada ejecución.email_delivery_age_seconds: edad del mensaje cuando se consume.email_cleanup_result: éxito, reintento o abandono.Pon límites concretos: por ejemplo, 12 intentos cada 5 segundos y un timeout total de 75 segundos. El número exacto depende del proveedor, pero debe ser menor que el timeout general del job para dejar tiempo a cerrar recursos. Si se alcanza el límite, el error debe decir qué se esperaba, desde cuando y con qué run ID.
Los dashboards deberían agrupar por causa, no solo por estado rojo o verde. Una tasa alta de provider_error pide una investigación distinta a una tasa alta de message_not_found. Para una revisión rapida, un enlace a la ejecución y un recibo redacted valen mas que una captura del pod.
En la interfaz que dispara el flujo, ayuda validar emails sin esperar a ciegas: estados como “creando fixture”, “esperando mensaje” y “limpiando” hacen visible dónde se consume el tiempo. No arregla el backend, pero evita que el operador repita clicks y multiplique ejecuciones.
Cuando falle una prueba de email en Kubernetes, revisa en este orden:
Si aparece una búsqueda manual como tepm mail com o tamp mail com, trátala como una señal de que alguien está intentando resolver el problema fuera del flujo controlado. No es una estrategia operativa: es deuda de observabilidad.
El objetivo no es que cada test sea perfecto. Es que cada fallo sea acotado, explicable y barato de retirar. Cuando un buzón temporal tiene dueño, caducidad y recibo, Kubernetes deja de ser el lugar donde se esconden los tests intermitentes y pasa a ser parte de la evidencia.