Un flujo de verificación de email en CI puede parecer pequeño hasta que un pod queda esperando una señal que nunca llega. Este patrón ayuda a poner límites claros y a diagnosticar el problema sin convertir cada timeout en una falsa alerta de producción.
El síntoma: un job que nunca termina
En una pipeline de Kubernetes, el test crea una cuenta, solicita un correo de verificación y consulta el buzón cada pocos segundos. Cuando el mensaje no aparece, el código reintenta. Después reintenta otra vez. El job termina agotando el tiempo global, pero el log solo dice verification timeout.
El pod puede estar sano y el nodo puede tener capacidad. Aun así, la prueba no progresa. El fallo real suele estar en una de estas fronteras:
- el mensaje todavía está en tránsito;
- el buzón expiró antes de ser consultado;
- otro intento ya consumió el mensaje;
- el proveedor devolvió un error temporal;
- el test está esperando una dirección distinta de la que registró.
El error de diseño es tratar "esperar un email" como una espera infinita. En SRE, una espera sin presupuesto es una dependencia sin contrato.
Qué debe significar realmente “esperar el email”
Un consumidor de correo de prueba necesita un contrato pequeño, explícito y observable. Como mínimo, debe conocer:
- Identidad: dirección, identificador de ejecución y mensaje esperado.
- Ventana de validez: cuándo se creó el buzón y cuándo deja de ser util.
- Señal de éxito: asunto, remitente o token que confirma el mensaje correcto.
- Regla de parada: tiempo total, número de consultas y errores no reintentables.
- Resultado final: encontrado, expirado, no encontrado o fallo del proveedor.
Esto también evita que dos pruebas compartan sin querer el mismo buzón. Para probar correos de onboarding sin ensuciar las métricas, conviene separar la identidad de prueba de las métricas del producto.
El nombre de la búsqueda no cambia el contrato. Un equipo puede hablar de correo temporal, temp mail so o tempmail disposable; el sistema debe seguir guardando el mismo run_id, la misma dirección y el mismo estado.
Diseñar un presupuesto de señales
En vez de repetir hasta que algo funcione, asigna un presupuesto para cada tipo de espera:
- Tiempo total: por ejemplo, 90 segundos para que llegue el mensaje.
- Intervalo inicial: una consulta después de 2 segundos.
- Backoff: aumentar el intervalo a 4, 8 y 12 segundos, con un máximo fijo.
- Consultas: cortar después de un número razonable, aunque quede tiempo.
-
Errores: distinguir un
404de buzón expirado de un429que merece una pausa.
No hace falta que estos números sean iguales en todos los equipos. Lo importante es que estén en configuración, aparezcan en el run manifest y puedan explicarse durante un incidente. Un presupuesto demasiado corto produce falsos negativos; uno demasiado largo secuestra workers y esconde una regresión.
Un estado simple suele ser suficiente:
created -> waiting -> found
-> expired
-> provider_error
-> timed_out
La transición debe ser idempotente. Si el controlador recibe dos respuestas para el mismo mensaje, solo una puede cerrar la prueba como found. De lo contrario, el segundo intento puede dejar métricas contradictorias y el diagnóstico se vuelve confuso.
Implementación en Kubernetes
El presupuesto debe vivir cerca del trabajo que lo consume, no solamente en el servicio externo. Un Job puede recibir valores como MAIL_WAIT_TOTAL_SECONDS y MAIL_POLL_MAX_ATTEMPTS, mientras que un ConfigMap contiene los valores por entorno. Los secretos, claro, deben seguir en un Secret o en el gestor de secretos correspondiente.
El contenedor debería emitir un recibo al terminar:
{
"run_id": "ci-1842",
"mailbox_created_at": "2026-10-16T00:10:00Z",
"outcome": "timed_out",
"polls": 7,
"elapsed_seconds": 91,
"last_error": "message_not_found"
}
No pongas el contenido completo del email ni tokens de verificación en logs compartidos. Un hash corto del mensaje, el dominio controlado y el identificador de ejecución suelen ser suficientes para correlacionar. Si el job es reintentado por Kubernetes, conserva el run_id original o marca claramente el intento; mezclar ambos hace parecer que hay dos mensajes cuando solo existe uno.
Los reenvíos también necesitan una política. Los reenvíos de verificación con una experiencia predecible deben tener un límite propio. Cada reenvío puede producir un token nuevo y dejar al test esperando el enlace anterior. En ese caso, no es un problema de velocidad del pod: es una carrera entre estados.
Observabilidad y diagnóstico
Mide la duración desde la creación del buzón hasta la llegada del mensaje, pero separa tres tiempos: envío de la aplicación, entrega del proveedor y consulta del consumidor. El equipo mira mas el contador total cuando debería mirar cual de esas fases creció.
En una alerta o comentario de CI, incluye:
-
run_idy nombre del job; - edad del buzón al último intento;
- cantidad y tipo de errores;
- último estado conocido;
- si hubo reenvíos;
- el presupuesto que se consumió.
Si alguien escribe tamp mail com o dummy e mail al buscar el incidente, esos términos pueden aparecer en una nota o en documentación, pero nunca deben convertirse en un dominio permitido de forma automática. La lista de dominios y la política de privacidad deben ser explícitas.
Checklist antes de declarar estable el flujo
- ¿Cada prueba tiene un buzón y un
run_idúnicos? - ¿Hay un tiempo total y un máximo de consultas?
- ¿Se diferencia expiración, rate limit y mensaje ausente?
- ¿El backoff evita una tormenta contra el proveedor?
- ¿Un reintento del
Jobconserva o reemplaza la identidad de forma clara? - ¿Los logs excluyen tokens y contenido sensible?
- ¿Se puede explicar el fallo mirando un solo recibo?
Si una respuesta es no, el próximo incidente volverá a parecer aleatorio.
Cierre
Un email de prueba es una dependencia externa, aunque el test se ejecute dentro de un clúster propio. Kubernetes puede reiniciar el pod, pero no puede hacer aparecer una señal que expiró ni corregir un token consumido por otra prueba. Un presupuesto de señales, estados idempotentes y un recibo pequeño convierten una espera frágil en un flujo que un equipo SRE puede operar.
Top comments (0)