<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alex Carter</title>
    <description>The latest articles on DEV Community by Alex Carter (@alexcarteruk).</description>
    <link>https://dev.to/alexcarteruk</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3832787%2F6c2a651c-6ecb-4fcd-ad18-b957fd195786.png</url>
      <title>DEV Community: Alex Carter</title>
      <link>https://dev.to/alexcarteruk</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alexcarteruk"/>
    <language>en</language>
    <item>
      <title>Terraform: un plan de cambio que se pueda explicar</title>
      <dc:creator>Alex Carter</dc:creator>
      <pubDate>Sat, 31 Oct 2026 23:22:45 +0000</pubDate>
      <link>https://dev.to/alexcarteruk/terraform-un-plan-de-cambio-que-se-pueda-explicar-18ki</link>
      <guid>https://dev.to/alexcarteruk/terraform-un-plan-de-cambio-que-se-pueda-explicar-18ki</guid>
      <description>&lt;p&gt;Cuando un cambio de infraestructura falla, el problema no siempre es que Terraform haya calculado mal. Muchas veces el plan era correcto, pero nadie pudo explicar a tiempo qué iba a cambiar, qué evidencia faltaba o cómo volver atrás. Para una guardia SRE, un plan útil es también un contrato de comunicación.&lt;/p&gt;

&lt;h2&gt;
  
  
  El síntoma: un plan correcto que nadie entiende
&lt;/h2&gt;

&lt;p&gt;El incidente suele empezar con una frase pequeña: “solo son unos recursos”. Después aparecen decenas de líneas, un &lt;code&gt;-/+&lt;/code&gt; inesperado y una aprobación que se hace con prisa. El plan pasa, pero la confianza no.&lt;/p&gt;

&lt;p&gt;He visto que el riesgo sube cuando el equipo revisa el número de recursos en vez de revisar las consecuencias. Crear dos recursos no es automáticamente seguro; reemplazar uno puede romper una dependencia que no aparece en el título del pull request. Un plan sin contexto se vuelve una lista, no una decisión.&lt;/p&gt;

&lt;p&gt;La primera señal de alerta es que la persona que aprueba no puede contestar tres preguntas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;¿Qué comportamiento cambia para el usuario?&lt;/li&gt;
&lt;li&gt;¿Qué dependencia puede quedar inestable?&lt;/li&gt;
&lt;li&gt;¿Cuál es el paso concreto para recuperar el servicio?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Si la respuesta es “lo vemos después del apply”, ya hay deuda de operación.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué debe responder un buen plan
&lt;/h2&gt;

&lt;p&gt;Un plan de Terraform debería ser legible para alguien que no escribió el módulo. Antes de mirar los detalles, dejo un resumen corto en el pull request:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Intención:&lt;/strong&gt; qué capacidad se añade o qué problema se corrige.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alcance:&lt;/strong&gt; cuentas, regiones, clusters y entornos afectados.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cambio sensible:&lt;/strong&gt; reemplazos, permisos, reglas de red o datos que se destruyen.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependencias:&lt;/strong&gt; qué servicio debe estar sano antes de aplicar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recuperación:&lt;/strong&gt; qué se revierte y qué no puede revertirse automáticamente.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Luego comparo ese resumen con el plan real. Si el texto dice “actualizar una regla” pero Terraform muestra un reemplazo, el cambio se detiene. Esa pausa es barata; una sorpresa en producción no lo es.&lt;/p&gt;

&lt;p&gt;También conviene guardar el hash del commit, la versión del proveedor y el workspace usado. Parece detalle menor, pero evita discutir sobre un plan que ya no corresponde al código actual. En la practica, muchos minutos se pierden por esa confusión.&lt;/p&gt;

&lt;h2&gt;
  
  
  Un flujo de revisión que funciona en guardia
&lt;/h2&gt;

&lt;p&gt;Uso cuatro pasos, incluso para cambios que parecen rutinarios.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Generar el plan en un entorno reproducible
&lt;/h3&gt;

&lt;p&gt;El job debe registrar el commit, la versión de Terraform, los proveedores y el estado remoto. Si el estado cambia durante la revisión, se genera un plan nuevo. No se aplica un archivo viejo solo porque ya tiene una aprobación.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Separar cambios esperados y cambios laterales
&lt;/h3&gt;

&lt;p&gt;Marco los recursos que forman parte de la intención y dejo aparte todo lo demás. Un cambio lateral puede ser drift, una variable compartida o una lectura distinta del proveedor. No lo escondo para que el diff se vea bonito: lo investigo.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Probar la explicación, no solo la sintaxis
&lt;/h3&gt;

&lt;p&gt;Otra persona lee el resumen y responde qué pasará si el proveedor devuelve un error a mitad del apply. Si no entiende el flujo, falta documentación. Esta revisión funciona como una pequeña prueba de contrato entre Terraform y la operación.&lt;/p&gt;

&lt;p&gt;En cambios que disparan notificaciones o validaciones por correo, dejo una evidencia verificable y con tiempo de expiración. Para depurar señales de correo tras un drenado, puede servir &lt;a href="https://dev.to/alexcarteruk/kubernetes-correos-claros-para-drenados-de-nodo-5e41"&gt;señales de correo en un drenado de nodo&lt;/a&gt;; la idea es la misma: cada señal debe llevar contexto suficiente para no depender de memoria humana. A veces el test aparece escrito como “tamp mail com” o “temp org mail” en una nota antigua; lo trato como texto de prueba, no como una identidad real.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Acordar una ventana de observación
&lt;/h3&gt;

&lt;p&gt;El apply no termina cuando el comando devuelve código cero. Defino qué métricas mirar, durante cuánto tiempo y qué umbral detiene el cambio. En flujos con email también conviene &lt;a href="https://dev.to/silviutech/react-validacion-de-email-sin-friccion-visual-44hh"&gt;validar la evidencia de un email&lt;/a&gt;, porque un mensaje enviado no significa que el usuario haya completado el paso siguiente.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cómo dejar preparado el rollback
&lt;/h2&gt;

&lt;p&gt;Un rollback confiable empieza antes del apply. Primero clasifico el cambio:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reversible:&lt;/strong&gt; se puede volver al commit y aplicar sin migración adicional.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reversible con cuidado:&lt;/strong&gt; necesita restaurar una variable, una regla o un secreto.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No reversible:&lt;/strong&gt; modifica datos, consume una migración o cambia una dependencia externa.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Para los dos últimos casos, el runbook incluye una copia o exportación comprobada, el propietario de la decisión y una condición clara de parada. “Ejecutar el commit anterior” no es suficiente si el recurso ya fue reemplazado o si el proveedor conserva un efecto fuera del estado.&lt;/p&gt;

&lt;p&gt;Un patrón práctico es usar dos artefactos: el plan que se va a aplicar y un documento breve de recuperación. El segundo contiene comandos, señales esperadas y el último punto seguro. No incluyo secretos en ninguno de los dos. Si el rollback requiere improvisar, todavía no está listo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist antes de aplicar
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] El plan fue generado desde el commit que se revisó.&lt;/li&gt;
&lt;li&gt;[ ] La cuenta, región y workspace están confirmados.&lt;/li&gt;
&lt;li&gt;[ ] Los reemplazos y permisos tienen una explicación explícita.&lt;/li&gt;
&lt;li&gt;[ ] El drift fue investigado o aceptado con un dueño.&lt;/li&gt;
&lt;li&gt;[ ] Existe un último punto seguro y un procedimiento de recuperación.&lt;/li&gt;
&lt;li&gt;[ ] Las métricas y logs de observación están abiertos.&lt;/li&gt;
&lt;li&gt;[ ] La ventana permite esperar el resultado, no solo lanzar el comando.&lt;/li&gt;
&lt;li&gt;[ ] El equipo sabe quién puede detener el cambio.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Si una casilla queda vacía, el cambio puede esperar. La presión de “aplicar antes del stand-up” rara vez compensa una dependencia que nadie ha mirado.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusión
&lt;/h2&gt;

&lt;p&gt;Terraform produce un plan técnico, pero la guardia necesita una decisión operable. Un buen flujo conecta intención, diff, dependencias, observación y recuperación. Cuando cualquiera del equipo puede explicar qué cambia y cómo parar, el plan deja de ser una salida de consola y se convierte en una herramienta SRE.&lt;/p&gt;

&lt;p&gt;No hace falta añadir más automatización a cada incidente. A veces basta con un resumen honesto, un artefacto reproducible y un rollback que otra persona pueda ejecutar sin adivinar. Esa claridad es pequeña, pero aguanta mejor cuando la noche se complica.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>sre</category>
      <category>devops</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Terraform: un rollback que SRE pueda ejecutar</title>
      <dc:creator>Alex Carter</dc:creator>
      <pubDate>Sat, 31 Oct 2026 02:22:44 +0000</pubDate>
      <link>https://dev.to/alexcarteruk/terraform-un-rollback-que-sre-pueda-ejecutar-2kpd</link>
      <guid>https://dev.to/alexcarteruk/terraform-un-rollback-que-sre-pueda-ejecutar-2kpd</guid>
      <description>&lt;p&gt;Un rollback de Terraform no deberia ser una frase que aparece cuando ya estamos nerviosos. En una guardia real, "vuelve al commit anterior" casi nunca alcanza: puede haber migraciones, cambios manuales, recursos compartidos o un provider que ya no calcula el mismo diff.&lt;/p&gt;

&lt;p&gt;En equipos de plataforma he aprendido a tratar el rollback como un contrato operativo. El objetivo no es prometer que todo vuelve a ser igual, sino dejar claro que accion se puede ejecutar, que evidencia debemos guardar y como sabremos que la recuperacion termino bien. Parece menos heroico, pero ayuda bastante cuando el incidente ocurre a las tres de la mañana.&lt;/p&gt;

&lt;h2&gt;
  
  
  El rollback no empieza con &lt;code&gt;terraform apply&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;El primer error suele ser correr &lt;code&gt;terraform plan&lt;/code&gt; con prisa y asumir que el plan inverso es automaticamente seguro. Terraform conoce el estado que tiene registrado, pero no conoce toda la historia de las decisiones alrededor del recurso.&lt;/p&gt;

&lt;p&gt;Antes de tocar nada, el on-call necesita responder cuatro preguntas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;¿Que cambio causo el problema y cual es su identificador de despliegue?&lt;/li&gt;
&lt;li&gt;¿El rollback modifica solo infraestructura o tambien datos y contratos de aplicacion?&lt;/li&gt;
&lt;li&gt;¿Que recursos fueron cambiados fuera de Terraform desde el ultimo apply?&lt;/li&gt;
&lt;li&gt;¿Cual es la señal observable que confirma la recuperacion?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Si una respuesta depende de buscar en cinco canales, el procedimiento no esta listo. Las &lt;a href="https://dev.to/alexcarteruk/terraform-alertas-de-drift-con-contexto-514k"&gt;alertas de drift con contexto&lt;/a&gt; son utiles precisamente porque reducen esa reconstruccion manual.&lt;/p&gt;

&lt;h2&gt;
  
  
  Que debe contener un contrato de rollback
&lt;/h2&gt;

&lt;p&gt;Un runbook pequeño puede incluir estos campos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Alcance:&lt;/strong&gt; workspace, cuenta cloud, modulo y recursos afectados.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version conocida:&lt;/strong&gt; commit, imagen o artefacto que queremos restaurar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Precondiciones:&lt;/strong&gt; backups validos, lock de estado disponible y ausencia de migraciones pendientes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comando de inspeccion:&lt;/strong&gt; el plan que se debe guardar antes de aplicar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Señal de exito:&lt;/strong&gt; metrica, health check o prueba funcional concreta.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Punto de parada:&lt;/strong&gt; condicion que obliga a escalar en vez de seguir probando.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Esta lista no elimina el riesgo. Lo hace visible. Un rollback que necesita borrar un recurso antes de recrearlo debe decirlo con claridad; esconderlo bajo un modulo "simple" es una mala sorpresa para el siguiente turno.&lt;/p&gt;

&lt;h2&gt;
  
  
  Un flujo seguro para ejecutar la vuelta atras
&lt;/h2&gt;

&lt;p&gt;Un flujo pequeño, repetible y con evidencia se parece a esto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform workspace &lt;span class="k"&gt;select &lt;/span&gt;prod
terraform plan &lt;span class="nt"&gt;-out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rollback.tfplan
terraform show &lt;span class="nt"&gt;-no-color&lt;/span&gt; rollback.tfplan &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; rollback-plan.txt
terraform apply rollback.tfplan
terraform output &lt;span class="nt"&gt;-json&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; rollback-output.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El comando no es la parte importante. Antes de &lt;code&gt;apply&lt;/code&gt;, guarda el plan y confirma que el diff corresponde al incidente, no a un drift nuevo. Despues del cambio, registra el identificador del apply y ejecuta una comprobacion que atraviese la ruta critica.&lt;/p&gt;

&lt;p&gt;Si el cambio afecta un servicio que envia correos, no uses la entrega de email como unica señal. Un chequeo de API, una metrica de errores y una prueba funcional dan mejor cobertura. Para interfaces de prueba, tambien conviene revisar que los estados de carga y error sean visibles; la &lt;a href="https://dev.to/silviutech/react-validacion-de-email-sin-friccion-visual-44hh"&gt;validacion de email sin friccion visual&lt;/a&gt; recuerda que una señal que existe pero no se puede interpretar no ayuda mucho.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pruebas de CI que realmente ayudan
&lt;/h2&gt;

&lt;p&gt;El pipeline deberia probar el camino de recuperacion, no solo el camino feliz. Algunas comprobaciones razonables son:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;validar que el plan de rollback no cambia recursos fuera del alcance&lt;/li&gt;
&lt;li&gt;comprobar que el estado remoto puede bloquearse y desbloquearse&lt;/li&gt;
&lt;li&gt;probar un recurso efimero en un workspace aislado&lt;/li&gt;
&lt;li&gt;conservar el plan como artefacto durante la ventana del incidente&lt;/li&gt;
&lt;li&gt;verificar que el smoke test falla si el servicio sigue apuntando a la configuracion rota&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;En esa ultima prueba es normal necesitar datos de correo. Un generador de cuentas de correo temporal o un correo temporal gratis puede servir para validar el flujo en un entorno aislado, pero no debe convertirse en una identidad de produccion. Tampoco conviene guardar mensajes o tokens reales en los logs. He visto pipelines con &lt;code&gt;fake e mail com&lt;/code&gt; escrito como dato de prueba; funciona como marcador, aunque es mejor documentar por que existe y cuando se elimina.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist para el on-call
&lt;/h2&gt;

&lt;p&gt;Antes de ejecutar la vuelta atras, revisaria esta lista:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;el incidente y el cambio tienen un ID comun&lt;/li&gt;
&lt;li&gt;el plan fue generado con el workspace correcto&lt;/li&gt;
&lt;li&gt;el diff no incluye recursos ajenos al incidente&lt;/li&gt;
&lt;li&gt;existe una copia del estado o un backup verificable&lt;/li&gt;
&lt;li&gt;alguien conoce el punto de parada y el canal de escalado&lt;/li&gt;
&lt;li&gt;la señal de exito esta definida antes del apply&lt;/li&gt;
&lt;li&gt;el smoke test cubre la dependencia mas importante&lt;/li&gt;
&lt;li&gt;el resultado queda adjunto al ticket, no solo en el chat&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Un detalle que suele olvidarse: despues del rollback hay que explicar que queda pendiente. Si el cambio manual sigue presente o el modulo necesita una correccion, el incidente no termino del todo. La infraestructura puede estar estable y el sistema de delivery aun estar en una situacion incomoda.&lt;/p&gt;

&lt;h2&gt;
  
  
  Q&amp;amp;A
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ¿Hay que revertir siempre al commit anterior?
&lt;/h3&gt;

&lt;p&gt;No. El commit anterior puede contener otro problema o no ser compatible con el estado actual. Elige la version que restaure el comportamiento y valida el plan antes de aplicar.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Un rollback debe ser automatico?
&lt;/h3&gt;

&lt;p&gt;Solo cuando el alcance, las precondiciones y la señal de exito son muy claros. Para recursos con datos o permisos sensibles, una pausa humana sigue siendo una buena defensa.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Como se mide si el runbook funciona?
&lt;/h3&gt;

&lt;p&gt;Mide el tiempo hasta la primera accion segura, el tiempo hasta recuperar la señal critica y cuantos pasos requieren conocimiento tribal. Si el runbook siempre necesita una llamada privada, todavia le falta contexto.&lt;/p&gt;

&lt;p&gt;La meta no es que el on-call memorice Terraform. Es que pueda tomar una decision razonable con evidencia suficiente, incluso cuando el incidente llega en el peor momento.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>sre</category>
      <category>devops</category>
      <category>security</category>
    </item>
    <item>
      <title>Kubernetes: un presupuesto de fallos para email en CI</title>
      <dc:creator>Alex Carter</dc:creator>
      <pubDate>Fri, 30 Oct 2026 23:22:44 +0000</pubDate>
      <link>https://dev.to/alexcarteruk/kubernetes-un-presupuesto-de-fallos-para-email-en-ci-3op3</link>
      <guid>https://dev.to/alexcarteruk/kubernetes-un-presupuesto-de-fallos-para-email-en-ci-3op3</guid>
      <description>&lt;p&gt;Un pipeline de CI puede estar verde y aun así esconder un problema serio: los emails de prueba no llegan, el worker reintenta sin límite y las alertas se convierten en ruido. El equipo ve muchos pods ocupados, pero no sabe si el fallo está en la aplicación, en la red o en el buzón.&lt;/p&gt;

&lt;p&gt;En una guardia aprendí a tratar el correo de prueba como una dependencia con presupuesto de fallos. No es un SLO perfecto ni una promesa de entrega instantánea. Es un límite operativo que ayuda a decidir cuándo reintentar, cuándo aislar el caso y cuándo parar para conservar evidencia.&lt;/p&gt;

&lt;h2&gt;
  
  
  El incidente: el pipeline que reintenta hasta empeorar la señal
&lt;/h2&gt;

&lt;p&gt;Imagina una suite que crea una cuenta, espera un mensaje de verificación y confirma el enlace. El mensaje tarda unos segundos mas de lo normal. El test hace polling, el job vence su timeout y Kubernetes lo reinicia. El nuevo pod vuelve a crear la cuenta y abre otra espera.&lt;/p&gt;

&lt;p&gt;Después de unos minutos tenemos varias consecuencias:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;muchos mensajes para la misma ejecución;&lt;/li&gt;
&lt;li&gt;logs mezclados entre pods y ramas;&lt;/li&gt;
&lt;li&gt;más carga sobre el servicio de email;&lt;/li&gt;
&lt;li&gt;un reporte que dice “timeout”, pero no muestra el último evento conocido.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;El problema no es solo la latencia. Es que el sistema no distingue una entrega lenta de un intento duplicado. Buscar términos como &lt;code&gt;tempmailso&lt;/code&gt; o &lt;code&gt;temp mail so&lt;/code&gt; puede llevar al equipo a probar buzones distintos, pero no arregla la ausencia de una identidad de ejecución.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué significa presupuesto de fallos para un email
&lt;/h2&gt;

&lt;p&gt;Un presupuesto sencillo puede expresarse con tres límites:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Tiempo total:&lt;/strong&gt; cuánto puede esperar un test por el mensaje, por ejemplo 90 segundos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Número de consultas:&lt;/strong&gt; cuántas veces se consulta el buzón, con backoff y jitter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Intentos de envío:&lt;/strong&gt; cuántas solicitudes de envío se permiten para una misma ejecución.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Estos valores dependen del sistema. Lo importante es que estén en el contrato del job y no escondidos en un &lt;code&gt;while true&lt;/code&gt;. Si se consume el presupuesto, el worker debe terminar con un diagnóstico: &lt;code&gt;delivery_budget_exhausted&lt;/code&gt;, el &lt;code&gt;run_id&lt;/code&gt;, la última consulta y la edad del mensaje esperado.&lt;/p&gt;

&lt;p&gt;También conviene separar un fallo de producto de uno de infraestructura. Si el endpoint aceptó el envío, pero el buzón no recibió nada dentro del tiempo acordado, la evidencia es distinta a una respuesta HTTP 500 o a un error de autenticación. Esa diferencia ahorra una investigación bastante larga.&lt;/p&gt;

&lt;h2&gt;
  
  
  Un contrato simple para el worker de Kubernetes
&lt;/h2&gt;

&lt;p&gt;El pod necesita límites explícitos y un resultado que otro sistema pueda leer. Un fragmento de configuración podría verse así:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;batch/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Job&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;generateName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;email-check-&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;workload&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ci-email-check&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;backoffLimit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;activeDeadlineSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;120&lt;/span&gt;
  &lt;span class="na"&gt;ttlSecondsAfterFinished&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3600&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;restartPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Never&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;checker&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;registry.example.com/ci-email-check:stable&lt;/span&gt;
          &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DELIVERY_BUDGET_SECONDS&lt;/span&gt;
              &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;90"&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MAX_INBOX_POLLS&lt;/span&gt;
              &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;12"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;backoffLimit&lt;/code&gt; protege al Job de reinicios infinitos, mientras &lt;code&gt;activeDeadlineSeconds&lt;/code&gt; marca un límite de vida. No sustituyen el timeout del cliente: son capas distintas. El cliente debe cortar la espera de forma limpia y escribir su recibo antes de salir.&lt;/p&gt;

&lt;p&gt;Para una implementación real, usaría esperas crecientes, por ejemplo 2, 4, 8 y 12 segundos, con un pequeño jitter. El polling rapido puede crear más tráfico sin hacer que el mensaje llegue antes. Y si cada prueba comparte un buzón, un resultado válido puede pertenecer a otra rama. Un buzón aislado por ejecución, o al menos un asunto con &lt;code&gt;run_id&lt;/code&gt;, es mucho más facil de depurar.&lt;/p&gt;

&lt;p&gt;Si el equipo está diseñando el endpoint que crea ese buzón, puede complementar este enfoque con los &lt;a href="https://dev.to/oliviachen7/fastapi-contratos-para-correos-temporales-en-ci-43j6"&gt;contratos para correos temporales en CI&lt;/a&gt;. El contrato de la API debería decir qué identifica al mensaje y cómo se informa una expiración, no solo devolver una dirección.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alertas y evidencia para no perseguir fantasmas
&lt;/h2&gt;

&lt;p&gt;No alertaría por cada timeout individual. Primero registraría métricas agrupadas por servicio, rama y causa:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;porcentaje de jobs que consumen más del 80% del presupuesto;&lt;/li&gt;
&lt;li&gt;tiempo desde el envío hasta el primer mensaje visible;&lt;/li&gt;
&lt;li&gt;cantidad de mensajes descartados por &lt;code&gt;run_id&lt;/code&gt; incorrecto;&lt;/li&gt;
&lt;li&gt;reinicios del Job y razón de terminación;&lt;/li&gt;
&lt;li&gt;diferencia entre “envío aceptado” y “mensaje observado”.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Una alerta útil podría decir: “el 15% de los checks de email agotaron su presupuesto en 10 minutos”. Eso permite comparar con una ventana anterior sin culpar al primer pod que falló. El número es solo un ejemplo de umbral; cada equipo debe medir su comportamiento normal antes de fijarlo.&lt;/p&gt;

&lt;p&gt;Para flujos con aprobaciones manuales o agentes, también sirve guardar una evidencia mínima del evento. Este patrón de &lt;a href="https://dev.to/silviutech/llms-aprobaciones-por-email-con-contexto-minimo-jf1"&gt;aprobaciones por email con contexto mínimo&lt;/a&gt; recuerda una regla útil: el siguiente paso debe saber por qué está actuando, no solo que “llegó algo”.&lt;/p&gt;

&lt;p&gt;En documentación de pruebas aparecen a veces variantes como &lt;code&gt;tamp mail com&lt;/code&gt;. No las usaría para identificar el sistema ni para construir una alerta; son texto de búsqueda y pueden contaminar dashboards si no se normalizan.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist de operación
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;¿Cada envío tiene un &lt;code&gt;run_id&lt;/code&gt; único y visible en logs?&lt;/li&gt;
&lt;li&gt;¿El cliente tiene límite de tiempo, consultas y envíos?&lt;/li&gt;
&lt;li&gt;¿El Job de Kubernetes tiene &lt;code&gt;backoffLimit&lt;/code&gt; y deadline?&lt;/li&gt;
&lt;li&gt;¿Un reinicio puede duplicar una cuenta o un mensaje?&lt;/li&gt;
&lt;li&gt;¿El resultado distingue entrega lenta, error HTTP y buzón equivocado?&lt;/li&gt;
&lt;li&gt;¿La alerta agrupa fallos en vez de notificar cada intento?&lt;/li&gt;
&lt;li&gt;¿Se conserva el recibo suficiente para reproducir la investigación?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La idea no es hacer que el email parezca más confiable de lo que es. Es convertir una dependencia externa en una parte observable del sistema. Con un presupuesto claro, el pipeline falla antes, pero explica mejor por qué.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preguntas rápidas
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ¿Debo reintentar siempre?
&lt;/h3&gt;

&lt;p&gt;No. Reintenta solo errores transitorios y dentro del presupuesto. Un error de autenticación o un &lt;code&gt;run_id&lt;/code&gt; desconocido necesita detenerse, no más polling.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Dónde encaja un generador de correos falsos?
&lt;/h3&gt;

&lt;p&gt;Un &lt;a href="https://tempmailso.com" rel="noopener noreferrer"&gt;generador de correos falsos&lt;/a&gt; puede ser útil para crear buzones de prueba en un entorno controlado. No reemplaza el contrato de identidad, los límites ni la observabilidad del pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Qué hago cuando se agota el presupuesto?
&lt;/h3&gt;

&lt;p&gt;Marca el job como fallido, guarda la última evidencia y evita reabrir automáticamente la misma ejecución. Así la siguiente investigación empieza con datos, no con otra cascada de retries.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>sre</category>
      <category>devops</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Kubernetes: un SLO para el correo de CI</title>
      <dc:creator>Alex Carter</dc:creator>
      <pubDate>Wed, 28 Oct 2026 08:22:56 +0000</pubDate>
      <link>https://dev.to/alexcarteruk/kubernetes-un-slo-para-el-correo-de-ci-2imc</link>
      <guid>https://dev.to/alexcarteruk/kubernetes-un-slo-para-el-correo-de-ci-2imc</guid>
      <description>&lt;p&gt;Un pipeline de CI puede tener todos sus tests en verde y aun asi esconder un fallo importante: la aplicación genero el correo, pero nadie pudo comprobar que llego. En equipos que prueban registro, recuperación de cuenta o invitaciones, un &lt;code&gt;temp mailbox&lt;/code&gt; controlado ayuda a separar la entrega real de una simple respuesta &lt;code&gt;200&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;La lección operativa es tratar el correo como una dependencia con un objetivo de servicio. No hace falta convertir cada prueba en una plataforma enorme. Basta con definir que significa “entregado”, asignar un presupuesto de tiempo y guardar evidencia suficiente para investigar cuando el job falla.&lt;/p&gt;

&lt;h2&gt;
  
  
  La entrega del correo también necesita un SLO
&lt;/h2&gt;

&lt;p&gt;Un SLO para un correo de prueba no promete que todos los mensajes del producto lleguen en un tiempo fijo. Describe una condición concreta del pipeline. Por ejemplo: “el mensaje de verificación debe aparecer en el buzón aislado dentro de 90 segundos, con el asunto esperado y un enlace para el entorno de staging”.&lt;/p&gt;

&lt;p&gt;Esta definición evita una confusión muy comun: que el API acepte el mensaje no significa que el correo este disponible para el test. Hay al menos tres saltos: la aplicación publica el evento, el proveedor de correo lo procesa y el buzón lo expone para lectura.&lt;/p&gt;

&lt;p&gt;Si el equipo ya trabaja con APIs, conviene separar los reintentos de negocio de los reintentos del test. Este enfoque sobre &lt;a href="https://dev.to/silviutech/fastapi-retries-seguros-en-verificacion-email-36f1-temp-slug-3306053?preview=e511fe95224baf342143ffca3d2b39de392a79e80b270b24ccec93a2b6a35316325b51d5c6ac6dd0aaff53e2df3c182be92a4a84d01363313b9338b1"&gt;reintentos seguros en la verificación de email&lt;/a&gt; ayuda a no duplicar acciones solo porque una lectura fue lenta.&lt;/p&gt;

&lt;h2&gt;
  
  
  Define el contrato antes del job de Kubernetes
&lt;/h2&gt;

&lt;p&gt;El job debe conocer el contrato de correo antes de arrancar. Un contrato pequeño puede incluir:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Identidad:&lt;/strong&gt; un buzón nuevo por ejecución, o un identificador de ejecución que no se repita.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mensaje esperado:&lt;/strong&gt; remitente, asunto, tipo de evento y una marca como &lt;code&gt;run_id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ventana:&lt;/strong&gt; cuanto tiempo esperar antes de declarar el caso como fallido.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seguridad:&lt;/strong&gt; no poner tokens reales ni datos de usuarios en el mensaje de prueba.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evidencia:&lt;/strong&gt; guardar el ID del mensaje, timestamps y el motivo de cada intento.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La identidad es especialmente importante en Kubernetes. Si dos pods comparten el mismo buzón, un test puede leer el correo de otro test y pasar por accidente. Un nombre como &lt;code&gt;ci-${GITHUB_RUN_ID}-${JOB_ID}&lt;/code&gt; hace visible el límite de propiedad. Si el proveedor no permite crear buzones de forma automatica, se puede usar una colección controlada de buzones y reservar uno por lease.&lt;/p&gt;

&lt;p&gt;Para escenarios manuales, un &lt;code&gt;create temporary mail&lt;/code&gt; puede servir para inspeccionar un flujo puntual. En CI, sin embargo, lo importante no es tener una dirección rapida, sino que el buzón tenga una interfaz de lectura estable y que la ejecución pueda demostrar qué mensaje consumió.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mide tres tiempos, no solo el resultado final
&lt;/h2&gt;

&lt;p&gt;Un resultado binario —pasó o falló— es pobre para SRE. Registra al menos estos tiempos:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Tiempo de publicación:&lt;/strong&gt; desde que el test pide la acción hasta que la aplicación confirma el evento.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tiempo de transporte:&lt;/strong&gt; desde ese evento hasta que el proveedor indica que aceptó el mensaje.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tiempo de disponibilidad:&lt;/strong&gt; desde la aceptación hasta que el mensaje puede leerse en el buzón.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Con esas marcas, el diagnóstico es mas corto. Si la publicación tarda, el problema puede estar en la aplicación o en la cola. Si la aceptación es rápida pero la disponibilidad se demora, hay que mirar el proveedor, el polling o los límites del buzón. Si el mensaje aparece pero no contiene el &lt;code&gt;run_id&lt;/code&gt;, el fallo es de aislamiento, no de latencia.&lt;/p&gt;

&lt;p&gt;Un pequeño registro JSON por ejecución es suficiente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"run_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ci-4821"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mail_event_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"evt-91ab"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"published_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-28T08:10:02Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"accepted_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-28T08:10:04Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"visible_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-28T08:10:19Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"attempts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subject_match"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No guardes el cuerpo completo si no hace falta. Un hash del mensaje, el asunto y los metadatos suelen bastar para investigar sin ampliar la superficie de datos sensibles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reintentos y fallos que sí ayudan a investigar
&lt;/h2&gt;

&lt;p&gt;El polling debe tener una pausa y un limite. Reintentar cada milisegundo solo añade ruido y puede activar rate limits. Un backoff corto con un máximo, por ejemplo 2, 4, 8 y 16 segundos, suele ser mas amable con el proveedor. El job debe distinguir entre “todavía no visible”, “mensaje incorrecto” y “buzón no disponible”. Son fallos diferentes y piden acciones diferentes.&lt;/p&gt;

&lt;p&gt;También conviene emitir una métrica por causa, no solamente un contador de jobs fallidos. &lt;code&gt;email_visibility_timeout&lt;/code&gt;, &lt;code&gt;wrong_run_id&lt;/code&gt; y &lt;code&gt;mailbox_auth_error&lt;/code&gt; cuentan historias distintas. En una guardia, esa diferencia ahorra bastante tiempo.&lt;/p&gt;

&lt;p&gt;El test no deberia reintentar una operación que crea un usuario si la primera petición pudo completarse. Primero consulta el estado con el identificador de ejecución; despues decide si hace falta repetir. Un correo repetido puede ocultar un problema de idempotencia.&lt;/p&gt;

&lt;p&gt;Para no convertir staging en una papelera, aplica una retención breve y elimina los buzones o mensajes al terminar. Las pruebas de onboarding merecen la misma disciplina; puedes ver otro enfoque en &lt;a href="https://dev.to/hannahdev56/como-probar-correos-de-onboarding-en-un-saas-sin-ensuciar-metricas-pjn"&gt;probar correos de onboarding sin ensuciar métricas&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preguntas frecuentes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ¿Es obligatorio usar un proveedor de correo real?
&lt;/h3&gt;

&lt;p&gt;No siempre. Un servidor SMTP local sirve para validar que la aplicación construye el mensaje. Para validar entrega, reglas del proveedor y lectura desde un buzón, necesitas una frontera mas parecida a producción. La elección depende de qué parte del contrato quieres cubrir.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Qué hago si el mensaje tarda más que el SLO?
&lt;/h3&gt;

&lt;p&gt;Marca el job como fallido con el registro de tiempos y conserva la evidencia mínima. No aumentes la ventana cada vez que falla: primero comprueba si el retraso es de transporte, polling o aislamiento. Un SLO que cambia para ocultar ruido deja de ser util.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Puedo reutilizar el mismo buzón?
&lt;/h3&gt;

&lt;p&gt;Solo si cada mensaje tiene un identificador único, el consumidor filtra por él y existe limpieza confiable. Un buzón dedicado por ejecución es mas simple de razonar, aunque puede costar más en trabajos muy frecuentes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist para el pipeline
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] El mensaje contiene un &lt;code&gt;run_id&lt;/code&gt; no reutilizado.&lt;/li&gt;
&lt;li&gt;[ ] El job tiene un presupuesto de entrega y un máximo de polling.&lt;/li&gt;
&lt;li&gt;[ ] Se registran publicación, aceptación y disponibilidad.&lt;/li&gt;
&lt;li&gt;[ ] Los reintentos no repiten operaciones no idempotentes.&lt;/li&gt;
&lt;li&gt;[ ] Los errores separan latencia, identidad y autenticación.&lt;/li&gt;
&lt;li&gt;[ ] No se envían datos reales al correo de prueba.&lt;/li&gt;
&lt;li&gt;[ ] Hay limpieza y retención definida para mensajes y buzones.&lt;/li&gt;
&lt;li&gt;[ ] El dashboard muestra tendencias, no solo el ultimo fallo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;En documentación informal alguien puede escribir “tempail” o “tem email”, pero el pipeline debe usar un vocabulario y un contrato exactos. Para SRE, la diferencia entre un correo aceptado y uno verificablemente disponible no es un detalle: es la frontera que convierte una prueba intermitente en una señal operable.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>sre</category>
      <category>devops</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Kubernetes: señales de correo en los retries</title>
      <dc:creator>Alex Carter</dc:creator>
      <pubDate>Sun, 25 Oct 2026 11:23:01 +0000</pubDate>
      <link>https://dev.to/alexcarteruk/kubernetes-senales-de-correo-en-los-retries-2f06</link>
      <guid>https://dev.to/alexcarteruk/kubernetes-senales-de-correo-en-los-retries-2f06</guid>
      <description>&lt;p&gt;En un pipeline de CI, un email de verificación parece una dependencia pequeña. Luego llega un fallo intermitente: el pod recibe el mensaje, el test expira, Kubernetes reintenta el job y el segundo intento encuentra un buzón vacío o un enlace ya consumido. El resultado es un rojo difícil de explicar: ¿falló la aplicación, el proveedor de correo o la coordinación del pipeline?&lt;/p&gt;

&lt;p&gt;La solución no es subir el timeout hasta que todo parezca estable. Es tratar el correo de prueba como una dependencia observable, con identidad, tiempo de vida y señales que sobrevivan a los retries.&lt;/p&gt;

&lt;h2&gt;
  
  
  El síntoma: un retry que cambia la evidencia
&lt;/h2&gt;

&lt;p&gt;Imagina un job que crea un usuario, solicita una verificación y busca el enlace en un buzón temporal. Si el proceso muere después de crear el usuario pero antes de leer el mensaje, el retry puede crear otro usuario o reutilizar el primero. En ambos casos, la evidencia queda mezclada.&lt;/p&gt;

&lt;p&gt;El equipo ve mensajes como “verification email not found”, pero ese mensaje no dice si el buzón nunca existió, si la entrega tardó, si el selector era incorrecto o si otro intento consumió el mensaje. Un SRE necesita distinguir esas causas antes de elegir entre reintentar, bloquear el despliegue o abrir una incidencia.&lt;/p&gt;

&lt;p&gt;Para eso conviene asignar un &lt;code&gt;run_id&lt;/code&gt; estable a cada ejecución y un &lt;code&gt;mailbox_id&lt;/code&gt; derivado de ella. El pod puede reiniciarse, pero la relación entre job, dirección y mensaje debe seguir siendo clara.&lt;/p&gt;

&lt;h2&gt;
  
  
  Define el contrato antes de tocar Kubernetes
&lt;/h2&gt;

&lt;p&gt;Un contrato sencillo para el flujo puede tener estas reglas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cada ejecución crea una identidad de prueba única.&lt;/li&gt;
&lt;li&gt;El job nunca busca mensajes de otra ejecución.&lt;/li&gt;
&lt;li&gt;Leer un email es una operación idempotente para el test.&lt;/li&gt;
&lt;li&gt;El cleanup ocurre después de guardar la evidencia mínima.&lt;/li&gt;
&lt;li&gt;Un retry conserva el &lt;code&gt;run_id&lt;/code&gt;, pero registra un nuevo intento.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;El cuarto punto suele olvidarse. Si borras el buzón al terminar el primer intento, el retry ya no puede saber qué ocurrió. Si nunca lo borras, los datos de prueba se acumulan y los siguientes jobs pueden recibir ruido.&lt;/p&gt;

&lt;p&gt;En la práctica, un registro pequeño basta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"run_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"orders-4821"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mailbox_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"orders-4821-a7f3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"attempt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-25T11:21:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"waiting_for_message"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El timestamp no tiene que ser perfecto, pero si debe estar disponible para comparar la espera con los eventos del pod.&lt;/p&gt;

&lt;h2&gt;
  
  
  Señales mínimas para un buzón de prueba
&lt;/h2&gt;

&lt;p&gt;No necesitas capturar el contenido completo del mensaje en cada log. Guarda señales pequeñas y útiles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;mailbox_created&lt;/code&gt;: la dirección quedó disponible.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;message_observed&lt;/code&gt;: llegó un mensaje con el identificador esperado.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;link_extracted&lt;/code&gt;: el test encontró la URL de verificación.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;assertion_passed&lt;/code&gt; o &lt;code&gt;assertion_failed&lt;/code&gt;: el resultado funcional.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cleanup_completed&lt;/code&gt;: el recurso fue retirado.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cada señal debe incluir &lt;code&gt;run_id&lt;/code&gt;, &lt;code&gt;mailbox_id&lt;/code&gt;, &lt;code&gt;attempt&lt;/code&gt;, nombre del namespace y una marca de tiempo. No incluyas el token completo del enlace ni el cuerpo entero del email; una huella o el identificador del mensaje suele ser suficiente para investigar.&lt;/p&gt;

&lt;p&gt;Para pruebas manuales o un entorno compartido, un &lt;a href="https://tempmailso.com" rel="noopener noreferrer"&gt;correo temporal&lt;/a&gt; puede aislar la verificación de un inbox personal. La regla importante es que la dirección se vincule a una sola ejecución y no se convierta en un depósito permanente.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cómo aislar los retries en CI
&lt;/h2&gt;

&lt;p&gt;En Kubernetes, el aislamiento empieza en los metadatos, no en una etiqueta bonita. Propaga el &lt;code&gt;run_id&lt;/code&gt; como label del Job y como campo estructurado en los logs. Usa un namespace efímero cuando el pipeline lo permita, o al menos una cuenta de servicio y un prefijo de recursos por ejecución.&lt;/p&gt;

&lt;p&gt;Una configuración conceptual podría verse así:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;test-run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;orders-4821&lt;/span&gt;
    &lt;span class="na"&gt;mail-contract&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;backoffLimit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;span class="na"&gt;activeDeadlineSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;180&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;backoffLimit&lt;/code&gt; limita cuántos intentos automáticos aparecen, mientras &lt;code&gt;activeDeadlineSeconds&lt;/code&gt; evita que una espera de correo se convierta en un job zombie. Estos límites no arreglan la entrega, pero hacen que el fallo tenga una forma conocida.&lt;/p&gt;

&lt;p&gt;El código del test también debe separar estados. “Aún no llegó” no es igual que “llegó un mensaje incorrecto”. El primer caso puede esperar con backoff; el segundo debería guardar la evidencia y fallar rapido. Un retraso de red y un contrato roto necesitan dueños distintos.&lt;/p&gt;

&lt;p&gt;En la interfaz que muestra el resultado, los mismos principios ayudan: &lt;a href="https://dev.to/silviutech/react-autocompletar-email-sin-ruido-2805"&gt;autocompletar un email sin ruido&lt;/a&gt; y &lt;a href="https://dev.to/silviutech/react-valida-emails-sin-castigar-el-foco-15eh"&gt;validar emails sin castigar el foco&lt;/a&gt; son detalles de frontend, pero reducen entradas ambiguas que luego complican la prueba de extremo a extremo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist de diagnóstico para el equipo
&lt;/h2&gt;

&lt;p&gt;Cuando falla un email en CI, revisa en este orden:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;¿El &lt;code&gt;run_id&lt;/code&gt; del pod coincide con el del buzón?&lt;/li&gt;
&lt;li&gt;¿El intento actual reutilizó la misma dirección de forma intencional?&lt;/li&gt;
&lt;li&gt;¿Existe una señal &lt;code&gt;mailbox_created&lt;/code&gt; antes de esperar?&lt;/li&gt;
&lt;li&gt;¿El proveedor entregó el mensaje, aunque el selector no lo encontrara?&lt;/li&gt;
&lt;li&gt;¿El token ya fue consumido por un intento anterior?&lt;/li&gt;
&lt;li&gt;¿El timeout cubre la latencia esperada o solo oculta una carrera?&lt;/li&gt;
&lt;li&gt;¿El cleanup guardó el identificador del mensaje antes de borrar?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Si el log dice &lt;code&gt;fake e mail com&lt;/code&gt; o &lt;code&gt;temp mailid&lt;/code&gt;, trátalo como una entrada de compatibilidad o una etiqueta heredada, no como una señal fiable por si sola. Esos términos pueden aparecer en documentación vieja y aveces llegan desde variables de prueba; no deben dirigir el enrutamiento del mensaje.&lt;/p&gt;

&lt;p&gt;También revisa que la política de acceso no dé a cada pod permiso para leer buzones ajenos. Un &lt;code&gt;Role&lt;/code&gt; estrecho por namespace es mas fácil de auditar que una credencial compartida entre todos los jobs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cierre: el correo como dependencia observable
&lt;/h2&gt;

&lt;p&gt;Un test de email estable no depende de que el primer intento sea perfecto. Depende de que cada retry conserve el contexto suficiente para responder tres preguntas: qué ejecución creó el buzón, qué mensaje se observó y qué limpieza se completó.&lt;/p&gt;

&lt;p&gt;Para escenarios de prueba de bajo riesgo, una integración con &lt;a href="https://tempmailso.com" rel="noopener noreferrer"&gt;tempmailso&lt;/a&gt; puede servir como frontera práctica, siempre que el equipo mantenga el propósito acotado y no envíe datos reales. El valor está en el contrato, no en acumular direcciones.&lt;/p&gt;

&lt;p&gt;Cuando esas señales están en Kubernetes, el incidente deja de ser “el email falló”. Pasa a ser una secuencia concreta: el buzón se creó, el mensaje tardó, el retry conservó el contexto o la política de acceso impidió leerlo. Esa diferencia ahorra una buena cantidad de depuración nocturna y hace que el pipeline sea mucho mas confiable.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>sre</category>
      <category>devops</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Kubernetes: un contrato de correo para CI</title>
      <dc:creator>Alex Carter</dc:creator>
      <pubDate>Sat, 24 Oct 2026 08:23:15 +0000</pubDate>
      <link>https://dev.to/alexcarteruk/kubernetes-un-contrato-de-correo-para-ci-4n19</link>
      <guid>https://dev.to/alexcarteruk/kubernetes-un-contrato-de-correo-para-ci-4n19</guid>
      <description>&lt;p&gt;Un pipeline puede tener todos sus pods en &lt;code&gt;Running&lt;/code&gt; y terminar en rojo porque nunca encontró el correo de verificación. La primera reacción suele ser reiniciar el worker o aumentar el timeout. A veces ayuda, pero mas frecuente solo oculta la causa.&lt;/p&gt;

&lt;p&gt;En equipos que mantienen pruebas de registro, recuperación de cuenta o invitaciones, el correo debe tener un contrato parecido al de una base de datos de prueba. Cada job necesita saber qué buzón puede usar, durante cuánto tiempo, qué mensaje espera y qué evidencia debe dejar si falla. Sin ese contrato, una prueba de email se convierte en una espera larga y poco explicable.&lt;/p&gt;

&lt;h2&gt;
  
  
  El problema no es solo el correo
&lt;/h2&gt;

&lt;p&gt;La entrega de un mensaje cruza varias capas: el test crea un usuario, la aplicación publica un evento, un worker procesa la tarea, el proveedor acepta el mensaje y el inbox lo expone a la prueba. Kubernetes puede estar sano mientras cualquiera de las otras capas está detenida.&lt;/p&gt;

&lt;p&gt;Los síntomas se parecen bastante:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;el mensaje no se creó porque falló el evento de signup;&lt;/li&gt;
&lt;li&gt;el worker lo procesó con credenciales del entorno equivocado;&lt;/li&gt;
&lt;li&gt;el correo llegó a otro buzón porque dos jobs compartieron dirección;&lt;/li&gt;
&lt;li&gt;el mensaje existe, pero la consulta empezó antes de la hora correcta;&lt;/li&gt;
&lt;li&gt;el inbox respondió con un error temporal y el test lo tomó como “no existe”.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Por eso conviene &lt;a href="https://dev.to/silviutech/react-microestados-que-calman-formularios-4kg0"&gt;hacer visibles los estados pequeños del inbox&lt;/a&gt;: “esperando”, “recibido”, “procesando” y “expirado” son más útiles que un único spinner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Define un contrato por job
&lt;/h2&gt;

&lt;p&gt;Antes de crear el recurso de Kubernetes, genera un &lt;code&gt;run_id&lt;/code&gt; único. Ese identificador debe viajar por el usuario sintético, el evento de email, los logs y la consulta al buzón. La dirección de prueba puede incorporar el ID o estar asociada a él en una tabla de leases.&lt;/p&gt;

&lt;p&gt;El contrato mínimo tiene cinco campos:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Propietario:&lt;/strong&gt; qué job y qué namespace pueden leer el buzón.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vigencia:&lt;/strong&gt; cuándo empieza y cuándo caduca la lease.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mensaje esperado:&lt;/strong&gt; destinatario lógico, tipo de evento y versión de plantilla.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reloj:&lt;/strong&gt; hora desde la que el test acepta mensajes, no solo el último mensaje visible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limpieza:&lt;/strong&gt; quién libera la dirección y qué ocurre si el pod muere.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Esto evita el fallo clasico de “el test pasó porque encontró un correo viejo”. También permite distinguir un problema de aplicación de un problema del proveedor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué debe guardar el job
&lt;/h2&gt;

&lt;p&gt;No hace falta copiar el contenido completo del email a los logs. Para investigar basta con guardar datos seguros y correlacionables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;run_id&lt;/code&gt; y nombre del job;&lt;/li&gt;
&lt;li&gt;namespace, commit y entorno;&lt;/li&gt;
&lt;li&gt;timestamp de solicitud y timestamp de primera consulta;&lt;/li&gt;
&lt;li&gt;identificador del mensaje, si el proveedor lo ofrece;&lt;/li&gt;
&lt;li&gt;estado observado: &lt;code&gt;waiting&lt;/code&gt;, &lt;code&gt;received&lt;/code&gt;, &lt;code&gt;consumed&lt;/code&gt; o &lt;code&gt;expired&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;contador y razón de cada reintento.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;El cuerpo del mensaje puede quedar fuera del log, sobre todo si contiene tokens. En su lugar, el test puede verificar el asunto, el host esperado del enlace y un hash o identificador de la plantilla. En una guardia, esa evidencia es mas accionable que cien líneas de HTML.&lt;/p&gt;

&lt;h2&gt;
  
  
  Investiga por capas
&lt;/h2&gt;

&lt;p&gt;Cuando el pipeline falla, sigue el camino del evento y no el orden de los recursos en &lt;code&gt;kubectl&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Primero confirma que la aplicación creó el evento para el &lt;code&gt;run_id&lt;/code&gt; correcto. Luego revisa si el worker recibió la tarea, si la reintentó y con qué configuración. Después comprueba la respuesta del proveedor. Solo al final consulta el inbox. Si el mensaje fue aceptado por el proveedor pero no aparece, el problema ya no está en el deployment que generó el evento.&lt;/p&gt;

&lt;p&gt;Una métrica sencilla ayuda mucho: registra por separado el tiempo de creación del evento, el tiempo hasta la aceptación del proveedor y el tiempo hasta la lectura por el test. Así se ve si el retraso está en la cola o en la consulta. En CI suele pasar rapido que todos miren el mismo timeout, aunque cada etapa tenga un presupuesto distinto.&lt;/p&gt;

&lt;p&gt;También revisa el drift de configuración: &lt;a href="https://dev.to/alexcarteruk/terraform-correos-de-drift-que-si-orientan-211j"&gt;detectar drift antes de culpar al pod&lt;/a&gt; evita perder una hora buscando un bug de aplicación cuando el secreto del namespace cambió.&lt;/p&gt;

&lt;h2&gt;
  
  
  Un patrón simple en Kubernetes
&lt;/h2&gt;

&lt;p&gt;El job puede recibir el contrato como variables inmutables y escribir su evidencia en un artefacto de CI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;EMAIL_RUN_ID&lt;/span&gt;
    &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;fieldRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;fieldPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;metadata.uid&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;EMAIL_LEASE_SECONDS&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;600"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;EMAIL_EXPECTED_ENV&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;staging"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El &lt;code&gt;metadata.uid&lt;/code&gt; identifica al pod, pero el sistema de pruebas debe crear además un &lt;code&gt;run_id&lt;/code&gt; estable para todo el job si hay varios pods. No uses solo el nombre del pod: los reintentos generan nombres distintos y complican el diagnóstico.&lt;/p&gt;

&lt;p&gt;La limpieza debe vivir en un &lt;code&gt;finally&lt;/code&gt; del test y también en un reconciler periódico. Si el pod desaparece antes de liberar el buzón, el reconciler recupera la lease cuando vence. El pod no tiene permisos suficiente para borrar recursos de otro namespace; limita el RBAC al recurso de leases y al namespace del job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cómo distinguir timeout de entrega y consumo
&lt;/h2&gt;

&lt;p&gt;Un timeout de entrega significa que no hay evidencia de aceptación o de recepción. Un timeout de consumo significa que el mensaje existe, pero el test no pudo leerlo o no reconoció su forma. Son incidentes diferentes.&lt;/p&gt;

&lt;p&gt;Una tabla de decisión corta:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Evidencia&lt;/th&gt;
&lt;th&gt;Hipótesis principal&lt;/th&gt;
&lt;th&gt;Siguiente comprobación&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No existe evento&lt;/td&gt;
&lt;td&gt;Fallo en la aplicación&lt;/td&gt;
&lt;td&gt;Logs del endpoint y del outbox&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Evento sin aceptación&lt;/td&gt;
&lt;td&gt;Worker, cola o credencial&lt;/td&gt;
&lt;td&gt;Reintentos y configuración&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aceptado sin mensaje&lt;/td&gt;
&lt;td&gt;Proveedor o dirección&lt;/td&gt;
&lt;td&gt;ID del mensaje y buzón&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mensaje visible sin match&lt;/td&gt;
&lt;td&gt;Selector del test&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;run_id&lt;/code&gt;, asunto y timestamp&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mensaje consumido por otro job&lt;/td&gt;
&lt;td&gt;Lease compartida&lt;/td&gt;
&lt;td&gt;Propietario y namespace&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Si aparece un término de búsqueda raro como temp org mail en documentación o tickets, trátalo como texto de entrada, no como una señal operativa. Lo que importa para la alerta es la identidad del job y la transición del mensaje.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist de guardia
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] ¿Cada job tiene una dirección o lease exclusiva?&lt;/li&gt;
&lt;li&gt;[ ] ¿El &lt;code&gt;run_id&lt;/code&gt; aparece en eventos, logs y consultas?&lt;/li&gt;
&lt;li&gt;[ ] ¿El test ignora mensajes anteriores al inicio del run?&lt;/li&gt;
&lt;li&gt;[ ] ¿Se separan tiempos de cola, entrega y lectura?&lt;/li&gt;
&lt;li&gt;[ ] ¿Los reintentos conservan la razón original?&lt;/li&gt;
&lt;li&gt;[ ] ¿La lease expira aunque muera el pod?&lt;/li&gt;
&lt;li&gt;[ ] ¿Los logs evitan tokens y cuerpos completos?&lt;/li&gt;
&lt;li&gt;[ ] ¿Existe un artefacto pequeño con la evidencia final?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Preguntas rápidas
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ¿Debo aumentar siempre el timeout?
&lt;/h3&gt;

&lt;p&gt;No. Aumentarlo puede esconder una cola atascada o una dirección compartida. Primero mide cada tramo y ajusta el presupuesto solo donde haya variación legítima.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Necesito un buzón por pod?
&lt;/h3&gt;

&lt;p&gt;No necesariamente. Necesitas un propietario inequívoco por job. Un buzón puede servir a varios pods coordinados si la lease y el selector de mensajes lo permiten; para pruebas paralelas independientes, un buzón por ejecución suele ser mas simple.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Qué hace que el contrato sea útil para SRE?
&lt;/h3&gt;

&lt;p&gt;Que permita responder tres preguntas sin repetir el pipeline: qué esperaba el job, qué ocurrió realmente y quién tenía la propiedad de la dirección. Si esas respuestas quedan en un artefacto seguro, el siguiente incidente se resuelve con evidencia, tambien cuando el pod ya no existe.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>sre</category>
      <category>devops</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Kubernetes: un runbook para fallos de email</title>
      <dc:creator>Alex Carter</dc:creator>
      <pubDate>Sat, 24 Oct 2026 02:22:51 +0000</pubDate>
      <link>https://dev.to/alexcarteruk/kubernetes-un-runbook-para-fallos-de-email-52al</link>
      <guid>https://dev.to/alexcarteruk/kubernetes-un-runbook-para-fallos-de-email-52al</guid>
      <description>&lt;p&gt;Un pipeline puede mostrar un pod saludable y, aun así, fallar cuando necesita leer un email de verificación. Este caso confunde porque mezcla varias dependencias: Kubernetes, la cola del mensaje, el proveedor de correo y el buzón que usa el test.&lt;/p&gt;

&lt;p&gt;En una guardia, la pregunta “¿llegó el correo?” no ayuda mucho. Es mejor preguntar en qué capa se perdió la señal y qué evidencia tenemos. Este runbook propone una forma corta de investigar el fallo sin reiniciar todo el cluster a ciegas.&lt;/p&gt;

&lt;h2&gt;
  
  
  El síntoma: el pod está verde y el pipeline rojo
&lt;/h2&gt;

&lt;p&gt;El test crea una cuenta, espera un mensaje y termina con timeout. Mientras tanto, &lt;code&gt;kubectl get pods&lt;/code&gt; muestra todos los pods en &lt;code&gt;Running&lt;/code&gt;. Eso solo confirma que el proceso sigue vivo; no confirma que el worker haya consumido el evento ni que el mensaje haya llegado al buzón correcto.&lt;/p&gt;

&lt;p&gt;Los síntomas más comunes son:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;el evento de signup se creó, pero no entró en la cola;&lt;/li&gt;
&lt;li&gt;el worker reintentó con una configuración antigua;&lt;/li&gt;
&lt;li&gt;el mensaje llegó a otra dirección o fue filtrado;&lt;/li&gt;
&lt;li&gt;el test empezó a buscar antes de guardar su hora de inicio;&lt;/li&gt;
&lt;li&gt;el buzón de prueba fue compartido por dos jobs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La diferencia parece pequeña, pero cambia por completo la acción siguiente. Reiniciar el deployment no arregla un selector de buzón incorrecto.&lt;/p&gt;

&lt;h2&gt;
  
  
  Primero separa las capas del fallo
&lt;/h2&gt;

&lt;p&gt;Me gusta dividir la investigación en cuatro comprobaciones, en este orden:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Aplicación:&lt;/strong&gt; ¿el endpoint aceptó el signup y devolvió un identificador de evento?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cola y worker:&lt;/strong&gt; ¿el evento fue procesado, cuántos intentos tuvo y qué error devolvió el proveedor?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Buzón:&lt;/strong&gt; ¿el mensaje aparece en el buzón reservado para este job, después de la hora de disparo?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test:&lt;/strong&gt; ¿el parser encontró el enlace correcto y respetó el timeout esperado?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Esto evita que SRE trate todos los fallos como un problema de infraestructura. Si el evento nunca salió de la aplicación, escalar el tamaño del pod solo añade ruido.&lt;/p&gt;

&lt;p&gt;Para tests que usan un &lt;strong&gt;correo temporal gratis&lt;/strong&gt;, también conviene registrar el identificador del buzón, el job que lo reservó y el instante exacto de creación. No hace falta guardar el contenido completo del mensaje en los logs del cluster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Un runbook pequeño para investigar
&lt;/h2&gt;

&lt;p&gt;El primer paso es guardar un contexto de ejecución. Un nombre de job, un &lt;code&gt;run_id&lt;/code&gt; y un &lt;code&gt;correlation_id&lt;/code&gt; deben viajar desde el test hasta el evento de correo. En los logs de la aplicación, buscaría algo parecido a esto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;run_id=ci-4821 correlation_id=signup-91 event=email.requested status=queued
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Después revisaría, de forma acotada, los eventos del worker para ese identificador. La pregunta no es solo si hubo un error, sino si el error es transitorio, permanente o causado por datos del test.&lt;/p&gt;

&lt;p&gt;El tercer paso es comparar relojes. El test debe guardar &lt;code&gt;triggered_at&lt;/code&gt; antes de solicitar el email y buscar mensajes recibidos después de ese momento. Si usa “el último mensaje” sin una frontera temporal, puede aceptar un correo viejo y producir un resultado intermitente.&lt;/p&gt;

&lt;p&gt;También reviso el aislamiento. Dos pods de CI no deberían consumir el mismo buzón de prueba. Una reserva simple con TTL, el nombre del job y una liberación al terminar suele ser suficiente para empezar. Si la reserva caduca mientras el test sigue esperando, el fallo debe decirlo claramente; de lo contrario parece un problema aleatorio de red.&lt;/p&gt;

&lt;p&gt;En el frontend, vale la pena &lt;a href="https://dev.to/silviutech/fastapi-separa-emails-de-signup-por-escenario-4ncb"&gt;separar emails de signup por escenario&lt;/a&gt; para que una prueba de recuperación no termine leyendo el correo de un alta. Y la interfaz debería mostrar &lt;a href="https://dev.to/silviutech/react-estados-de-carga-accesibles-para-email-1nmk"&gt;estados de carga claros en flujos de email&lt;/a&gt;, incluso cuando el backend todavía está esperando.&lt;/p&gt;

&lt;h2&gt;
  
  
  Diseña los tiempos y reintentos
&lt;/h2&gt;

&lt;p&gt;Un timeout no es una estrategia de recuperación. Define tres tiempos por separado:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;poll interval:&lt;/strong&gt; cada cuánto consulta el test;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;provider timeout:&lt;/strong&gt; cuánto puede tardar la aceptación del proveedor;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;test deadline:&lt;/strong&gt; cuánto espera el pipeline antes de fallar.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Por ejemplo, un polling de cinco segundos durante un minuto deja doce oportunidades de lectura. Ese número no debe copiarse sin pensar: debe ser mayor que la latencia normal del sistema y menor que el tiempo que el job puede ocupar recursos sin aportar información.&lt;/p&gt;

&lt;p&gt;Los reintentos también necesitan una frontera. Reintentar la consulta del buzón tiene sentido; crear otra cuenta y enviar otro email puede ocultar el fallo original. Cada intento debe conservar su &lt;code&gt;run_id&lt;/code&gt;, su hora y la razón por la que el mensaje anterior fue rechazado.&lt;/p&gt;

&lt;p&gt;Durante una revisión, alguien puede escribir búsquedas como &lt;code&gt;tamp mail com&lt;/code&gt; o &lt;code&gt;temp mailid&lt;/code&gt; al buscar servicios de prueba. Esos términos sirven como ruido de diagnóstico, no como valores válidos de configuración. Hay que mantenerlos fuera de nombres de variables, etiquetas y reglas de alertas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist de cierre para SRE
&lt;/h2&gt;

&lt;p&gt;Antes de cerrar el incidente, comprueba:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] el evento de signup tiene un identificador rastreable;&lt;/li&gt;
&lt;li&gt;[ ] la cola registra el resultado final y el número de intentos;&lt;/li&gt;
&lt;li&gt;[ ] el buzón pertenece a un solo job y tiene TTL;&lt;/li&gt;
&lt;li&gt;[ ] el test usa una frontera temporal, no solo el mensaje más reciente;&lt;/li&gt;
&lt;li&gt;[ ] los logs omiten contenido sensible del email;&lt;/li&gt;
&lt;li&gt;[ ] el alerta enlaza al &lt;code&gt;run_id&lt;/code&gt; y a la causa observada;&lt;/li&gt;
&lt;li&gt;[ ] el runbook dice qué hacer si falla cada capa.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Un buen cierre no es “repetimos el pipeline y pasó”. Es “la primera ejecución falló porque el worker usó una credencial antigua; la segunda pasó después de corregir la configuración”. Esa frase permite aprender y evita que el siguiente turno empiece desde cero.&lt;/p&gt;

&lt;h2&gt;
  
  
  Q&amp;amp;A rápido
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ¿Debo aumentar el timeout cuando falla un test de email?
&lt;/h3&gt;

&lt;p&gt;Solo después de comprobar la latencia real y la frontera temporal. Un timeout mayor puede esconder un mensaje que nunca se envió o un buzón equivocado.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Es mejor reiniciar el worker?
&lt;/h3&gt;

&lt;p&gt;Si hay un error transitorio y el runbook lo contempla, quizá. Primero captura los logs y el estado de la cola; reiniciar antes puede borrar la mejor evidencia.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Qué métrica seguiría primero?
&lt;/h3&gt;

&lt;p&gt;La proporción de eventos que pasan de &lt;code&gt;requested&lt;/code&gt; a &lt;code&gt;delivered&lt;/code&gt; dentro del deadline del test. Separaría esa métrica por entorno y causa de fallo, porque un promedio global suele tapar los jobs más lentos.&lt;/p&gt;

&lt;p&gt;La idea central es sencilla: Kubernetes mantiene procesos, pero no garantiza por sí solo el contrato completo de un email. Cuando cada capa deja una señal pequeña y relacionada con el mismo &lt;code&gt;run_id&lt;/code&gt;, el fallo deja de ser una adivinanza y se convierte en una investigación repetible.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>sre</category>
      <category>devops</category>
      <category>testing</category>
    </item>
    <item>
      <title>Kubernetes: presupuestos de tiempo para correo en CI</title>
      <dc:creator>Alex Carter</dc:creator>
      <pubDate>Thu, 22 Oct 2026 20:22:47 +0000</pubDate>
      <link>https://dev.to/alexcarteruk/kubernetes-presupuestos-de-tiempo-para-correo-en-ci-4im7</link>
      <guid>https://dev.to/alexcarteruk/kubernetes-presupuestos-de-tiempo-para-correo-en-ci-4im7</guid>
      <description>&lt;p&gt;Un pipeline de CI puede tardar varios minutos y aun así terminar con un mensaje poco útil: “no llegó el correo”. En una prueba que usa un &lt;code&gt;disposable email address&lt;/code&gt;, ese fallo no dice si la aplicación no publicó el evento, si el worker quedó pendiente, si Kubernetes reinició el pod o si el inbox todavía no había recibido el mensaje.&lt;/p&gt;

&lt;p&gt;La solución práctica es asignar un presupuesto de tiempo a cada frontera. No se trata de hacer el test más rápido a la fuerza. Se trata de saber qué parte consumió el tiempo disponible para que el siguiente incidente sea menos misterioso.&lt;/p&gt;

&lt;h2&gt;
  
  
  El síntoma: un timeout que no explica nada
&lt;/h2&gt;

&lt;p&gt;Imagina una prueba de registro en un namespace de staging. El formulario responde 201, pero el test espera el mensaje de confirmación durante 60 segundos y falla. El equipo revisa el log del navegador, ve un “email not found” y vuelve a lanzar el job. A veces pasa; otras no.&lt;/p&gt;

&lt;p&gt;Ese retry es tentador, pero mezcla varios relojes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;la publicación del evento por la API;&lt;/li&gt;
&lt;li&gt;la recogida del evento por el worker;&lt;/li&gt;
&lt;li&gt;el envío al proveedor de correo;&lt;/li&gt;
&lt;li&gt;la aparición del mensaje en el buzón de prueba;&lt;/li&gt;
&lt;li&gt;la lectura del mensaje y la confirmación del enlace.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cuando todos viven bajo un único timeout, la prueba pierde su capacidad de diagnóstico. Un SRE necesita al menos una señal por frontera, aunque sea una señal sencilla.&lt;/p&gt;

&lt;h2&gt;
  
  
  Define un presupuesto por etapa
&lt;/h2&gt;

&lt;p&gt;Empieza con una tabla que pueda leer cualquier persona del equipo. Por ejemplo, para un flujo normal de 45 segundos:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Etapa&lt;/th&gt;
&lt;th&gt;Presupuesto&lt;/th&gt;
&lt;th&gt;Evidencia mínima&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;API publica el evento&lt;/td&gt;
&lt;td&gt;2 s&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;request_id&lt;/code&gt; y estado HTTP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Worker recoge el evento&lt;/td&gt;
&lt;td&gt;8 s&lt;/td&gt;
&lt;td&gt;nombre del pod y contador&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Proveedor acepta el envío&lt;/td&gt;
&lt;td&gt;10 s&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;message_id&lt;/code&gt; o error&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mensaje aparece en el inbox&lt;/td&gt;
&lt;td&gt;20 s&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;mailbox_id&lt;/code&gt; y &lt;code&gt;received_at&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test lee y verifica&lt;/td&gt;
&lt;td&gt;5 s&lt;/td&gt;
&lt;td&gt;estado final y código de salida&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Los números no son universales. Son un contrato inicial que luego se ajusta con datos reales. Si la mayoría de las entregas ocurre en cinco segundos, un presupuesto de veinte quizá sea razonable para CI; si la cola tarda más por la noche, hay que medirlo y no adivinarlo.&lt;/p&gt;

&lt;p&gt;La prueba debe conservar los tiempos de cada etapa. No guardes el contenido completo del mensaje en un artefacto público: normalmente basta con un identificador, un hash de dirección y los estados. Así el diagnóstico ayuda sin filtrar datos de prueba.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué observar en Kubernetes
&lt;/h2&gt;

&lt;p&gt;En Kubernetes, un fallo de correo puede estar oculto detrás de una ejecución aparentemente sana. Revisa cuatro señales antes de aumentar el timeout:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Reinicios del worker.&lt;/strong&gt; Un &lt;code&gt;restartCount&lt;/code&gt; nuevo cerca del momento del envío puede explicar una cola que no avanzó.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edad del pod.&lt;/strong&gt; Si el despliegue rotó durante la prueba, el evento quizá quedó en memoria y no llegó a ser confirmado.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Profundidad de la cola.&lt;/strong&gt; Una cola creciendo con consumidores listos indica presión; una cola quieta con cero consumidores indica otro problema.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Correlación.&lt;/strong&gt; El mismo &lt;code&gt;request_id&lt;/code&gt; debe aparecer en la API, el worker y el adaptador de correo.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Para una consulta rápida, un job de diagnóstico puede producir algo como esto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;run=ci-1842 request=req-7f2 worker=mail-worker-6d8c
api=accepted worker=processed provider=accepted inbox=pending
pod_restarts=0 queue_depth=3 elapsed=18s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ese estado “inbox=pending” es mucho más útil que un timeout genérico. Si la cola sigue creciendo, el test no debe culpar al buzón todavía.&lt;/p&gt;

&lt;h2&gt;
  
  
  Una implementación sencilla en CI
&lt;/h2&gt;

&lt;p&gt;Usa un reloj monotónico y registra una línea al entrar y salir de cada etapa. El adaptador del buzón debería aceptar un deadline absoluto, no reiniciar el contador completo en cada poll. De lo contrario, diez intentos de seis segundos pueden convertir un presupuesto de veinte segundos en un minuto sin que sea evidente.&lt;/p&gt;

&lt;p&gt;También conviene reservar una dirección por ejecución. Un buzón compartido hace difícil saber si el mensaje actual llegó o si el test leyó un correo viejo. En documentación y búsquedas internas pueden aparecer errores como &lt;code&gt;tempail&lt;/code&gt; o &lt;code&gt;tamp mail com&lt;/code&gt;; no los uses para diseñar la lógica, pero sí puedes mapearlos a la terminología correcta en el buscador del equipo.&lt;/p&gt;

&lt;p&gt;Cuando el mensaje no llega, conserva el resumen de estados y termina con una causa concreta: &lt;code&gt;worker-timeout&lt;/code&gt;, &lt;code&gt;provider-rejected&lt;/code&gt;, &lt;code&gt;inbox-timeout&lt;/code&gt; o &lt;code&gt;verification-timeout&lt;/code&gt;. Así un retry puede ser selectivo y no repetir toda la suite sin motivo.&lt;/p&gt;

&lt;p&gt;Para sistemas con varios consumidores, merece la pena formalizar el contrato de señales. El artículo sobre &lt;a href="https://dev.to/silviutech/contratos-de-email-para-agentes-llm-4dh6"&gt;contratos de correo para automatizaciones&lt;/a&gt; muestra una forma de pensar en eventos y estados sin depender de un log enorme. Y para las alertas operativas, las &lt;a href="https://dev.to/alexcarteruk/terraform-cloud-alertas-de-failover-comprobables-3ie"&gt;alertas de failover comprobables&lt;/a&gt; siguen la misma idea: una alerta debe poder probarse con una evidencia observable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist para el siguiente incidente
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;¿Cada etapa tiene un presupuesto separado?&lt;/li&gt;
&lt;li&gt;¿La prueba conserva &lt;code&gt;request_id&lt;/code&gt;, &lt;code&gt;mailbox_id&lt;/code&gt; y &lt;code&gt;message_id&lt;/code&gt; sin guardar datos innecesarios?&lt;/li&gt;
&lt;li&gt;¿El worker tuvo reinicios o perdió el lease del evento?&lt;/li&gt;
&lt;li&gt;¿La cola avanzaba, estaba bloqueada o no tenía consumidores?&lt;/li&gt;
&lt;li&gt;¿El deadline era absoluto o se reiniciaba en cada intento?&lt;/li&gt;
&lt;li&gt;¿El resultado final distingue entrega, lectura y verificación?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Un timeout sigue siendo una limitación real, pero deja de ser una explicación vacía. Con presupuestos pequeños, señales correlacionadas y un buzón aislado por ejecución, Kubernetes y CI pueden decirte dónde está el problema antes de que alguien pulse “reintentar”.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>sre</category>
      <category>devops</category>
      <category>testing</category>
    </item>
    <item>
      <title>Kubernetes: un contrato de señales para email en CI</title>
      <dc:creator>Alex Carter</dc:creator>
      <pubDate>Tue, 20 Oct 2026 20:23:01 +0000</pubDate>
      <link>https://dev.to/alexcarteruk/kubernetes-un-contrato-de-senales-para-email-en-ci-8c4</link>
      <guid>https://dev.to/alexcarteruk/kubernetes-un-contrato-de-senales-para-email-en-ci-8c4</guid>
      <description>&lt;p&gt;Un pipeline de CI que verifica un email no falla solamente cuando el mensaje no llega. También falla cuando llega tarde, cuando el worker mira el inbox equivocado o cuando dos pods creen tener el mismo buzón. El primer síntoma son pruebas intermitentes: un commit verde por la mañana y rojo por la tarde, sin un cambio claro en el código.&lt;/p&gt;

&lt;p&gt;En revisiones de operaciones, me resulta más útil tratar el email como una señal distribuida. El test publica una intención, el proveedor procesa un mensaje y el inbox devuelve evidencia. Kubernetes ejecuta los actores, pero no convierte automáticamente esa cadena en observable. Necesitamos un contrato pequeño para saber qué pasó, dónde y cuándo.&lt;/p&gt;

&lt;h2&gt;
  
  
  La señal perdida entre el pod y el inbox
&lt;/h2&gt;

&lt;p&gt;Un flujo típico crea un usuario, solicita verificación, espera un mensaje y sigue el enlace. En un cluster, cada paso puede vivir en pods distintos. Si el test guarda solo una dirección de buzón y un &lt;code&gt;sleep(10)&lt;/code&gt;, no sabremos si la espera terminó porque llegó el mensaje o porque se agotó el tiempo.&lt;/p&gt;

&lt;p&gt;El problema crece con un &lt;code&gt;dummy e mail&lt;/code&gt; de pruebas compartido por varios jobs. Un mensaje antiguo puede satisfacer la aserción equivocada. Otra ejecución puede limpiar el inbox mientras el primer pod todavía está leyendo. Y si el correo no llega a tiempo, el pipeline queda en espera sin saber porqué.&lt;/p&gt;

&lt;p&gt;Por eso cada ejecución necesita un identificador de correlación, una fecha de expiración y un resultado explícito. El nombre del pod sirve para buscar logs, pero no es una identidad de negocio estable: Kubernetes puede recrearlo durante el mismo test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué debe contener el contrato de señales
&lt;/h2&gt;

&lt;p&gt;Un contrato sencillo puede acompañar cada solicitud de email con estos campos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;run_id&lt;/code&gt;: identifica la ejecución completa del pipeline.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mailbox_lease_id&lt;/code&gt;: identifica la reserva temporal del buzón.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;message_id&lt;/code&gt;: identifica el mensaje esperado, si el proveedor lo expone.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;requested_at&lt;/code&gt; y &lt;code&gt;expires_at&lt;/code&gt;: delimitan la ventana válida.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;state&lt;/code&gt;: &lt;code&gt;requested&lt;/code&gt;, &lt;code&gt;observed&lt;/code&gt;, &lt;code&gt;expired&lt;/code&gt; o &lt;code&gt;failed&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;attempt&lt;/code&gt;: cuenta las lecturas, no solo los envíos.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No hace falta copiar el token de verificación ni la dirección completa en cada log. Una huella corta del destinatario y el &lt;code&gt;run_id&lt;/code&gt; suelen bastar para unir eventos. Así podemos investigar sin convertir los logs en otra bandeja de entrada.&lt;/p&gt;

&lt;p&gt;Este enfoque se parece a diseñar &lt;a href="https://dev.to/silviutech/react-validacion-de-email-sin-saltos-de-layout-9b0"&gt;validación de email sin saltos de layout&lt;/a&gt;: el estado visible debe explicar la transición real, no ocultar el problema detrás de una espera genérica. Para un pipeline, la interfaz es el resultado del job y sus evidencias.&lt;/p&gt;

&lt;h2&gt;
  
  
  Diseñar el lease del buzón
&lt;/h2&gt;

&lt;p&gt;Un buzón de prueba debe reservarse antes de crear el usuario. La reserva tiene propietario, duración y una operación de liberación idempotente. Si el job muere, el lease expira solo; no dependemos de que un &lt;code&gt;finally&lt;/code&gt; sea ejecutado en un pod que ya desapareció.&lt;/p&gt;

&lt;p&gt;El orden recomendado es:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Crear &lt;code&gt;run_id&lt;/code&gt; y solicitar un &lt;code&gt;mailbox_lease_id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Guardar ambos valores como variables del job, no como configuración global del cluster.&lt;/li&gt;
&lt;li&gt;Crear el usuario y enviar el email con un encabezado de correlación cuando sea posible.&lt;/li&gt;
&lt;li&gt;Consultar el inbox filtrando por lease, destinatario y ventana temporal.&lt;/li&gt;
&lt;li&gt;Confirmar el mensaje observado y liberar el lease.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;En esta etapa puede usarse un &lt;a href="https://dev.to/silviutech/fastapi-un-buzon-de-pruebas-que-deja-pistas-4mcj"&gt;buzón de pruebas que deja pistas&lt;/a&gt; para separar la captura del mensaje de la aserción final. La idea no es hacer polling mas agresivo, sino leer con una condición que explique por qué el mensaje pertenece a esta ejecución.&lt;/p&gt;

&lt;p&gt;Si el flujo necesita un servicio externo, una opción es usar un &lt;a href="https://tempmailso.com" rel="noopener noreferrer"&gt;correo temporal desechable&lt;/a&gt; con un alcance limitado al test. No debe reutilizarse para datos reales ni convertirse en un destino permanente. El lease debe guardar solo la referencia necesaria y borrar la evidencia sensible al cerrar la ejecución.&lt;/p&gt;

&lt;h2&gt;
  
  
  Correlacionar eventos sin filtrar datos
&lt;/h2&gt;

&lt;p&gt;El job puede emitir eventos con una forma estable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"run_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ci-8421"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mailbox_lease_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lease-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"event"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"message_observed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"observed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"age_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1840&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message_fingerprint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"b7c2..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La huella no necesita ser reversible. Evita registrar asunto completo, cuerpo o enlaces con tokens. En un incidente, &lt;code&gt;message_observed&lt;/code&gt; junto con &lt;code&gt;age_ms&lt;/code&gt; responde una pregunta concreta: ¿el proveedor tardó, el lector consultó mal o la aserción llegó después de que expiró el lease?&lt;/p&gt;

&lt;p&gt;Conviene que los eventos de Kubernetes y los del servicio de email compartan &lt;code&gt;run_id&lt;/code&gt;. El primer intento de instrumentación suele mezclar nombres de namespaces, y luego nadie sabe cual es el correcto. Una tabla o consulta guardada con las transiciones ayuda más que veinte filtros improvisados.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reintentos y estados terminales
&lt;/h2&gt;

&lt;p&gt;El lector debe reintentar con un límite y registrar el motivo de cada intento. Un &lt;code&gt;404&lt;/code&gt; de mensaje todavía no es un fallo si la ventana sigue abierta; un lease expirado sí es un estado terminal. Una reintento después de &lt;code&gt;expired&lt;/code&gt; solo aumenta ruido y puede observar un correo de otra ejecución.&lt;/p&gt;

&lt;p&gt;Una regla práctica es separar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Transitorio:&lt;/strong&gt; inbox aún vacío, respuesta temporal del proveedor o pod reiniciado.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terminal:&lt;/strong&gt; lease vencido, formato inválido o mensaje asociado a otro &lt;code&gt;run_id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accionable:&lt;/strong&gt; proveedor lento, duplicado detectado o pérdida de correlación.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Si un worker se reinicia, debe recuperar el estado desde el contrato persistido. No debe empezar desde cero solo porque su memoria se perdió. En SRE, esta diferencia reduce los falsos positivos y hace que una reejecución sea una decisión, no un reflejo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist de operación
&lt;/h2&gt;

&lt;p&gt;Antes de confiar en el flujo, compruebo lo siguiente:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;¿Cada job obtiene un lease único y con expiración?&lt;/li&gt;
&lt;li&gt;¿El &lt;code&gt;run_id&lt;/code&gt; aparece en los eventos del test, del proveedor y del lector?&lt;/li&gt;
&lt;li&gt;¿La consulta descarta mensajes fuera de la ventana temporal?&lt;/li&gt;
&lt;li&gt;¿Un pod nuevo puede continuar desde el estado guardado?&lt;/li&gt;
&lt;li&gt;¿Los logs evitan tokens, cuerpos y direcciones completas?&lt;/li&gt;
&lt;li&gt;¿Los reintentos tienen límite y diferencian lo transitorio de lo terminal?&lt;/li&gt;
&lt;li&gt;¿El cleanup funciona aunque el job termine por timeout?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;También probaría una caída del lector, un mensaje duplicado y un retraso del proveedor. El caso feliz demuestra que el email funciona; esos tres casos muestran si el contrato realmente protege al equipo.&lt;/p&gt;

&lt;p&gt;Un buzón temporal es solo una pieza de la prueba. La confiabilidad aparece cuando cada mensaje tiene contexto, cada lease tiene límites y cada espera termina con una razón legible. Con ese contrato, Kubernetes deja de ser el lugar donde “a veces falla CI” y se vuelve una fuente útil de señales para diagnosticar el flujo.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>sre</category>
      <category>devops</category>
      <category>testing</category>
    </item>
    <item>
      <title>Kubernetes: evidencia para emails de prueba en CI</title>
      <dc:creator>Alex Carter</dc:creator>
      <pubDate>Sat, 17 Oct 2026 17:23:09 +0000</pubDate>
      <link>https://dev.to/alexcarteruk/kubernetes-evidencia-para-emails-de-prueba-en-ci-53li</link>
      <guid>https://dev.to/alexcarteruk/kubernetes-evidencia-para-emails-de-prueba-en-ci-53li</guid>
      <description>&lt;p&gt;Un pipeline puede terminar en verde y aun así dejar una pregunta incómoda: ¿se envió realmente el email de prueba, o solo terminó el job que lo solicitó? En equipos que ejecutan muchos tests en Kubernetes, esa diferencia convierte un fallo sencillo en una investigación larga.&lt;/p&gt;

&lt;p&gt;La solución no es guardar más logs sin orden. Es tratar cada email de prueba como una evidencia con identidad, tiempo de vida y estado observable. El pod que inicia la prueba no debe ser dueño permanente del buzón ni de la entrega. Debe solicitar una ejecución, obtener un lease y publicar señales que otro componente pueda revisar.&lt;/p&gt;

&lt;h2&gt;
  
  
  El síntoma: CI verde, evidencia perdida
&lt;/h2&gt;

&lt;p&gt;Un flujo típico crea un usuario, pide un enlace de verificación y espera un mensaje. Cuando el test corre en paralelo, aparecen síntomas conocidos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dos pods leen el mismo mensaje y uno consume la evidencia del otro.&lt;/li&gt;
&lt;li&gt;Un job termina antes de que el buzón reciba el email.&lt;/li&gt;
&lt;li&gt;Un reintento crea una segunda dirección y hace imposible saber cual era la válida.&lt;/li&gt;
&lt;li&gt;El teardown elimina el recurso antes de conservar el asunto, el identificador y la hora de recepción.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La búsqueda &lt;code&gt;temp mail mail&lt;/code&gt; puede llevar a soluciones rápidas, pero en CI importa más la frontera de responsabilidad. Una &lt;a href="https://tempmailso.com" rel="noopener noreferrer"&gt;cuenta de correo desechable&lt;/a&gt; puede servir para una comprobación controlada; no sustituye un contrato de ejecución, una política de datos ni un registro de eventos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separar la ejecución del buzón
&lt;/h2&gt;

&lt;p&gt;Primero separo tres objetos que a menudo se mezclan:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Solicitud de prueba:&lt;/strong&gt; contiene el test, el commit, el namespace y el destinatario lógico.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lease de ejecución:&lt;/strong&gt; concede durante un periodo corto el derecho a consumir una evidencia.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mensaje observado:&lt;/strong&gt; guarda un identificador mínimo, el asunto normalizado, la hora y el resultado de las aserciones.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;El destinatario puede ser una dirección temporal, pero la ejecución debe tener un &lt;code&gt;run_id&lt;/code&gt; propio. Así, si el proveedor tarda o el pod cae, un worker de recuperación puede retomar la misma intención. El estado del onboarding también merece un contrato explícito; este &lt;a href="https://dev.to/hannahdev56/saas-un-contrato-de-estados-para-el-onboarding-4p07"&gt;contrato de estados del onboarding&lt;/a&gt; es una referencia útil para pensar en transiciones y no solo en respuestas HTTP.&lt;/p&gt;

&lt;p&gt;El &lt;code&gt;run_id&lt;/code&gt; debe viajar en los logs estructurados, en las métricas y en los artefactos de CI. No conviene poner tokens completos en esos lugares. Para una investigación basta con un hash corto del destinatario, el commit y un identificador de ejecución.&lt;/p&gt;

&lt;h2&gt;
  
  
  Diseñar un lease para cada prueba
&lt;/h2&gt;

&lt;p&gt;Un lease debe tener dueño, expiración y versión. En una tabla o almacén equivalente, el registro puede seguir este ciclo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;requested -&amp;gt; claimed -&amp;gt; waiting_for_message -&amp;gt; asserted -&amp;gt; released
                         \-&amp;gt; timed_out
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El worker reclama una ejecución con una operación atómica. El filtro debe comprobar que no existe un lease vigente y que el intento anterior no superó su límite. Si el pod desaparece, la expiración vuelve a hacer elegible el registro. Esto es mas seguro que confiar en un &lt;code&gt;finally&lt;/code&gt; del test, porque Kubernetes puede terminar un contenedor sin darle tiempo suficiente.&lt;/p&gt;

&lt;p&gt;Una consulta conceptual tendría estas condiciones:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;email_test_runs&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;lease_owner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;lease_until&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'90 seconds'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'claimed'&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;run_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;run_id&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lease_until&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;lease_until&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;max_attempts&lt;/span&gt;
&lt;span class="n"&gt;RETURNING&lt;/span&gt; &lt;span class="n"&gt;run_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lease_until&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En PostgreSQL real, la actualización debe estar protegida por una transacción y una condición que impida que un worker viejo complete el registro después de perder su lease. Si se usan colas, &lt;code&gt;SKIP LOCKED&lt;/code&gt; ayuda a repartir trabajo sin que los workers esperen por filas bloqueadas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Métricas y límites para SRE
&lt;/h2&gt;

&lt;p&gt;La señal principal no es solo el porcentaje de tests verdes. Para una operación SRE conviene observar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tiempo entre &lt;code&gt;requested&lt;/code&gt; y &lt;code&gt;message_received&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;porcentaje de ejecuciones que expiran el lease;&lt;/li&gt;
&lt;li&gt;reintentos por proveedor, namespace y versión del test;&lt;/li&gt;
&lt;li&gt;mensajes encontrados pero no coincidentes con el &lt;code&gt;run_id&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;edad de la cola y cantidad de ejecuciones en &lt;code&gt;timed_out&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Un presupuesto de reintentos evita que una API lenta congele todo el pipeline. Después de dos o tres intentos, es mejor mover la ejecución a una cola de mensajes fallidos y adjuntar el artefacto de diagnóstico. Un circuit breaker también evita que cientos de pods golpeen al mismo proveedor cuando ya hay señales de indisponibilidad.&lt;/p&gt;

&lt;p&gt;La configuración debe incluir límites claros: TTL del buzón, tiempo máximo de espera, tamaño de lote y política de limpieza. Conviene probar la configuracion con un caso de terminación abrupta del pod, no solo con un test feliz. Una métrica sin un umbral operativo es una señal bonita, pero no una alerta util.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preguntas frecuentes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ¿Un lease garantiza que nunca se duplique un email?
&lt;/h3&gt;

&lt;p&gt;No. Garantiza una propiedad de concurrencia durante su vigencia. Si el proveedor acepta el mensaje y el worker muere antes de registrar la respuesta, el reintento todavía puede duplicarlo. Para reducir el impacto, usa una clave idempotente cuando el proveedor la soporte y haz que el test acepte un conjunto limitado de mensajes equivalentes.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Debo crear un buzón por pod?
&lt;/h3&gt;

&lt;p&gt;No necesariamente. Un buzón por ejecución suele ser más fácil de aislar, pero aumenta la limpieza y el coste operativo. La decisión depende del volumen, la retención y la posibilidad de filtrar por destinatario, asunto y &lt;code&gt;run_id&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Qué debe conservar CI?
&lt;/h3&gt;

&lt;p&gt;Conserva el &lt;code&gt;run_id&lt;/code&gt;, el resultado de cada aserción, el timestamp de recepción y un resumen del mensaje. Evita guardar el cuerpo completo si contiene datos personales. Para búsquedas como &lt;code&gt;fake e mail com&lt;/code&gt;, una nota en el contexto de la prueba puede ayudar a rastrear intención, pero no debe convertirse en un dato de producción.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist de implementación
&lt;/h2&gt;

&lt;p&gt;Antes de desplegar este patrón, revisa:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cada ejecución tiene un &lt;code&gt;run_id&lt;/code&gt; único y una política de expiración.&lt;/li&gt;
&lt;li&gt;La reclamación es atómica y el worker verifica su lease al completar.&lt;/li&gt;
&lt;li&gt;Los reintentos tienen límite, backoff y clasificación de errores.&lt;/li&gt;
&lt;li&gt;Los artefactos contienen evidencia suficiente sin exponer tokens.&lt;/li&gt;
&lt;li&gt;Hay métricas para latencia, expiración, cola y mensajes huérfanos.&lt;/li&gt;
&lt;li&gt;Un job de limpieza elimina buzones y registros después de su TTL.&lt;/li&gt;
&lt;li&gt;Se simula la caída de un pod durante la espera y durante la aserción.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Los formularios que disparan estas pruebas también necesitan estados comprensibles para el usuario; los &lt;a href="https://dev.to/silviutech/react-formularios-rapidos-que-tambien-incluyen-4gfb"&gt;formularios inclusivos y rápidos&lt;/a&gt; muestran por qué esa señal no debería quedar escondida detrás de un spinner. Cuando la evidencia de email tiene dueño, expiración y contexto, el pipeline deja de ser una caja negra y la operacion se vuelve mucho más predecible.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>sre</category>
      <category>devops</category>
      <category>testing</category>
    </item>
    <item>
      <title>Kubernetes: contrato de email para cada ejecución</title>
      <dc:creator>Alex Carter</dc:creator>
      <pubDate>Fri, 16 Oct 2026 23:23:17 +0000</pubDate>
      <link>https://dev.to/alexcarteruk/kubernetes-contrato-de-email-para-cada-ejecucion-17ep</link>
      <guid>https://dev.to/alexcarteruk/kubernetes-contrato-de-email-para-cada-ejecucion-17ep</guid>
      <description>&lt;p&gt;En un pipeline de CI, un email de verificación parece una dependencia pequeña. El pod crea un usuario, espera el mensaje y continúa. Pero cuando varias ejecuciones comparten buzón, el resultado verde puede esconder un problema: una prueba leyó el mensaje de otra ejecución.&lt;/p&gt;

&lt;p&gt;En equipos de plataforma trato el correo de prueba como un recurso con contrato, igual que un volumen temporal o una dirección IP. Cada ejecución debe declarar quién es, qué buzón puede leer, qué mensaje espera y cuándo deja de ser válido. Esta regla hace que una incidencia sea explicable incluso cuando el pod ya terminó.&lt;/p&gt;

&lt;p&gt;El patrón funciona con un servicio de &lt;a href="https://tempmailso.com" rel="noopener noreferrer"&gt;temp mail.so&lt;/a&gt; o con un capturador dentro del clúster. Lo importante es la frontera de confianza y la evidencia que deja cada ejecución.&lt;/p&gt;

&lt;h2&gt;
  
  
  El síntoma: el pod está verde, pero el email no llegó
&lt;/h2&gt;

&lt;p&gt;Un fallo típico empieza con un mensaje como “verification email not found”. El equipo aumenta el timeout y reintenta. A veces la prueba pasa, pero solo porque encontró un mensaje antiguo. Otras veces dos pods consumen el mismo correo y el resultado cambia según el orden de ejecución.&lt;/p&gt;

&lt;p&gt;La señal útil no es únicamente si llegó un email. Necesitamos saber:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;qué ejecución pidió el mensaje;&lt;/li&gt;
&lt;li&gt;qué dirección o alias estaba reservado;&lt;/li&gt;
&lt;li&gt;cuándo se envió la solicitud;&lt;/li&gt;
&lt;li&gt;qué mensajes fueron observados;&lt;/li&gt;
&lt;li&gt;cuál era el identificador esperado;&lt;/li&gt;
&lt;li&gt;cuándo expiró la reserva.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sin esos datos, una cadena como &lt;code&gt;tamp mail com&lt;/code&gt; puede aparecer en un ticket sin que sepamos si describe un alias o una búsqueda manual. La prueba queda verde o roja, pero no necesariamente correcta.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué debe contener el contrato de una ejecución
&lt;/h2&gt;

&lt;p&gt;Un contrato pequeño evita que cada test invente su propia política. Para cada ejecución guardo un manifiesto con cinco campos:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Identidad:&lt;/strong&gt; un &lt;code&gt;run_id&lt;/code&gt; único, el nombre del test y el índice del worker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Propiedad:&lt;/strong&gt; la dirección reservada y el consumidor autorizado.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expectativa:&lt;/strong&gt; asunto, destinatario y un token de correlación.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tiempo:&lt;/strong&gt; &lt;code&gt;created_at&lt;/code&gt;, límite de espera y &lt;code&gt;expires_at&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resultado:&lt;/strong&gt; mensaje reclamado, motivo de fallo y limpieza realizada.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;El token de correlación debe pertenecer a la ejecución, no solo al usuario de prueba. Un asunto como “Verify your account” es demasiado común. Un identificador corto del run es más fácil de filtrar y no depende del orden del proveedor.&lt;/p&gt;

&lt;p&gt;Conviene que el contrato tenga estados explícitos: &lt;code&gt;reserved&lt;/code&gt;, &lt;code&gt;waiting&lt;/code&gt;, &lt;code&gt;claimed&lt;/code&gt;, &lt;code&gt;expired&lt;/code&gt; y &lt;code&gt;released&lt;/code&gt;. Si la ejecución pasa de &lt;code&gt;waiting&lt;/code&gt; a &lt;code&gt;expired&lt;/code&gt;, el sistema debe explicar esa transición. No devería convertirla silenciosamente en otro intento con la misma dirección.&lt;/p&gt;

&lt;h2&gt;
  
  
  Un manifiesto de email para Kubernetes
&lt;/h2&gt;

&lt;p&gt;El manifiesto no necesita ser un recurso CRD. Puede ser un archivo JSON generado por el job y publicado como artefacto junto con los logs del pod; así resulta mas facil comparar ejecuciones:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"run_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"checkout-1842-07"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"owner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"worker-07"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mailbox"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"checkout-1842-07@example.test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expected"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"recipient"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"checkout-1842-07@example.test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"subject_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"run-1842-07"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-17T05:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expires_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-17T05:02:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"waiting"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El pod debe comprobar la expiración antes de cada lectura. El sidecar o servicio de correo puede registrar el mismo &lt;code&gt;run_id&lt;/code&gt;, pero no debería aceptar un mensaje sin una propiedad válida. Así se reduce el riesgo de que un retry lea la bandeja de la ejecución anterior.&lt;/p&gt;

&lt;p&gt;Para el aislamiento del entorno, recomiendo una dirección por run cuando el proveedor lo permite. Si solo existe un buzón compartido, usa aliases, tokens de correlación y una política de reclamación atómica. La práctica de &lt;a href="https://dev.to/silviutech/fastapi-aisla-pruebas-de-email-por-entorno-3e2l"&gt;aislar pruebas de email por entorno&lt;/a&gt; ayuda a separar esta decisión de la lógica de la aplicación.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cómo diagnosticar el fallo sin reintentar a ciegas
&lt;/h2&gt;

&lt;p&gt;Cuando una prueba falla, reviso las evidencias en este orden:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Comparo &lt;code&gt;created_at&lt;/code&gt;, entrega y &lt;code&gt;expires_at&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Verifico destinatario y token, no solo el asunto.&lt;/li&gt;
&lt;li&gt;Busco otro pod que tenga la misma dirección o &lt;code&gt;run_id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Compruebo si la reclamación fue atómica.&lt;/li&gt;
&lt;li&gt;Reviso si el cleanup ocurrió antes de guardar el artefacto.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Si el mensaje llegó después de expirar, es un problema de latencia o del presupuesto de espera. Si llegó a tiempo pero el token no coincide, hay contaminación entre ejecuciones o un bug en el productor. Si la aplicación falló después de reclamarlo, el buzón probablemente no es la causa.&lt;/p&gt;

&lt;p&gt;Este orden evita subir todos los timeouts. Un timeout más largo puede ocultar el síntoma y hacer el pipeline mas lento. Primero hay que medir; luego ajustar el presupuesto con una razón concreta. Tambien conviene mostrar estos datos en el resumen del job, no enterrarlos en un log de veinte mil líneas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist para SRE y equipos de plataforma
&lt;/h2&gt;

&lt;p&gt;Antes de dar por confiable una prueba de email en Kubernetes, compruebo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cada ejecución tiene un &lt;code&gt;run_id&lt;/code&gt; y una dirección propia, o una regla de aislamiento equivalente;&lt;/li&gt;
&lt;li&gt;el contrato registra propietario, token, expiración y estado;&lt;/li&gt;
&lt;li&gt;el polling empieza después de la acción que dispara el mensaje;&lt;/li&gt;
&lt;li&gt;la reclamación valida destinatario y correlación;&lt;/li&gt;
&lt;li&gt;una expiración produce un error específico, no un retry infinito;&lt;/li&gt;
&lt;li&gt;los artefactos sobreviven al borrado del pod;&lt;/li&gt;
&lt;li&gt;el cleanup ocurre después de guardar la evidencia;&lt;/li&gt;
&lt;li&gt;las métricas distinguen demora, contaminación y fallo de la aplicación;&lt;/li&gt;
&lt;li&gt;el próximo run no puede reutilizar un buzón todavía reservado.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La interfaz del test también importa. Un estado de carga claro y un error de email que no mueve el layout, como explica &lt;a href="https://dev.to/silviutech/react-errores-de-email-sin-mover-el-layout-pp4"&gt;errores de email sin mover el layout&lt;/a&gt;, reduce el ruido durante la depuración manual. Es la misma idea en otra capa: un estado visible debe corresponder con un estado real.&lt;/p&gt;

&lt;p&gt;Algunos equipos escriben &lt;code&gt;dummy e mail&lt;/code&gt; en scripts o notas. No pasa nada por ese texto, pero no debe convertirse en una identidad compartida ni en una regla de búsqueda. Las convenciones pueden ser imperfectas; el contrato de ownership no.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preguntas frecuentes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ¿Es mejor crear un buzón por pod o por test?
&lt;/h3&gt;

&lt;p&gt;Por test suele dar un diagnóstico más limpio. Por pod puede ser suficiente si el worker ejecuta una sola prueba y el aislamiento está garantizado. La elección debe seguir el nivel real de paralelismo.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Cuánto debe durar la reserva?
&lt;/h3&gt;

&lt;p&gt;Empieza con el tiempo normal de entrega más un margen pequeño. Mide los casos lentos y revisa el valor segun la latencia observada. Una reserva muy larga deja vivir datos obsoletos; una muy corta produce falsos fallos.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Qué hago si el proveedor solo ofrece un buzón compartido?
&lt;/h3&gt;

&lt;p&gt;Usa aliases o tokens únicos, reclamación atómica y un manifiesto por ejecución. Si no puedes garantizar ninguna de esas fronteras, trata el buzón como una dependencia limitada y registra esa limitación como riesgo del pipeline.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>sre</category>
      <category>devops</category>
      <category>testing</category>
    </item>
    <item>
      <title>Kubernetes: un presupuesto para emails de prueba</title>
      <dc:creator>Alex Carter</dc:creator>
      <pubDate>Thu, 15 Oct 2026 17:22:46 +0000</pubDate>
      <link>https://dev.to/alexcarteruk/kubernetes-un-presupuesto-para-emails-de-prueba-3e96</link>
      <guid>https://dev.to/alexcarteruk/kubernetes-un-presupuesto-para-emails-de-prueba-3e96</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  El síntoma: un job que nunca termina
&lt;/h2&gt;

&lt;p&gt;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 &lt;code&gt;verification timeout&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;el mensaje todavía está en tránsito;&lt;/li&gt;
&lt;li&gt;el buzón expiró antes de ser consultado;&lt;/li&gt;
&lt;li&gt;otro intento ya consumió el mensaje;&lt;/li&gt;
&lt;li&gt;el proveedor devolvió un error temporal;&lt;/li&gt;
&lt;li&gt;el test está esperando una dirección distinta de la que registró.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué debe significar realmente “esperar el email”
&lt;/h2&gt;

&lt;p&gt;Un consumidor de correo de prueba necesita un contrato pequeño, explícito y observable. Como mínimo, debe conocer:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Identidad:&lt;/strong&gt; dirección, identificador de ejecución y mensaje esperado.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ventana de validez:&lt;/strong&gt; cuándo se creó el buzón y cuándo deja de ser util.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Señal de éxito:&lt;/strong&gt; asunto, remitente o token que confirma el mensaje correcto.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regla de parada:&lt;/strong&gt; tiempo total, número de consultas y errores no reintentables.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resultado final:&lt;/strong&gt; encontrado, expirado, no encontrado o fallo del proveedor.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Esto también evita que dos pruebas compartan sin querer el mismo buzón. Para &lt;a href="https://dev.to/hannahdev56/como-probar-correos-de-onboarding-en-un-saas-sin-ensuciar-metricas-pjn"&gt;probar correos de onboarding sin ensuciar las métricas&lt;/a&gt;, conviene separar la identidad de prueba de las métricas del producto.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;run_id&lt;/code&gt;, la misma dirección y el mismo estado.&lt;/p&gt;

&lt;h2&gt;
  
  
  Diseñar un presupuesto de señales
&lt;/h2&gt;

&lt;p&gt;En vez de repetir hasta que algo funcione, asigna un presupuesto para cada tipo de espera:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tiempo total:&lt;/strong&gt; por ejemplo, 90 segundos para que llegue el mensaje.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Intervalo inicial:&lt;/strong&gt; una consulta después de 2 segundos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backoff:&lt;/strong&gt; aumentar el intervalo a 4, 8 y 12 segundos, con un máximo fijo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consultas:&lt;/strong&gt; cortar después de un número razonable, aunque quede tiempo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Errores:&lt;/strong&gt; distinguir un &lt;code&gt;404&lt;/code&gt; de buzón expirado de un &lt;code&gt;429&lt;/code&gt; que merece una pausa.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Un estado simple suele ser suficiente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;created -&amp;gt; waiting -&amp;gt; found
                   -&amp;gt; expired
                   -&amp;gt; provider_error
                   -&amp;gt; timed_out
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La transición debe ser idempotente. Si el controlador recibe dos respuestas para el mismo mensaje, solo una puede cerrar la prueba como &lt;code&gt;found&lt;/code&gt;. De lo contrario, el segundo intento puede dejar métricas contradictorias y el diagnóstico se vuelve confuso.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementación en Kubernetes
&lt;/h2&gt;

&lt;p&gt;El presupuesto debe vivir cerca del trabajo que lo consume, no solamente en el servicio externo. Un &lt;code&gt;Job&lt;/code&gt; puede recibir valores como &lt;code&gt;MAIL_WAIT_TOTAL_SECONDS&lt;/code&gt; y &lt;code&gt;MAIL_POLL_MAX_ATTEMPTS&lt;/code&gt;, mientras que un &lt;code&gt;ConfigMap&lt;/code&gt; contiene los valores por entorno. Los secretos, claro, deben seguir en un &lt;code&gt;Secret&lt;/code&gt; o en el gestor de secretos correspondiente.&lt;/p&gt;

&lt;p&gt;El contenedor debería emitir un recibo al terminar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"run_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ci-1842"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mailbox_created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-16T00:10:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"outcome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"timed_out"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"polls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"elapsed_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;91&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"last_error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"message_not_found"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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 &lt;code&gt;run_id&lt;/code&gt; original o marca claramente el intento; mezclar ambos hace parecer que hay dos mensajes cuando solo existe uno.&lt;/p&gt;

&lt;p&gt;Los reenvíos también necesitan una política. &lt;a href="https://dev.to/silviutech/react-reenvios-de-verificacion-sin-ansiedad-4km0"&gt;Los reenvíos de verificación con una experiencia predecible&lt;/a&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observabilidad y diagnóstico
&lt;/h2&gt;

&lt;p&gt;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ó.&lt;/p&gt;

&lt;p&gt;En una alerta o comentario de CI, incluye:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;run_id&lt;/code&gt; y nombre del job;&lt;/li&gt;
&lt;li&gt;edad del buzón al último intento;&lt;/li&gt;
&lt;li&gt;cantidad y tipo de errores;&lt;/li&gt;
&lt;li&gt;último estado conocido;&lt;/li&gt;
&lt;li&gt;si hubo reenvíos;&lt;/li&gt;
&lt;li&gt;el presupuesto que se consumió.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Si alguien escribe &lt;code&gt;tamp mail com&lt;/code&gt; o &lt;code&gt;dummy e mail&lt;/code&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist antes de declarar estable el flujo
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;¿Cada prueba tiene un buzón y un &lt;code&gt;run_id&lt;/code&gt; únicos?&lt;/li&gt;
&lt;li&gt;¿Hay un tiempo total y un máximo de consultas?&lt;/li&gt;
&lt;li&gt;¿Se diferencia expiración, rate limit y mensaje ausente?&lt;/li&gt;
&lt;li&gt;¿El backoff evita una tormenta contra el proveedor?&lt;/li&gt;
&lt;li&gt;¿Un reintento del &lt;code&gt;Job&lt;/code&gt; conserva o reemplaza la identidad de forma clara?&lt;/li&gt;
&lt;li&gt;¿Los logs excluyen tokens y contenido sensible?&lt;/li&gt;
&lt;li&gt;¿Se puede explicar el fallo mirando un solo recibo?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Si una respuesta es no, el próximo incidente volverá a parecer aleatorio.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cierre
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>sre</category>
      <category>devops</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
