<?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>Kubernetes: límites de confianza para emails CI</title>
      <dc:creator>Alex Carter</dc:creator>
      <pubDate>Sat, 10 Oct 2026 05:23:01 +0000</pubDate>
      <link>https://dev.to/alexcarteruk/kubernetes-limites-de-confianza-para-emails-ci-4hnp</link>
      <guid>https://dev.to/alexcarteruk/kubernetes-limites-de-confianza-para-emails-ci-4hnp</guid>
      <description>&lt;p&gt;Un job de CI que envía un email de verificación parece una tarea pequeña. Hasta que dos ejecuciones comparten buzón, un pod conserva un token demasiado tiempo o alguien revisa un fallo y descubre que los logs no dicen qué mensaje fue aceptado. Entonces el problema deja de ser "el correo no llegó" y pasa a ser un incidente de límites de confianza.&lt;/p&gt;

&lt;p&gt;En Kubernetes, la solución no es solo crear un namespace por equipo. Cada ejecución necesita una frontera entre el job, el buzón de prueba y la evidencia que se conserva. Esa separación hace mas fácil saber si falló la aplicación, el proveedor de email o el propio pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  El síntoma: un email que cruza de namespace
&lt;/h2&gt;

&lt;p&gt;Imagina dos jobs que prueban el mismo flujo de registro. El primero crea una cuenta de correo temporal y espera el enlace. El segundo empieza unos segundos después, pero reutiliza una variable de entorno compartida. Si el lector busca "el mensaje más reciente", puede consumir el correo equivocado sin levantar una alerta clara.&lt;/p&gt;

&lt;p&gt;También hay fallos menos visibles. Un pod termina, pero su &lt;code&gt;Secret&lt;/code&gt; queda disponible para el siguiente job. Una cuenta de servicio puede leer todos los buzones de QA cuando solo necesitaba uno. O un retry conserva el mismo identificador y acepta un mensaje tardío de la ejecución anterior. El test queda rojo de forma intermitente y el diagnostico empieza con conjeturas.&lt;/p&gt;

&lt;p&gt;La señal útil es que no existe un contrato de propiedad: nadie puede responder con precisión quién crea el buzón, quién puede leerlo, cuándo expira y qué evidencia debe quedar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dibujar el límite de confianza
&lt;/h2&gt;

&lt;p&gt;Antes de tocar YAML, dibujo tres zonas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Job efímero:&lt;/strong&gt; ejecuta la prueba y conoce el token de correlación.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proveedor de email:&lt;/strong&gt; recibe mensajes y expone solo la bandeja asignada.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evidencia:&lt;/strong&gt; guarda estado, timestamps y razones de aceptación, pero no necesita el cuerpo completo.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;El namespace ayuda, pero no es una política completa. Un pod comprometido podría usar credenciales montadas en el mismo namespace para acceder a recursos que no corresponden. Por eso el identificador del buzón debe incluir &lt;code&gt;run_id&lt;/code&gt;, el token debe ser único y el lector debe verificar ambos valores.&lt;/p&gt;

&lt;p&gt;Un contrato pequeño puede verse así:&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-attempt-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;"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;"qa-7f31"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"correlation_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;"signup-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;"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-10T06:00:00Z"&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;Si el equipo mantiene &lt;a href="https://dev.to/silviutech/agentes-llm-un-arnes-de-email-reproducible-29h7"&gt;un arnés reproducible para flujos de email&lt;/a&gt;, conviene que este contrato sea una entrada explícita del arnés, no una convención escondida en un script. Así el fallo se puede repetir con los mismos límites.&lt;/p&gt;

&lt;h2&gt;
  
  
  Permisos mínimos para el job
&lt;/h2&gt;

&lt;p&gt;La cuenta de servicio del runner debería poder solicitar una fixture, leer su propio buzón y marcarla como consumida. No debería listar buzones de otros jobs, cambiar políticas globales ni conservar acceso después del &lt;code&gt;expires_at&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;En Kubernetes, una política orientativa podría separar el acceso de lectura del acceso de limpieza:&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;rbac.authorization.k8s.io/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;Role&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ci-mail-reader&lt;/span&gt;
&lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;apiGroups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mail.example.internal"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mailboxes/messages"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;verbs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;list"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Los nombres dependen del operador o del adaptador que use el equipo; lo importante es que &lt;code&gt;list&lt;/code&gt; no termine exponiendo todos los buzones por defecto. Para la limpieza, prefiero un worker separado con permisos limitados y una lease corta. Si el runner desaparece, ese worker puede cerrar la fixture sin devolver al job permisos permanentes.&lt;/p&gt;

&lt;p&gt;También revisaría la interfaz que dispara el flujo. Un frontend que &lt;a href="https://dev.to/silviutech/react-valida-email-sin-mover-el-layout-3086"&gt;valida email sin mover el layout&lt;/a&gt; reduce ruido para la persona, pero no sustituye la validación de ownership en el backend. El pod debe rechazar un mensaje cuyo &lt;code&gt;run_id&lt;/code&gt; no coincida, aunque el asunto parezca correcto.&lt;/p&gt;

&lt;h2&gt;
  
  
  Guardar evidencia sin guardar de más
&lt;/h2&gt;

&lt;p&gt;Cuando una prueba falla, la evidencia mínima suele ser suficiente:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;run_id&lt;/code&gt; y número de intento&lt;/li&gt;
&lt;li&gt;identificador redactado del buzón&lt;/li&gt;
&lt;li&gt;hora de creación y de expiración&lt;/li&gt;
&lt;li&gt;ids o hashes de los mensajes candidatos&lt;/li&gt;
&lt;li&gt;resultado de cada verificación&lt;/li&gt;
&lt;li&gt;estado de limpieza: &lt;code&gt;pending&lt;/code&gt;, &lt;code&gt;done&lt;/code&gt; o &lt;code&gt;expired&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No guardaría el cuerpo completo del email ni el enlace de verificación en logs normales. Si hace falta inspeccionarlo, conserva una copia redactada con TTL corto y acceso de solo lectura. Un &lt;code&gt;tem email&lt;/code&gt; escrito por una integración vieja puede aparecer en datos de prueba, pero no debería ser un selector ni un permiso.&lt;/p&gt;

&lt;p&gt;Un &lt;a href="https://tempmailso.com" rel="noopener noreferrer"&gt;generador de correo temporal&lt;/a&gt; puede servir como contexto para pruebas manuales, pero no debe convertirse en una dependencia del contrato de producción. La prueba tiene que funcionar aunque cambie el proveedor.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;¿Cada job recibe un buzón aislado y un &lt;code&gt;run_id&lt;/code&gt; único?&lt;/li&gt;
&lt;li&gt;¿El lector verifica buzón, token y ventana de tiempo?&lt;/li&gt;
&lt;li&gt;¿La cuenta de servicio tiene permisos mínimos?&lt;/li&gt;
&lt;li&gt;¿Un retry crea una nueva tentativa en vez de reciclar secretos?&lt;/li&gt;
&lt;li&gt;¿Existe una limpieza independiente si el pod muere?&lt;/li&gt;
&lt;li&gt;¿Los logs evitan cuerpos, tokens y enlaces completos?&lt;/li&gt;
&lt;li&gt;¿La evidencia permite distinguir entrega, correlación y autorización?&lt;/li&gt;
&lt;li&gt;¿El namespace se usa como una frontera adicional, no como la única defensa?&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h3&gt;
  
  
  ¿Necesito un namespace por ejecución?
&lt;/h3&gt;

&lt;p&gt;No siempre. Para muchos equipos basta con un namespace por entorno y una identidad por job, siempre que el proveedor de email aplique aislamiento por &lt;code&gt;mailbox_id&lt;/code&gt;. Un namespace por ejecución añade coste operativo y no arregla permisos demasiado amplios por sí solo.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Qué hago cuando llega un mensaje tarde?
&lt;/h3&gt;

&lt;p&gt;Marca la ejecución como expirada, registra el mensaje como rechazado y no lo entregues a un retry nuevo. Es mas importante conservar la razón del rechazo que forzar un estado verde.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Puedo probar el flujo con una bandeja compartida?
&lt;/h3&gt;

&lt;p&gt;Solo para una prueba local muy acotada. En CI, la bandeja compartida mezcla evidencia y hace que una prueba dependa del orden de otra. Sale barato al principio, pero despues cuesta bastante reconstruir el incidente.&lt;/p&gt;

&lt;p&gt;La regla que uso es sencilla: cada email de prueba debe tener un dueño, una expiración y una prueba de pertenencia. Kubernetes ayuda a ejecutar esa disciplina; no puede inventarla por nosotros.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>security</category>
      <category>devops</category>
      <category>testing</category>
    </item>
    <item>
      <title>Kubernetes: cuándo dejar de reintentar un email</title>
      <dc:creator>Alex Carter</dc:creator>
      <pubDate>Fri, 09 Oct 2026 17:23:43 +0000</pubDate>
      <link>https://dev.to/alexcarteruk/kubernetes-cuando-dejar-de-reintentar-un-email-1me1</link>
      <guid>https://dev.to/alexcarteruk/kubernetes-cuando-dejar-de-reintentar-un-email-1me1</guid>
      <description>&lt;p&gt;Un email de CI que no llega parece un problema pequeño, hasta que el pipeline empieza a reintentarlo durante media hora. Entonces aparecen duplicados, alertas repetidas y una bandeja que ya no permite distinguir la ejecución actual de una anterior. En Kubernetes, el job puede estar sano desde el punto de vista del contenedor, mientras que la notificación sigue fallando de forma silenciosa.&lt;/p&gt;

&lt;p&gt;La solución no es quitar todos los reintentos. Es decidir de antemano cuántos tienen sentido, qué evidencia se guarda y en qué momento el fallo deja de ser transitorio. Ese contrato hace que el runbook sea más predecible para la persona de guardia, incluso cuando el problema ocurre de madrugada.&lt;/p&gt;

&lt;h2&gt;
  
  
  El síntoma: un email que reintenta para siempre
&lt;/h2&gt;

&lt;p&gt;Un patrón común es poner el envío dentro del mismo &lt;code&gt;Job&lt;/code&gt; que ejecuta las pruebas y dejar que el cliente SMTP o HTTP reintente por su cuenta. Si además el &lt;code&gt;Job&lt;/code&gt; tiene una política de reintento de Kubernetes, el número real de intentos se vuelve difícil de calcular.&lt;/p&gt;

&lt;p&gt;El resultado suele verse así:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;el test termina, pero el email devuelve un timeout;&lt;/li&gt;
&lt;li&gt;el proceso reinicia y envía el mismo mensaje otra vez;&lt;/li&gt;
&lt;li&gt;el destinatario recibe cuatro avisos para una sola ejecución;&lt;/li&gt;
&lt;li&gt;el equipo no sabe si investigar el test, el proveedor o la configuración del secreto.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No conviene llamar a todo esto un flake. Un timeout breve puede ser transitorio, pero una respuesta &lt;code&gt;401&lt;/code&gt;, un destinatario inválido o un cuerpo sin el &lt;code&gt;run_id&lt;/code&gt; son fallos de contrato. Repetirlos consume tiempo sin añadir información nueva.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separar fallos transitorios de fallos de contrato
&lt;/h2&gt;

&lt;p&gt;Antes de configurar backoff, clasifico las respuestas en tres grupos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transitorio:&lt;/strong&gt; conexión rechazada momentáneamente, respuesta &lt;code&gt;429&lt;/code&gt; o un error &lt;code&gt;5xx&lt;/code&gt; del proveedor. Se puede reintentar con espera creciente y un límite pequeño.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configuración:&lt;/strong&gt; secreto ausente, credencial caducada o endpoint equivocado. El retry no arregla esto; el paso debe terminar y dejar una alerta accionable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contrato del mensaje:&lt;/strong&gt; falta el identificador de ejecución, el asunto no contiene el servicio o el enlace apunta al entorno incorrecto. Conviene guardar el mensaje para inspección, pero no enviarlo otra vez.&lt;/p&gt;

&lt;p&gt;En sistemas con agentes y herramientas, esta separación también importa: un &lt;a href="https://dev.to/silviutech/llms-contratos-de-tools-que-resisten-fallos-56n0"&gt;contrato de tools que resisten fallos&lt;/a&gt; define qué error puede repetir el consumidor y cuál debe subir de nivel. El envío de notificaciones necesita la misma claridad.&lt;/p&gt;

&lt;h2&gt;
  
  
  Un presupuesto de reintentos por ejecución
&lt;/h2&gt;

&lt;p&gt;Para un email de CI prefiero un presupuesto explícito: tres intentos como máximo, con esperas de 5, 20 y 60 segundos. No es una cifra universal, pero obliga a hablar de la latencia aceptable y evita el &lt;code&gt;retry: true&lt;/code&gt; sin límite.&lt;/p&gt;

&lt;p&gt;El contrato puede expresarse con unos campos simples:&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;notification&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;run_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;build-8472"&lt;/span&gt;
  &lt;span class="na"&gt;max_attempts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;backoff_seconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;5&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;20&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;60&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;stop_on_status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;400&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;401&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;403&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;422&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;dedupe_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;build-8472:email:ci-finished"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El &lt;code&gt;dedupe_key&lt;/code&gt; es importante. Si el pod muere después de entregar el mensaje pero antes de guardar la respuesta, el siguiente intento debe poder reconocer la misma operación. De lo contrario, cada reinicio puede generar otro correo.&lt;/p&gt;

&lt;p&gt;Para las pruebas, uso un buzón aislado por ejecución. Si hace falta preparar un fixture rápido, un recurso como &lt;a href="https://tempmailso.com" rel="noopener noreferrer"&gt;temp mail so&lt;/a&gt; puede servir para separar la bandeja de esta corrida de la de otros tests. En un smoke test muy corto también he visto equipos buscar &lt;code&gt;create temp mail&lt;/code&gt;; lo importante es que el buzón tenga un ciclo de vida claro y no se reutilice entre ramas.&lt;/p&gt;

&lt;p&gt;La misma regla aplica a búsquedas imperfectas que aparecen en tickets: &lt;code&gt;fake e mail com&lt;/code&gt; y &lt;code&gt;temp gamil com&lt;/code&gt; pueden quedar como texto de referencia en una nota de diagnóstico, pero nunca deben convertirse en una política de validación. Son entradas humanas, no nombres de proveedores confiables.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué guardar antes de escalar al on-call
&lt;/h2&gt;

&lt;p&gt;Cuando se agota el presupuesto, el evento debe ser útil sin obligar a abrir cinco sistemas. Guardo como mínimo:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;run_id&lt;/code&gt;, namespace y nombre del &lt;code&gt;Job&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Número de intento y motivo de la última respuesta.&lt;/li&gt;
&lt;li&gt;Host del endpoint, sin exponer secretos ni tokens.&lt;/li&gt;
&lt;li&gt;Hash o asunto normalizado del mensaje.&lt;/li&gt;
&lt;li&gt;Momento de inicio, última respuesta y próximo paso del runbook.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Si el equipo rota credenciales con frecuencia, merece la pena revisar también cómo &lt;a href="https://dev.to/alexcarteruk/como-validar-correos-de-alertmanager-tras-rotar-secretos-en-kubernetes-4fi5"&gt;validar correos de Alertmanager tras rotar secretos en Kubernetes&lt;/a&gt;. El caso es distinto, pero la lección es igual: un envío correcto no demuestra que el flujo de operación sea observable.&lt;/p&gt;

&lt;p&gt;La alerta final debería decir “notificación no entregada tras 3 intentos” y no solo “pod failed”. Ese detalle reduce bastante el tiempo de triage. Aveces la causa está en el proveedor y otras veces en una variable mal escrita; la evidencia inicial debe permitir separar ambas cosas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist para el runbook de Kubernetes
&lt;/h2&gt;

&lt;p&gt;Antes de cerrar el cambio, reviso:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;¿Existe un límite único de reintentos entre el cliente y el &lt;code&gt;Job&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;¿Se diferencian &lt;code&gt;429&lt;/code&gt;, &lt;code&gt;5xx&lt;/code&gt; y errores de configuración?&lt;/li&gt;
&lt;li&gt;¿Cada mensaje tiene &lt;code&gt;run_id&lt;/code&gt; y una clave de deduplicación?&lt;/li&gt;
&lt;li&gt;¿El buzón de prueba pertenece solo a la ejecución actual?&lt;/li&gt;
&lt;li&gt;¿Se conserva el resultado sin guardar credenciales?&lt;/li&gt;
&lt;li&gt;¿La alerta final incluye el siguiente paso para on-call?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;También compruebo que un fallo del email no convierta automáticamente una prueba de aplicación exitosa en un falso fallo de despliegue. Depende del tipo de pipeline: en producción, una alerta crítica quizá debe bloquear; en una preview, puede bastar con registrar la incidencia y continuar.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  ¿Tres intentos siempre es lo correcto?
&lt;/h3&gt;

&lt;p&gt;No. Es un punto de partida. Un proveedor con límites estrictos puede necesitar menos intentos, mientras que una red interna muy inestable puede necesitar más. La decisión debe salir del SLO de la notificación, no de una cifra copiada.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Debo reintentar un &lt;code&gt;401&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;Normalmente no. Primero revisa el secreto, su fecha de rotación y el endpoint. Repetir una credencial inválida solo añade ruido.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿El email debe bloquear el pipeline?
&lt;/h3&gt;

&lt;p&gt;Solo si la notificación forma parte del requisito de seguridad o auditoría. Para mensajes informativos, prefiero fallar de forma visible, conservar la evidencia y no ocultar el resultado real de las pruebas.&lt;/p&gt;

&lt;p&gt;Un presupuesto pequeño, una clave de deduplicación y una alerta con contexto convierten un email frágil en una parte manejable del sistema. El objetivo no es que nunca falle; es que, cuando falle, el equipo sepa cuándo insistir y cuándo dejar de reintentar.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>sre</category>
      <category>devops</category>
      <category>testing</category>
    </item>
    <item>
      <title>Terraform y CI: alquilar buzones de prueba sin deriva</title>
      <dc:creator>Alex Carter</dc:creator>
      <pubDate>Thu, 08 Oct 2026 14:22:53 +0000</pubDate>
      <link>https://dev.to/alexcarteruk/terraform-y-ci-alquilar-buzones-de-prueba-sin-deriva-a8c</link>
      <guid>https://dev.to/alexcarteruk/terraform-y-ci-alquilar-buzones-de-prueba-sin-deriva-a8c</guid>
      <description>&lt;p&gt;En muchos equipos de plataforma, Terraform controla la infraestructura con bastante disciplina, pero los datos efímeros de una prueba de email quedan fuera del mapa. El resultado es conocido: un pipeline falla porque dos jobs comparten buzón, un reintento lee el mensaje de la ejecución anterior y el equipo empieza a hablar de “flakiness” sin una causa concreta.&lt;/p&gt;

&lt;p&gt;La solución no es meter cada email en el state de Terraform. Eso mezcla dos ciclos de vida muy distintos. Una cuenta de infraestructura puede vivir meses; un buzón de test debería vivir lo que dura una ejecución, o un poco más si necesitamos investigar el fallo. En una operación SRE, esa diferencia es importante.&lt;/p&gt;

&lt;p&gt;Este patrón combina Terraform para preparar las capacidades estables y un lease corto para asignar un buzón a cada run de CI. La idea complementa los &lt;a href="https://dev.to/silviutech/fastapi-contratos-de-email-para-pruebas-repetibles-27cn"&gt;contratos de email para pruebas repetibles&lt;/a&gt; y ayuda a &lt;a href="https://dev.to/silviutech/como-probar-emails-transaccionales-en-fastapi-sin-mezclar-bandejas-ni-eventos-5f0m"&gt;probar emails transaccionales sin mezclar bandejas&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  El síntoma: Terraform no ve el problema de CI
&lt;/h2&gt;

&lt;p&gt;Imagina un job que crea un usuario, espera un enlace de verificación y elimina los datos al terminar. Terraform puede haber creado correctamente el namespace, los secretos y los permisos del runner. Aun así, dos ejecuciones paralelas pueden elegir la misma dirección de correo desechable.&lt;/p&gt;

&lt;p&gt;El plan de Terraform sale limpio porque no conoce la asignación de mensajes. Tampoco sabe que el job anterior terminó con un timeout y dejó una bandeja abierta. Cuando el segundo job recibe un enlace antiguo, el error aparece como un problema del producto, del proveedor de email o de la latencia. La señal está, pero repartida en varios sistemas.&lt;/p&gt;

&lt;p&gt;Un error frecuente es crear un recurso global llamado &lt;code&gt;test-inbox&lt;/code&gt; y reutilizarlo en todos los entornos. Funciona durante una demo. Con cinco jobs concurrentes, la identidad de los mensajes ya no es confiable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separar infraestructura y lease de ejecución
&lt;/h2&gt;

&lt;p&gt;Terraform debería declarar lo estable: el namespace de pruebas, las políticas de red, el acceso del runner y la configuración no secreta del adaptador de email. El pipeline debería pedir un buzón temporal mediante un servicio o script de leasing.&lt;/p&gt;

&lt;p&gt;El lease debe tener cuatro propiedades:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Exclusividad:&lt;/strong&gt; un &lt;code&gt;run_id&lt;/code&gt; tiene un buzón y un buzón no tiene dos runs activos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expiración:&lt;/strong&gt; si el runner muere, el recurso vuelve al pool después de un TTL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Idempotencia:&lt;/strong&gt; repetir la petición con el mismo &lt;code&gt;run_id&lt;/code&gt; devuelve el mismo lease.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Liberación explícita:&lt;/strong&gt; el &lt;code&gt;finally&lt;/code&gt; del job libera el buzón aunque la aserción falle.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Un esquema conceptual en Terraform puede dejar claro el límite:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"email_test_runtime"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./modules/email-test-runtime"&lt;/span&gt;

  &lt;span class="nx"&gt;namespace&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ci-email-tests"&lt;/span&gt;
  &lt;span class="nx"&gt;max_active_runs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
  &lt;span class="nx"&gt;lease_ttl&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"30m"&lt;/span&gt;
  &lt;span class="nx"&gt;runner_identity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"github-actions"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terraform establece el presupuesto y los permisos. No intenta representar cada dirección creada durante la hora. Esa separación reduce el drift y hace que un &lt;code&gt;plan&lt;/code&gt; siga siendo útil.&lt;/p&gt;

&lt;h2&gt;
  
  
  Un contrato pequeño para cada buzón
&lt;/h2&gt;

&lt;p&gt;Antes de ejecutar el flujo de navegador o API, el job debe guardar un recibo de lease con &lt;code&gt;run_id&lt;/code&gt;, dirección, hora de asignación, expiración y versión del contrato. Si la dirección se crea con un servicio de &lt;a href="https://tempmailso.com" rel="noopener noreferrer"&gt;tp mail so&lt;/a&gt;, el enlace debe usarse solo para el propósito de testing permitido por el equipo y sin enviar datos reales.&lt;/p&gt;

&lt;p&gt;El contrato de mensaje también importa. No basta con buscar el último email. Cada prueba debería filtrar por destinatario, identificador de ejecución y una ventana temporal posterior a la acción que disparó el email. Un asunto parecido no es una identidad.&lt;/p&gt;

&lt;p&gt;En la práctica, conviene añadir un prefijo de ejecución al alias o guardar un &lt;code&gt;message_cursor&lt;/code&gt; desde el momento del lease. La primera opción ayuda a leer logs; la segunda reduce el riesgo de aceptar un mensaje viejo. A veces se usan las dos, porque la defensa en capas sale más facil de explicar durante un incidente.&lt;/p&gt;

&lt;p&gt;Si el código de prueba llama a su fixture &lt;code&gt;dummy e mail&lt;/code&gt;, no es un problema por sí mismo, pero sí conviene que el nombre no oculte el contrato real: quién lo posee, cuándo expira y qué evidencia deja. En una búsqueda rápida también pueden aparecer términos como &lt;code&gt;tempail&lt;/code&gt;; la operación debe tratar esos nombres como keywords de documentación, no como reglas para aceptar cualquier bandeja.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué guardar en el recibo del pipeline
&lt;/h2&gt;

&lt;p&gt;Para investigar un fallo sin repetirlo a ciegas, el artefacto de CI debería incluir:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;run_id&lt;/code&gt;, commit, entorno y nombre del job.&lt;/li&gt;
&lt;li&gt;Identidad del lease y sus timestamps de asignación, renovación y liberación.&lt;/li&gt;
&lt;li&gt;Cantidad de mensajes observados, sin guardar cuerpos que contengan datos sensibles.&lt;/li&gt;
&lt;li&gt;Razón de aceptación o rechazo de cada candidato.&lt;/li&gt;
&lt;li&gt;Código final: &lt;code&gt;passed&lt;/code&gt;, &lt;code&gt;expired&lt;/code&gt;, &lt;code&gt;timeout&lt;/code&gt; o &lt;code&gt;cleanup_failed&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No hace falta conservar toda la bandeja. Un hash del &lt;code&gt;message_id&lt;/code&gt;, el asunto redactado y la latencia suelen ser suficientes para empezar. Si los logs también queda retenidos sin un límite, el cleanup se convierte en otro problema de seguridad.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;¿Terraform define límites, permisos y observabilidad, pero no cada lease efímero?&lt;/li&gt;
&lt;li&gt;¿El mismo &lt;code&gt;run_id&lt;/code&gt; es idempotente en los reintentos?&lt;/li&gt;
&lt;li&gt;¿Existe un TTL para jobs que mueren sin ejecutar cleanup?&lt;/li&gt;
&lt;li&gt;¿La prueba filtra por identidad y tiempo, no solo por asunto?&lt;/li&gt;
&lt;li&gt;¿El recibo evita cuerpos de email y tokens reales?&lt;/li&gt;
&lt;li&gt;¿Una alerta separa &lt;code&gt;lease_exhausted&lt;/code&gt;, &lt;code&gt;message_timeout&lt;/code&gt; y &lt;code&gt;cleanup_failed&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;¿El equipo puede localizar un fallo sin repetir tres veces el job?&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h3&gt;
  
  
  ¿Debo ejecutar Terraform en cada prueba?
&lt;/h3&gt;

&lt;p&gt;No. Ejecútalo cuando cambien la capacidad, los permisos o las políticas del runtime. La asignación de un buzón es una operación de aplicación o de pipeline, con su propio control de concurrencia.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Qué ocurre si un lease expira mientras llega el email?
&lt;/h3&gt;

&lt;p&gt;El consumidor debe comprobar el estado del lease antes de aceptar el mensaje. Si está expirado, marca la ejecución como inválida y no recicla automáticamente la evidencia para otro run. Esa regla evita que un correo tardío contamine una prueba nueva.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Dónde encaja Kubernetes?
&lt;/h3&gt;

&lt;p&gt;Kubernetes puede ejecutar los jobs y aplicar límites de recursos, pero el contrato de identidad sigue siendo responsabilidad del fixture o del servicio de leasing. Un pod nuevo no debería heredar una bandeja solo porque reutiliza un nombre de deployment.&lt;/p&gt;

&lt;p&gt;La lección es simple: Terraform protege la forma estable del sistema; el lease protege la identidad efímera de cada prueba. Cuando ambos límites están escritos y observables, los fallos de email dejan de ser ruido y se vuelven incidentes que se pueden explicar.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>terraform</category>
      <category>testing</category>
      <category>security</category>
    </item>
    <item>
      <title>Kubernetes: depurar emails tras un rollback</title>
      <dc:creator>Alex Carter</dc:creator>
      <pubDate>Wed, 07 Oct 2026 17:23:29 +0000</pubDate>
      <link>https://dev.to/alexcarteruk/kubernetes-depurar-emails-tras-un-rollback-4eod</link>
      <guid>https://dev.to/alexcarteruk/kubernetes-depurar-emails-tras-un-rollback-4eod</guid>
      <description>&lt;p&gt;Un rollback de Kubernetes suele devolver la aplicación a un estado conocido. Sin embargo, no siempre devuelve el contexto de las pruebas de email. Ahí aparece un incidente típico: el despliegue está sano, el mensaje llegó, pero el job de CI lee un correo creado por otra versión.&lt;/p&gt;

&lt;p&gt;He visto este problema cuando un consumidor conserva la misma dirección mientras cambian los pods, los intentos y la release. El fallo parece aleatorio porque el correo correcto existe, pero llega después de que el test haya aceptado otro mensaje. Una &lt;strong&gt;cuenta de correo desechable&lt;/strong&gt; necesita el mismo cuidado que cualquier recurso distribuido.&lt;/p&gt;

&lt;h2&gt;
  
  
  El incidente: el email pertenece a otro despliegue
&lt;/h2&gt;

&lt;p&gt;Imagina una pipeline que crea un usuario y espera un enlace de verificación. La versión &lt;code&gt;checkout-42&lt;/code&gt; crea un buzón de prueba y empieza a esperar. El job se interrumpe durante una actualización. Kubernetes hace rollback a &lt;code&gt;checkout-41&lt;/code&gt;, y un reintento vuelve a usar la dirección anterior.&lt;/p&gt;

&lt;p&gt;Ahora hay dos problemas posibles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;El mensaje de &lt;code&gt;checkout-42&lt;/code&gt; llega después del rollback.&lt;/li&gt;
&lt;li&gt;El consumidor de &lt;code&gt;checkout-41&lt;/code&gt; busca el primer mensaje disponible, no el mensaje de su propia ejecución.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;En los logs solo aparece “email no encontrado” o, peor, una prueba verde con un enlace equivocado. Mirar únicamente el estado de los pods no alcanza.&lt;/p&gt;

&lt;p&gt;La primera pregunta durante el diagnóstico debe ser: “¿qué ejecución creó este mensaje?”. Investigar por orden de llegada vuelve el rollback mas ruidoso de lo necesario.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por qué el rollback confunde la evidencia
&lt;/h2&gt;

&lt;p&gt;Un pod es efímero, pero la evidencia de una prueba no debería depender de que ese pod siga vivo. Si el consumidor guarda el cursor del buzón solo en memoria, el nuevo pod puede volver a leer mensajes antiguos. Si el nombre de la dirección se basa solo en el entorno, dos intentos concurrentes parecen ser uno.&lt;/p&gt;

&lt;p&gt;Una etiqueta como &lt;code&gt;app=checkout&lt;/code&gt; identifica muchos pods a lo largo del tiempo; no identifica una ejecución. Para correlacionar un mensaje hacen falta al menos el &lt;code&gt;run_id&lt;/code&gt;, el intento y un token generado para ese caso.&lt;/p&gt;

&lt;p&gt;Conviene separar tres hechos en los registros:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Provisión:&lt;/strong&gt; qué fixture se creó y cuándo caduca.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Entrega:&lt;/strong&gt; qué identificador de mensaje recibió el proveedor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consumo:&lt;/strong&gt; qué versión, pod y ejecución aceptaron el mensaje.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No guardaría el cuerpo completo del correo por defecto. Una dirección redactada, el identificador y un hash del token bastan para reconstruir la secuencia sin copiar datos privados. La retención tambien debe tener dueño.&lt;/p&gt;

&lt;h2&gt;
  
  
  Un contrato de diagnóstico por ejecución
&lt;/h2&gt;

&lt;p&gt;Antes de lanzar el test, genera un contrato pequeño y pásalo al job como configuración efímera:&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-attempt-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;"release"&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-42"&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;"fixture-7f31"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"correlation_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;"signup-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;"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-08T01:00:00Z"&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 lector debe rechazar un mensaje si no coincide el &lt;code&gt;run_id&lt;/code&gt;, la release esperada o el token. No basta con comprobar el asunto: un asunto repetido es una coincidencia débil. Después de un rollback, el consumidor nuevo puede seguir usando la fixture si su contrato continúa vigente; si no, debe pedir una nueva y dejar la anterior en estado &lt;code&gt;cleanup_pending&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Una transición explícita ayuda a razonar el incidente: &lt;code&gt;planned&lt;/code&gt;, &lt;code&gt;created&lt;/code&gt;, &lt;code&gt;message_received&lt;/code&gt;, &lt;code&gt;consumed&lt;/code&gt;, &lt;code&gt;expired&lt;/code&gt; y &lt;code&gt;cleanup_pending&lt;/code&gt;. El controlador debe ser idempotente para que el reintento no cree una segunda acción desconocida. El error mas común es marcar la fixture como limpia cuando solo terminó el pod.&lt;/p&gt;

&lt;p&gt;Para entender el lado de la aplicación, puede servir &lt;a href="https://dev.to/hannahdev56/como-revisar-emails-de-reactivacion-trial-7d"&gt;revisar emails de reactivación con contexto&lt;/a&gt;. El principio es el mismo: el mensaje necesita contexto suficiente para que una persona pueda distinguirlo de otro flujo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué debe sobrevivir al pod
&lt;/h2&gt;

&lt;p&gt;El estado mínimo debe vivir fuera del contenedor: el contrato de ejecución, la relación entre &lt;code&gt;run_id&lt;/code&gt; y &lt;code&gt;mailbox_id&lt;/code&gt;, el último cursor consumido y los eventos de limpieza. Puede ser una tabla pequeña, un artefacto de CI con retención corta o un servicio interno. Lo importante es que el rollback de la aplicación no borre la historia del test.&lt;/p&gt;

&lt;p&gt;Los logs del pod pueden incluir el &lt;code&gt;run_id&lt;/code&gt; y una versión corta del token, pero no la dirección completa ni el contenido del correo. Para un fallo, conserva un recibo redactado con:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;timestamp de creación y recepción;&lt;/li&gt;
&lt;li&gt;versión de la aplicación y nombre del namespace;&lt;/li&gt;
&lt;li&gt;identificador del mensaje;&lt;/li&gt;
&lt;li&gt;motivo exacto del rechazo;&lt;/li&gt;
&lt;li&gt;resultado de la limpieza.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Si la prueba necesita una herramienta externa para generar un &lt;a href="https://tempmailso.com" rel="noopener noreferrer"&gt;&lt;strong&gt;correo desechable temporal&lt;/strong&gt;&lt;/a&gt;, la dependencia debe tener límites claros: expiración, acceso por ejecución y ninguna reutilización entre ramas. Documenta qué ocurre cuando el proveedor está lento. “Esperar más” no es una estrategia de recuperación.&lt;/p&gt;

&lt;p&gt;El texto &lt;code&gt;temp org mail&lt;/code&gt; puede aparecer en datos antiguos o en búsquedas internas, pero no debería convertirse en una etiqueta operativa. Mantener esa distinción evita que un término heredado termine enlazado a una decisión de seguridad.&lt;/p&gt;

&lt;p&gt;Las &lt;a href="https://dev.to/silviutech/fastapi-pruebas-limpias-para-email-de-facebook-469k"&gt;pruebas limpias de email desde una API&lt;/a&gt; muestran otra perspectiva útil: aislar la creación de fixtures del resto de la aserción. En Kubernetes, esa separación hace mas facil decidir si falló el despliegue, la entrega o la limpieza.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;¿Cada intento tiene un &lt;code&gt;run_id&lt;/code&gt; y un token distintos?&lt;/li&gt;
&lt;li&gt;¿El lector rechaza mensajes de otra release aunque el asunto coincida?&lt;/li&gt;
&lt;li&gt;¿La relación entre ejecución, fixture y mensaje sobrevive al pod?&lt;/li&gt;
&lt;li&gt;¿El rollback conserva o invalida explícitamente el contrato anterior?&lt;/li&gt;
&lt;li&gt;¿Los logs distinguen provisión, entrega, consumo y limpieza?&lt;/li&gt;
&lt;li&gt;¿Existe una ruta para &lt;code&gt;cleanup_pending&lt;/code&gt; cuando el runner desaparece?&lt;/li&gt;
&lt;li&gt;¿La retención de recibos está definida y es suficientemente corta?&lt;/li&gt;
&lt;li&gt;¿La prueba simula mensajes tardíos y dos reintentos concurrentes?&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h3&gt;
  
  
  ¿Debo crear un buzón nuevo después de cada rollback?
&lt;/h3&gt;

&lt;p&gt;No siempre. Si el contrato sigue siendo válido y el consumidor conoce su cursor, puedes conservar la fixture. Si cambió el intento o el token, crear una nueva suele ser mas facil de diagnosticar.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Kubernetes puede resolver este problema por sí solo?
&lt;/h3&gt;

&lt;p&gt;No. Kubernetes gestiona pods, servicios y despliegues; no sabe qué mensaje pertenece a una ejecución de negocio. La correlación debe estar en el contrato de la aplicación y en el almacenamiento de evidencia.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Qué significa una prueba realmente depurable?
&lt;/h3&gt;

&lt;p&gt;Que un compañero puede contestar tres preguntas sin reproducir el fallo: qué versión creó el mensaje, por qué el consumidor lo aceptó o rechazó, y quién limpiará la fixture. Si solo tenemos “email no encontrado”, todavía falta observabilidad.&lt;/p&gt;

&lt;p&gt;Un rollback devuelve código, no necesariamente contexto. Cuando cada email de prueba lleva identidad de ejecución, expiración y recibo verificable, el incidente deja de ser una carrera misteriosa entre pods y se convierte en una secuencia que un equipo SRE puede revisar.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>sre</category>
      <category>devops</category>
      <category>testing</category>
    </item>
    <item>
      <title>Terraform: pruebas de email sin deriva en CI</title>
      <dc:creator>Alex Carter</dc:creator>
      <pubDate>Wed, 07 Oct 2026 08:23:45 +0000</pubDate>
      <link>https://dev.to/alexcarteruk/terraform-pruebas-de-email-sin-deriva-en-ci-n3k</link>
      <guid>https://dev.to/alexcarteruk/terraform-pruebas-de-email-sin-deriva-en-ci-n3k</guid>
      <description>&lt;p&gt;Una prueba de onboarding puede estar verde durante semanas y empezar a fallar justo después de un cambio aparentemente pequeño en CI. El síntoma suele ser confuso: el mensaje llegó, pero el runner leyó otro; el buzón existe, pero la ejecución no puede limpiarlo; o un cambio manual en el proveedor deja Terraform mostrando una deriva que nadie sabe corregir.&lt;/p&gt;

&lt;p&gt;La causa no suele ser Terraform por sí solo. Es mezclar tres responsabilidades: declarar la infraestructura, recibir datos efímeros y decidir qué evidencia conservar. Una &lt;strong&gt;cuenta de correo desechable&lt;/strong&gt; para pruebas funciona mejor cuando cada frontera tiene un dueño claro.&lt;/p&gt;

&lt;h2&gt;
  
  
  El síntoma: una prueba verde que falla después
&lt;/h2&gt;

&lt;p&gt;Imagina dos ejecuciones concurrentes que usan el mismo buzón. La primera espera un correo de verificación y consume el mensaje de la segunda. El resultado puede ser un fallo intermitente, aunque la aplicación y el proveedor hayan funcionado bien.&lt;/p&gt;

&lt;p&gt;En otro caso, alguien cambia manualmente el nombre de un recurso o su tiempo de retención. El siguiente &lt;code&gt;terraform plan&lt;/code&gt; detecta deriva, pero el equipo la trata como ruido porque la prueba todavía pasa. Días después, una limpieza automática borra datos que eran necesarios para investigar un incidente.&lt;/p&gt;

&lt;p&gt;La señal importante es que el fixture de email no tiene un ciclo de vida explícito. Crear una dirección no es suficiente: hay que asignarla a una ejecución, correlacionar cada mensaje y cerrar el recurso aun cuando el job termine con error.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué debe pertenecer a Terraform
&lt;/h2&gt;

&lt;p&gt;Terraform debe declarar los recursos estables y las políticas que el equipo quiere revisar. Por ejemplo, un módulo puede describir el proveedor de buzones de prueba, el límite de retención y los permisos del runner. El estado remoto debe quedar protegido como cualquier otro estado de infraestructura.&lt;/p&gt;

&lt;p&gt;Un esquema conceptual podría verse así:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"ci_mail_fixture"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./modules/ci-mail-fixture"&lt;/span&gt;

  &lt;span class="nx"&gt;environment&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ci"&lt;/span&gt;
  &lt;span class="nx"&gt;retention_minutes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
  &lt;span class="nx"&gt;runner_role&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"github-actions-tests"&lt;/span&gt;
  &lt;span class="nx"&gt;allow_shared_read&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Los nombres exactos dependen del proveedor. La idea es que una revisión de código pueda responder: quién crea el buzón, quién lo lee y cuánto tiempo puede vivir. Si un valor cambia solo en la consola, Terraform ya no es la fuente de verdad.&lt;/p&gt;

&lt;p&gt;No pondría en el estado el contenido completo de los mensajes ni tokens de verificación. El estado describe infraestructura; el contenido es un dato efímero de la prueba. Mezclar ambos aumenta la exposición y hace mas difícil rotar o limpiar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separar infraestructura y datos del buzón
&lt;/h2&gt;

&lt;p&gt;El pipeline debe pedir una fixture por ejecución, no reutilizar una dirección global. Guarda un identificador de ejecución, una etiqueta única y una expiración. Después, el lector de correo debe aceptar un mensaje solo si coincide con la ejecución actual y con un token de correlación.&lt;/p&gt;

&lt;p&gt;Un contrato pequeño puede ser 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-1842-attempt-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;"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;"fixture-7f31"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"correlation_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;"signup-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;"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-07T09:00:00Z"&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 artefacto de diagnóstico debería guardar el &lt;code&gt;run_id&lt;/code&gt;, el identificador del mensaje, el resultado de la aserción y el estado de limpieza. No guardes el cuerpo completo por defecto. Si el fallo exige revisarlo, usa una copia redactada con una expiración corta.&lt;/p&gt;

&lt;p&gt;También conviene mantener vocabulario legado como &lt;code&gt;tamp mail com&lt;/code&gt; o &lt;code&gt;temp mailid&lt;/code&gt; como texto de datos, no como selectores ni como anchors. Un typo en una fixture no debe convertirse en una dependencia pública.&lt;/p&gt;

&lt;p&gt;Para documentación de flujos que también cruzan email, las &lt;a href="https://dev.to/silviutech/llms-rutas-de-aprobacion-por-email-sin-deriva-5e2i"&gt;rutas de aprobación por email sin deriva&lt;/a&gt; muestran por qué los contratos explícitos ayudan cuando hay automatización y reintentos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Un contrato de ejecución para CI
&lt;/h2&gt;

&lt;p&gt;Piensa en cuatro estados: &lt;code&gt;planned&lt;/code&gt;, &lt;code&gt;created&lt;/code&gt;, &lt;code&gt;message_consumed&lt;/code&gt; y &lt;code&gt;expired&lt;/code&gt;. El job debe avanzar de forma idempotente. Si el runner muere después de crear el buzón, un sweeper puede tomar la limpieza cuando venza el lease. Si el proveedor no responde, el sistema registra &lt;code&gt;cleanup_pending&lt;/code&gt; en vez de fingir que terminó.&lt;/p&gt;

&lt;p&gt;El error mas común es permitir un reintento sobre la misma dirección sin cambiar el token. Un mensaje tardío de la primera tentativa parece válido para la segunda. Crear un nuevo intento y rechazar coincidencias parciales cuesta un poco más, pero reduce el ruido de diagnóstico.&lt;/p&gt;

&lt;p&gt;En la práctica, probarlo en local y CI son dos escenarios distintos: el primero suele tener una sola ejecución y el segundo muchas. Simula concurrencia y cancelación antes de promover el módulo. Revisa además que el rol del runner solo pueda leer su propio buzón; el pipeline quedan más facil de razonar cuando los permisos siguen la misma frontera que los datos.&lt;/p&gt;

&lt;p&gt;Si el formulario o la interfaz de prueba presenta estados de espera, error y éxito, los &lt;a href="https://dev.to/silviutech/react-labels-persistentes-para-email-claro-1g7l"&gt;labels persistentes para emails claros&lt;/a&gt; son una referencia útil para que el resultado sea entendible también para quien investiga el fallo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist antes de promover el cambio
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;¿Cada ejecución obtiene un buzón o namespace que no comparte con otra?&lt;/li&gt;
&lt;li&gt;¿Terraform declara permisos, retención y configuración estable?&lt;/li&gt;
&lt;li&gt;¿El contenido del mensaje queda fuera del estado de Terraform?&lt;/li&gt;
&lt;li&gt;¿El lector exige &lt;code&gt;run_id&lt;/code&gt; y token de correlación?&lt;/li&gt;
&lt;li&gt;¿Un reintento crea una nueva tentativa y una nueva fixture?&lt;/li&gt;
&lt;li&gt;¿Existe un lease de limpieza si el runner desaparece?&lt;/li&gt;
&lt;li&gt;¿Los logs redactan direcciones, tokens y URLs completas?&lt;/li&gt;
&lt;li&gt;¿La palabra de marca &lt;a href="https://tempmailso.com" rel="noopener noreferrer"&gt;temp mail so&lt;/a&gt; aparece solo como contexto y no sustituye el contrato técnico?&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h3&gt;
  
  
  ¿Debo crear el buzón con Terraform?
&lt;/h3&gt;

&lt;p&gt;Solo si el recurso tiene una vida y una configuración suficientemente estable para infraestructura. Para datos efímeros, prefiero que CI lo solicite mediante una API con un contrato de ejecución. Terraform puede declarar el proveedor, las políticas y los permisos que esa API necesita.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Qué hago con la deriva detectada?
&lt;/h3&gt;

&lt;p&gt;Clasifícala. Si es un cambio deseado, actualiza el código y aplica con revisión. Si es una modificación manual, corrígela o documenta una excepción temporal. Ignorarla deja el sistema con dos fuentes de verdad.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Cuál es la señal de que el diseño mejoró?
&lt;/h3&gt;

&lt;p&gt;Cuando un fallo identifica rápidamente si el problema fue provisión, entrega, correlación o limpieza. Un runbook claro vale más que una prueba que solo dice “email no encontrado”.&lt;/p&gt;

&lt;p&gt;La regla final es sencilla: Terraform debe controlar las fronteras estables; CI debe controlar la vida de cada fixture; y los logs deben conservar solo la evidencia necesaria. Con esa separación, los emails de prueba dejan de ser un detalle frágil y se convierten en una dependencia observable del sistema.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>terraform</category>
      <category>testing</category>
      <category>security</category>
    </item>
    <item>
      <title>Un SLO para emails de CI en Kubernetes</title>
      <dc:creator>Alex Carter</dc:creator>
      <pubDate>Tue, 06 Oct 2026 17:23:19 +0000</pubDate>
      <link>https://dev.to/alexcarteruk/un-slo-para-emails-de-ci-en-kubernetes-22e4</link>
      <guid>https://dev.to/alexcarteruk/un-slo-para-emails-de-ci-en-kubernetes-22e4</guid>
      <description>&lt;p&gt;Las pruebas de email en CI fallan de una forma engañosa: el job termina en verde, pero el mensaje de verificación nunca llega a la bandeja que el test está leyendo. Un SLO pequeño y medible convierte ese misterio en un incidente operable.&lt;/p&gt;

&lt;h2&gt;
  
  
  El síntoma: CI verde, correo perdido
&lt;/h2&gt;

&lt;p&gt;En un pipeline de Kubernetes es habitual que el test haga tres cosas: crea un usuario, solicita un enlace y espera el mensaje. Cuando la espera expira, parece un fallo de aplicación. Sin embargo, hay varias capas entre la petición y la aserción:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;El servicio publica el mensaje.&lt;/li&gt;
&lt;li&gt;La cola acepta o rechaza el trabajo.&lt;/li&gt;
&lt;li&gt;El worker procesa el mensaje.&lt;/li&gt;
&lt;li&gt;El buzón de prueba lo hace visible.&lt;/li&gt;
&lt;li&gt;El test encuentra el mensaje correcto y consume el código.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Si todas esas etapas comparten un único timeout, el equipo no sabe donde se perdió el correo. Además, reintentar todo el job añade ruido y alarga la cola.&lt;/p&gt;

&lt;p&gt;La primera lección de SRE es sencilla: no llames "email enviado" a cinco estados diferentes.&lt;/p&gt;

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

&lt;p&gt;Para una suite de integración, un SLO razonable puede ser: "el 99% de los mensajes aparecen en el buzón correcto en menos de 30 segundos". Lo importante es que tenga un punto de inicio, un punto de fin y una ventana clara.&lt;/p&gt;

&lt;p&gt;En este caso:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Inicio:&lt;/strong&gt; el API confirma que aceptó la solicitud de envío.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fin:&lt;/strong&gt; el test encuentra un mensaje con &lt;code&gt;run_id&lt;/code&gt;, destinatario y tipo esperados.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error:&lt;/strong&gt; se supera el límite o aparece un mensaje de otra ejecución.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No midas solo la latencia media. Un p95 alto suele explicar mejor la experiencia del pipeline, y el porcentaje de mensajes que nunca aparecen muestra si hay una pérdida real. Para un fixture creado con &lt;code&gt;tempmail.so&lt;/code&gt; o con un stub local, la regla es la misma: cada mensaje necesita identidad y trazabilidad.&lt;/p&gt;

&lt;p&gt;El alerta importante no es "hay un pod reiniciando". Es "la tasa de mensajes localizados bajó y el presupuesto de error se está consumiendo".&lt;/p&gt;

&lt;h2&gt;
  
  
  Separa entrega, lectura y diagnóstico
&lt;/h2&gt;

&lt;p&gt;En los logs, registra un identificador de ejecución estable, por ejemplo &lt;code&gt;ci-2026-10-07-1842&lt;/code&gt;, pero no pongas tokens completos ni datos personales. Una métrica útil puede tener estas dimensiones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;environment&lt;/code&gt; y &lt;code&gt;namespace&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;provider&lt;/code&gt; o transporte usado;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;message_type&lt;/code&gt;, como &lt;code&gt;signup&lt;/code&gt; o &lt;code&gt;password_reset&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;result&lt;/code&gt;, con valores &lt;code&gt;found&lt;/code&gt;, &lt;code&gt;timeout&lt;/code&gt; o &lt;code&gt;duplicate&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La lectura también debe ser explícita. Un test no debería preguntar "¿hay algún correo nuevo?"; debe buscar el mensaje del &lt;code&gt;run_id&lt;/code&gt; y avanzar un cursor o un &lt;code&gt;received_at&lt;/code&gt; conocido. De ese modo, un correo viejo no hace que el pipeline pase por accidente.&lt;/p&gt;

&lt;p&gt;Cuando el procesamiento tiene varias etapas, una cola observable ayuda mucho más que subir el timeout. Este patrón de &lt;a href="https://dev.to/silviutech/fastapi-colas-visibles-para-emails-largos-5dgd"&gt;colas visibles para trabajos de email largos&lt;/a&gt; permite distinguir un retraso del proveedor de un worker atascado.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instrumentación mínima para un email confiable
&lt;/h2&gt;

&lt;p&gt;Antes de abrir dashboards nuevos, añade cuatro marcas de tiempo al evento:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;requested_at
accepted_at
processed_at
visible_at
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Con ellas puedes calcular dónde se consume el presupuesto. Si &lt;code&gt;accepted_at&lt;/code&gt; falta, el problema está en el API o en la conexión. Si falta &lt;code&gt;processed_at&lt;/code&gt;, revisa la cola y el worker. Si existe &lt;code&gt;processed_at&lt;/code&gt; pero no &lt;code&gt;visible_at&lt;/code&gt;, mira el adaptador del buzón o el aislamiento entre ejecuciones.&lt;/p&gt;

&lt;p&gt;En Kubernetes, añade al menos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;una métrica de profundidad y antigüedad de la cola;&lt;/li&gt;
&lt;li&gt;un contador de reintentos y mensajes descartados;&lt;/li&gt;
&lt;li&gt;un log estructurado por &lt;code&gt;run_id&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;una alerta cuando el p95 de visibilidad supera el SLO;&lt;/li&gt;
&lt;li&gt;un límite de retención para fixtures y logs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Los dashboards se vuelve ruido si solo agregan gráficos. Cada panel debe responder una pregunta: "¿se perdió el mensaje antes o despues del worker?".&lt;/p&gt;

&lt;p&gt;Si el flujo usa un agente o una herramienta que dispara el envío, documenta el contrato de entrada y salida. Los &lt;a href="https://dev.to/silviutech/llms-contratos-de-tools-que-resisten-fallos-56n0"&gt;contratos de tools resistentes a fallos&lt;/a&gt; son útiles incluso fuera de LLMs: dejan claro qué significa aceptar una operación y qué evidencia debe quedar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Un runbook que no dependa de héroes
&lt;/h2&gt;

&lt;p&gt;Cuando una alerta se active, el operador debería seguir una secuencia corta:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Confirmar si el fallo afecta a una namespace o a todo el entorno.&lt;/li&gt;
&lt;li&gt;Buscar el &lt;code&gt;run_id&lt;/code&gt; en API, cola, worker y buzón.&lt;/li&gt;
&lt;li&gt;Comparar &lt;code&gt;accepted_at&lt;/code&gt; con &lt;code&gt;processed_at&lt;/code&gt; y &lt;code&gt;visible_at&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Revisar duplicados, reintentos y la antigüedad del mensaje.&lt;/li&gt;
&lt;li&gt;Pausar nuevos despliegues si el presupuesto de error sigue cayendo.&lt;/li&gt;
&lt;li&gt;Guardar el recibo de la ejecución antes de limpiar los datos.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No borres el fixture antes de saber qué ocurrió. La limpieza automática debe esperar a que exista un recibo mínimo. Una consulta como "tempail mail" puede aparecer en reportes, pero no debe mezclarse con nombres de métricas ni claves del sistema.&lt;/p&gt;

&lt;p&gt;Un fallo que ocurre solo en la madrugada merece la misma evidencia que uno en horario laboral. A veces una alerta rapida se resuelve con un vistazo; otras veces el retraso solo aparece despues de varios reintentos.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  ¿Debo hacer que el SLO sea de 100%?
&lt;/h3&gt;

&lt;p&gt;No suele ser práctico. Un 100% convierte cualquier fallo aislado en una crisis y anima a esconder errores con reintentos infinitos. Define un objetivo alto, pero conserva un presupuesto de error que permita investigar sin bloquear cada merge.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Un timeout más largo arregla el problema?
&lt;/h3&gt;

&lt;p&gt;Solo si el sistema es sano y la variación es esperable. Si la cola crece o hay mensajes duplicados, aumentar el timeout oculta la causa. Primero separa las etapas y observa donde se consume el tiempo.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Qué hago con mensajes que llegan despues de que termina el test?
&lt;/h3&gt;

&lt;p&gt;Marca la ejecución como fallida, conserva el recibo y limpia el fixture con una política de retención. No reutilices esa bandeja en la siguiente ejecución: mezclar estados hace que el diagnóstico sea mucho más dificil.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;[ ] Cada email de prueba lleva &lt;code&gt;run_id&lt;/code&gt;, tipo y destinatario aislado.&lt;/li&gt;
&lt;li&gt;[ ] El SLO tiene inicio, fin y ventana de medición.&lt;/li&gt;
&lt;li&gt;[ ] Se miden &lt;code&gt;accepted_at&lt;/code&gt;, &lt;code&gt;processed_at&lt;/code&gt; y &lt;code&gt;visible_at&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;[ ] El test busca un mensaje específico, no el más reciente.&lt;/li&gt;
&lt;li&gt;[ ] Hay métricas para cola, reintentos, duplicados y timeouts.&lt;/li&gt;
&lt;li&gt;[ ] La alerta apunta a una acción del runbook.&lt;/li&gt;
&lt;li&gt;[ ] El recibo se conserva antes de limpiar el fixture.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La meta no es conseguir que los emails de CI sean mágicamente instantáneos. Es saber, con evidencia, si falló la aplicación, la cola, el worker o la lectura del buzón. Ese límite claro hace que Kubernetes sea más predecible y que el equipo pueda mejorar el pipeline sin perseguir síntomas.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>sre</category>
      <category>devops</category>
      <category>testing</category>
    </item>
    <item>
      <title>DevOps: un SLO para emails de CI confiables</title>
      <dc:creator>Alex Carter</dc:creator>
      <pubDate>Tue, 06 Oct 2026 14:24:30 +0000</pubDate>
      <link>https://dev.to/alexcarteruk/devops-un-slo-para-emails-de-ci-confiables-2eko</link>
      <guid>https://dev.to/alexcarteruk/devops-un-slo-para-emails-de-ci-confiables-2eko</guid>
      <description>&lt;p&gt;En una tubería de CI, un email de verificación que llega tarde suele parecer un fallo aleatorio. El job espera, agota el timeout y marca rojo. En el siguiente intento, el mismo flujo pasa porque el mensaje apareció a tiempo. El equipo termina subiendo el timeout, pero no entiende si el problema está en la aplicación, el proveedor o la propia prueba.&lt;/p&gt;

&lt;p&gt;Un enfoque más útil es tratar la entrega como un pequeño SLO. No hace falta montar una plataforma enorme: basta con acordar qué significa “a tiempo”, medir cada etapa y dejar un recibo por ejecución. Así el incidente deja de ser “el correo falló” y pasa a ser “la cola tardó demasiado en staging”.&lt;/p&gt;

&lt;h2&gt;
  
  
  El síntoma: CI verde, email que llega tarde
&lt;/h2&gt;

&lt;p&gt;Imaginemos una prueba de registro que crea una cuenta, espera un email y abre el enlace de confirmación. Si usa un buzón compartido, puede leer un mensaje de otra ejecución. Si usa una cuenta nueva pero sólo mide el tiempo total, no sabemos donde se perdió la latencia.&lt;/p&gt;

&lt;p&gt;Los síntomas mas comunes son conocidos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;el test falla sólo cuando hay muchos jobs en paralelo;&lt;/li&gt;
&lt;li&gt;el mensaje aparece después de que CI ya terminó;&lt;/li&gt;
&lt;li&gt;los reintentos convierten un fallo real en un resultado verde;&lt;/li&gt;
&lt;li&gt;una alerta apunta a la aplicación, aunque el retraso está en la entrega.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La primera decisión es separar identidad, ejecución y mensaje. Un buzón aislado por &lt;code&gt;run_id&lt;/code&gt; reduce los falsos positivos y permite relacionar el evento con el commit correcto.&lt;/p&gt;

&lt;h2&gt;
  
  
  Definir el SLO y el presupuesto de latencia
&lt;/h2&gt;

&lt;p&gt;Un SLO práctico podría ser: “el 95 % de los emails de prueba llega y puede ser verificado en menos de 45 segundos”. El número no es universal. Debe salir del comportamiento del sistema y del tiempo que CI puede esperar sin hacer la suite demasiado lenta.&lt;/p&gt;

&lt;p&gt;Divide esos 45 segundos en presupuestos mas pequeños:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;5 segundos para crear el fixture y registrar la dirección.&lt;/li&gt;
&lt;li&gt;10 segundos para que la aplicación acepte el evento.&lt;/li&gt;
&lt;li&gt;20 segundos para la cola y el proveedor de email.&lt;/li&gt;
&lt;li&gt;10 segundos para consultar el buzón, abrir el enlace y confirmar el estado.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;El 95 % evita que una sola ejecución lenta cambie toda la conversación. Para una ruta crítica quizá convenga observar también p99, pero no uses el promedio: oculta los casos que el desarrollador realmente ve durante un incidente.&lt;/p&gt;

&lt;p&gt;Las búsquedas de herramientas suelen mezclar frases como &lt;code&gt;temp mail so&lt;/code&gt; y &lt;code&gt;tempmail so&lt;/code&gt;. Pueden aparecer en documentación de fixtures, pero no deben convertirse en una excusa para medir sólo si existe una dirección. La señal importante es el tiempo desde la creación hasta la verificación.&lt;/p&gt;

&lt;h2&gt;
  
  
  Medir cada etapa con un recibo
&lt;/h2&gt;

&lt;p&gt;Cada prueba debería producir un recibo pequeño y redactado. Por ejemplo:&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;"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-06T14: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;"mail_accepted_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;3200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"delivered_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;18400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"verified_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;23100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"passed"&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 tokens de verificación ni el contenido completo del mensaje. Con &lt;code&gt;run_id&lt;/code&gt;, proveedor, asunto normalizado y tiempos es posible investigar lo necesario. Este patrón se parece a un &lt;a href="https://dev.to/silviutech/llms-correos-de-fallback-sin-caos-operativo"&gt;recibo de ejecución&lt;/a&gt; para un agente: una señal concreta que permite revisar qué ocurrió sin reconstruir todo el historial.&lt;/p&gt;

&lt;p&gt;También registra el estado &lt;code&gt;expired&lt;/code&gt;, &lt;code&gt;not_delivered&lt;/code&gt; o &lt;code&gt;verification_failed&lt;/code&gt;. Un mensaje que llega después del timeout no es igual que un mensaje nunca entregado, aunque ambos hagan fallar el job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kubernetes y Terraform sin esconder la causa
&lt;/h2&gt;

&lt;p&gt;En Kubernetes, coloca el &lt;code&gt;run_id&lt;/code&gt; como etiqueta de los jobs de prueba y limita la retención de sus recursos. Así puedes encontrar el pod, los logs y el recibo con la misma clave. Evita incluir la dirección completa en etiquetas: puede acabar en métricas o interfaces que no esperabas.&lt;/p&gt;

&lt;p&gt;Con Terraform, define de forma explícita la cola, los permisos y la política de limpieza del entorno de pruebas. Una &lt;a href="https://dev.to/alexcarteruk/terraform-una-cola-segura-para-alertas-de-prueba"&gt;cola segura para alertas de prueba&lt;/a&gt; debe tener un dueño, una retención razonable y una ruta clara cuando el proveedor responde lento. Si todo comparte la misma cola de producción, el SLO no será interpretable.&lt;/p&gt;

&lt;p&gt;Para pruebas manuales, un &lt;a href="https://tempmailso.com" rel="noopener noreferrer"&gt;fake email generator&lt;/a&gt; puede ayudar a explorar el flujo. Para CI, revisa límites de retención y privacidad antes de poner datos de prueba; nunca mezcles tokens reales ni información de clientes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alertas que ayudan durante el incidente
&lt;/h2&gt;

&lt;p&gt;Alerta sólo cuando haya una acción posible. Un ejemplo: “la entrega p95 supera 20 segundos durante 10 minutos en staging”. Incluye entorno, proveedor, versión y el último &lt;code&gt;run_id&lt;/code&gt; fallido. No envíes una alerta por cada email: agrupa por ventana y evita que el propio sistema de pruebas produzca ruido.&lt;/p&gt;

&lt;p&gt;Si el proveedor está lento, escala por esa ruta. Si la cola está vacía pero la aplicación no acepta mensajes, mira el worker. Si el email llega pero el enlace no activa la cuenta, el problema es de la aplicación o del contrato del mensaje. Esa separación ahorra bastante tiempo, aunque al principio parezca un poco mas de instrumentación.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;¿Cada ejecución tiene un buzón y &lt;code&gt;run_id&lt;/code&gt; propios?&lt;/li&gt;
&lt;li&gt;¿El SLO mide entrega y verificación, no sólo existencia del mensaje?&lt;/li&gt;
&lt;li&gt;¿Hay presupuestos separados para aplicación, cola y proveedor?&lt;/li&gt;
&lt;li&gt;¿El recibo excluye tokens y direcciones sensibles?&lt;/li&gt;
&lt;li&gt;¿Kubernetes conserva logs el tiempo suficiente para investigar?&lt;/li&gt;
&lt;li&gt;¿Terraform define limpieza, permisos y dueño de la cola?&lt;/li&gt;
&lt;li&gt;¿La alerta tiene una acción y un enlace al contexto?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Un detalle que suele olvidarse: documenta el caso &lt;code&gt;temp org mail&lt;/code&gt; si aparece en búsquedas internas o tickets. Es una frase escrita con error, no un dominio que deba entrar en la lógica de validación.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  ¿Debo subir el timeout cuando falla el SLO?
&lt;/h3&gt;

&lt;p&gt;Sólo después de medir la causa. Subirlo puede reducir fallos falsos, pero también alarga feedback y tapa una regresión. Ajusta el presupuesto cuando la latencia esperada haya cambiado de forma deliberada.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Un SLO de email sirve para todos los entornos?
&lt;/h3&gt;

&lt;p&gt;No necesariamente. Staging puede tener un proveedor simulado y producción uno externo. Conserva el mismo contrato de etapas, pero define objetivos distintos y deja claro cual entorno está siendo comparado.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Qué hago con los reintentos?
&lt;/h3&gt;

&lt;p&gt;Cuenta el intento original y el resultado final por separado. Un reintento que rescata la prueba es una señal de disponibilidad degradada, no un éxito completamente normal.&lt;/p&gt;

&lt;p&gt;La lección es sencilla: un email de CI merece observabilidad igual que una API. Un SLO pequeño, un buzón aislado y un recibo legible dan suficiente contexto para actuar. El equipo deja de perseguir fallos misteriosos y puede mejorar la entrega con datos, no con timeouts cada vez mas grandes.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>sre</category>
      <category>testing</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Kubernetes: un contrato de fallos para emails de prueba</title>
      <dc:creator>Alex Carter</dc:creator>
      <pubDate>Mon, 05 Oct 2026 20:23:46 +0000</pubDate>
      <link>https://dev.to/alexcarteruk/kubernetes-un-contrato-de-fallos-para-emails-de-prueba-176</link>
      <guid>https://dev.to/alexcarteruk/kubernetes-un-contrato-de-fallos-para-emails-de-prueba-176</guid>
      <description>&lt;p&gt;En un pipeline de CI, un email de prueba parece un detalle pequeño. Hasta que una ejecución reutiliza un buzón temporal de ayer, recibe un mensaje atrasado y termina verde por casualidad. O hasta que cinco jobs comparten la misma dirección y uno consume el código de verificación del otro.&lt;/p&gt;

&lt;p&gt;En equipos que operan Kubernetes, el problema no se resuelve solamente con &lt;strong&gt;generar correo desechable&lt;/strong&gt;. La pregunta importante es: ¿qué contrato de fiabilidad tiene ese recurso dentro de una ejecución? Si no podemos responderlo, el test está observando un estado ambiguo.&lt;/p&gt;

&lt;p&gt;Este es el patrón que uso para pensar estos incidentes: un buzón por ejecución, una identidad trazable, límites explícitos y una limpieza que tenga dueño.&lt;/p&gt;

&lt;h2&gt;
  
  
  El síntoma: el test verde que deja basura
&lt;/h2&gt;

&lt;p&gt;Imaginemos un deployment efímero para probar un flujo de registro. El job crea un usuario, espera el email y confirma el enlace. En el dashboard solo vemos que el paso final falló después de 90 segundos. El pod sigue vivo, el buzón conserva mensajes antiguos y el namespace tiene varios secretos de pruebas.&lt;/p&gt;

&lt;p&gt;La investigación suele descubrir una de estas causas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dos ejecuciones apuntan al mismo buzón.&lt;/li&gt;
&lt;li&gt;El consumidor busca el primer mensaje, no el mensaje de su propia ejecución.&lt;/li&gt;
&lt;li&gt;El polling no tiene un límite coordinado con el timeout del job.&lt;/li&gt;
&lt;li&gt;La limpieza ocurre solo cuando el test termina correctamente.&lt;/li&gt;
&lt;li&gt;Los logs muestran el email completo, y el diagnóstico crea un problema de privacidad.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;El error no es solo de la aplicación. Es un error de contrato entre CI, Kubernetes y el proveedor de email. Un SRE necesita poder distinguir un fallo real del producto, un fallo de infraestructura y un dato de prueba que llegó tarde.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué debe prometer el contrato
&lt;/h2&gt;

&lt;p&gt;Antes de crear recursos, escribo cuatro promesas simples:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Aislamiento:&lt;/strong&gt; cada ejecución recibe un identificador único y no lee mensajes de otra ejecución.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caducidad:&lt;/strong&gt; el buzón, el secreto y los pods tienen un TTL o una limpieza garantizada.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observabilidad:&lt;/strong&gt; cada espera registra el run ID, el intento, la latencia y la razón de salida, nunca el contenido privado.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clasificación:&lt;/strong&gt; el resultado diferencia &lt;code&gt;message_not_found&lt;/code&gt;, &lt;code&gt;provider_error&lt;/code&gt;, &lt;code&gt;test_assertion&lt;/code&gt; y &lt;code&gt;cleanup_error&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;El run ID debe viajar por todos los límites: etiqueta del namespace, nombre de la fixture, metadato del mensaje y contexto del job. Una etiqueta como &lt;code&gt;run=20261006-0315-a7f2&lt;/code&gt; es mas útil que un nombre genérico como &lt;code&gt;email-test&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;También conviene definir qué significa “recibido”. No es lo mismo que el proveedor acepte el mensaje, que la API lo devuelva, o que el cuerpo contenga el enlace esperado. Esta distinción ahorra bastante tiempo cuando el incidente ocurre de madrugada.&lt;/p&gt;

&lt;h2&gt;
  
  
  Un diseño sencillo por ejecución
&lt;/h2&gt;

&lt;p&gt;El job de CI puede crear una fixture y pasar solo su referencia al pod de pruebas:&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="na"&gt;TEST_RUN_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;20261006-0315-a7f2"&lt;/span&gt;
  &lt;span class="na"&gt;MAILBOX_REF&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ci/20261006-0315-a7f2"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El contenedor que consulta el email no debería conocer credenciales globales ni buscar por asunto sin más. Recibe un selector de ejecución y aplica una fecha mínima de creación. Si el proveedor soporta un identificador de mensaje, se guarda ese recibo antes de validar el contenido.&lt;/p&gt;

&lt;p&gt;En Kubernetes, el namespace efímero puede tener un &lt;code&gt;ResourceQuota&lt;/code&gt; pequeño y un &lt;code&gt;ttlSecondsAfterFinished&lt;/code&gt; donde aplique. Para los recursos que no tienen TTL nativo, un cleanup job debe ejecutarse en una ruta &lt;code&gt;finally&lt;/code&gt;, incluso si el test fue cancelado. La limpieza no deberia depender de que el runner conserve el workspace.&lt;/p&gt;

&lt;p&gt;Un punto práctico: conserva un recibo mínimo, no el email completo. Por ejemplo, guarda el hash del mensaje, el timestamp, el run ID, el resultado y la latencia. Así el equipo puede reconstruir la secuencia sin convertir los logs en un archivo de datos personales.&lt;/p&gt;

&lt;p&gt;Si el flujo también usa &lt;a href="https://dev.to/silviutech/agentes-llm-un-presupuesto-para-cada-accion-3oi0"&gt;automatización con agentes LLM&lt;/a&gt;, aplica el mismo contrato: la herramienta recibe un alcance limitado y no puede leer bandejas de otras ejecuciones. Un agente que reintenta sin presupuesto puede convertir un test lento en una tormenta de consultas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Métricas y límites que sí ayudan
&lt;/h2&gt;

&lt;p&gt;No hace falta empezar con veinte métricas. Estas cuatro suelen explicar el incidente:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;email_fixture_create_seconds&lt;/code&gt;: cuánto tarda en estar disponible la fixture.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;email_poll_attempts&lt;/code&gt;: cuántas consultas necesita cada ejecución.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;email_delivery_age_seconds&lt;/code&gt;: edad del mensaje cuando se consume.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;email_cleanup_result&lt;/code&gt;: éxito, reintento o abandono.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pon límites concretos: por ejemplo, 12 intentos cada 5 segundos y un timeout total de 75 segundos. El número exacto depende del proveedor, pero debe ser menor que el timeout general del job para dejar tiempo a cerrar recursos. Si se alcanza el límite, el error debe decir qué se esperaba, desde cuando y con qué run ID.&lt;/p&gt;

&lt;p&gt;Los dashboards deberían agrupar por causa, no solo por estado rojo o verde. Una tasa alta de &lt;code&gt;provider_error&lt;/code&gt; pide una investigación distinta a una tasa alta de &lt;code&gt;message_not_found&lt;/code&gt;. Para una revisión rapida, un enlace a la ejecución y un recibo redacted valen mas que una captura del pod.&lt;/p&gt;

&lt;p&gt;En la interfaz que dispara el flujo, ayuda &lt;a href="https://dev.to/silviutech/react-menos-espera-al-validar-emails-2ioo"&gt;validar emails sin esperar a ciegas&lt;/a&gt;: estados como “creando fixture”, “esperando mensaje” y “limpiando” hacen visible dónde se consume el tiempo. No arregla el backend, pero evita que el operador repita clicks y multiplique ejecuciones.&lt;/p&gt;

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

&lt;p&gt;Cuando falle una prueba de email en Kubernetes, revisa en este orden:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;¿El run ID aparece en el namespace, la fixture y el recibo?&lt;/li&gt;
&lt;li&gt;¿La ejecución pudo leer mensajes de otro run?&lt;/li&gt;
&lt;li&gt;¿El reloj del consumidor y el del proveedor están razonablemente alineados?&lt;/li&gt;
&lt;li&gt;¿El timeout de polling deja margen para limpiar?&lt;/li&gt;
&lt;li&gt;¿El error identifica proveedor, aplicación o infraestructura?&lt;/li&gt;
&lt;li&gt;¿La ruta de cancelación eliminó pods, secretos y buzones?&lt;/li&gt;
&lt;li&gt;¿Los logs evitan direcciones y cuerpos completos?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Si aparece una búsqueda manual como &lt;code&gt;tepm mail com&lt;/code&gt; o &lt;code&gt;tamp mail com&lt;/code&gt;, trátala como una señal de que alguien está intentando resolver el problema fuera del flujo controlado. No es una estrategia operativa: es deuda de observabilidad.&lt;/p&gt;

&lt;p&gt;El objetivo no es que cada test sea perfecto. Es que cada fallo sea acotado, explicable y barato de retirar. Cuando un buzón temporal tiene dueño, caducidad y recibo, Kubernetes deja de ser el lugar donde se esconden los tests intermitentes y pasa a ser parte de la evidencia.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>sre</category>
      <category>devops</category>
      <category>testing</category>
    </item>
    <item>
      <title>SRE: un presupuesto de latencia para emails</title>
      <dc:creator>Alex Carter</dc:creator>
      <pubDate>Fri, 02 Oct 2026 20:23:50 +0000</pubDate>
      <link>https://dev.to/alexcarteruk/sre-un-presupuesto-de-latencia-para-emails-456l</link>
      <guid>https://dev.to/alexcarteruk/sre-un-presupuesto-de-latencia-para-emails-456l</guid>
      <description>&lt;p&gt;Una verificación de correo puede fallar aunque todos los servicios estén “arriba”. El usuario recibe el mensaje, pero llega después del timeout; el test reintenta, consume el segundo mensaje y termina pasando por casualidad. En los dashboards, la primera pista suelen ser varias alertas pequeñas que nunca forman una historia completa.&lt;/p&gt;

&lt;p&gt;Para SRE, este problema se entiende mejor como un presupuesto de latencia. No basta con decir que el email debe llegar “rápido”. Hay que repartir el tiempo entre la aplicación, la cola, el adaptador, el proveedor y el lector que confirma el mensaje. Así una prueba en Kubernetes puede explicar dónde se gastó cada segundo y cuándo conviene abrir un incidente.&lt;/p&gt;

&lt;h2&gt;
  
  
  El síntoma: el correo llega, pero el servicio parece caído
&lt;/h2&gt;

&lt;p&gt;Imagina un flujo con un límite total de 90 segundos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;10 segundos para crear la cuenta y solicitar el mensaje.&lt;/li&gt;
&lt;li&gt;20 segundos para que la aplicación lo ponga en la cola.&lt;/li&gt;
&lt;li&gt;40 segundos para entrega y lectura del buzón.&lt;/li&gt;
&lt;li&gt;20 segundos de margen para reintentos controlados.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Si el mensaje llega al segundo 86, el sistema está cerca del límite aunque el endpoint de signup responda en 200 ms. Si llega al segundo 110, el usuario ve un fallo, pero el correo atrasado puede activar un enlace que ya no corresponde a su intento.&lt;/p&gt;

&lt;p&gt;La diferencia importa. Un error de aplicación pide una corrección de código; una demora de cola pide observabilidad, capacidad o una política distinta de espera. Mezclar ambas cosas produce alertas ruidosas y runbooks poco utiles.&lt;/p&gt;

&lt;p&gt;También conviene distinguir entre correo entregado y correo observado. El proveedor puede aceptar un mensaje mientras el adaptador todavía no ha actualizado su índice. En una prueba con &lt;code&gt;temp mail email&lt;/code&gt;, la dirección temporal es solo el destino: no reemplaza un identificador de ejecución ni una métrica de entrega.&lt;/p&gt;

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

&lt;p&gt;Empieza con un contrato sencillo. Cada etapa debe registrar &lt;code&gt;started_at&lt;/code&gt;, &lt;code&gt;finished_at&lt;/code&gt; y un &lt;code&gt;run_id&lt;/code&gt; estable. El contrato podría verse así:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;signup.request       0s  - 10s
mail.enqueue         0s  - 20s
mail.delivery        0s  - 40s
mail.observation     0s  - 70s
verification.submit  0s  - 90s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Los rangos no son una promesa universal. Son una hipótesis que se valida con ejecuciones reales. Si el adaptador consume normalmente en 12 segundos, asignarle 40 segundos puede ocultar una regresión. Si una región tiene una cola más lenta, reducir el timeout del test solo crea falsos negativos.&lt;/p&gt;

&lt;p&gt;Usa el mismo &lt;code&gt;run_id&lt;/code&gt; en la solicitud, en los metadatos del mensaje y en los logs del consumidor. No incluyas la dirección completa en nombres de pods o etiquetas; termina apareciendo en eventos, métricas y artefactos. Una identificación corta y opaca es mas fácil de buscar y reduce exposición innecesaria.&lt;/p&gt;

&lt;p&gt;Para comprobar la relación entre red y entrega, ayuda tener un &lt;a href="https://dev.to/alexcarteruk/kubernetes-correos-utiles-en-cambios-de-red"&gt;runbook de correos durante cambios de red&lt;/a&gt;. El objetivo no es atribuir cada demora a la red, sino comprobar si el cambio afectó una etapa concreta del presupuesto.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instrumenta la ejecución en Kubernetes
&lt;/h2&gt;

&lt;p&gt;Cada ejecución de CI debería tener un namespace o al menos un conjunto de etiquetas propio. El job del test puede guardar el estado antes de limpiar los recursos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"email-check-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CI_RUN_ID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
kubectl create namespace &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$namespace&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
kubectl label namespace &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$namespace&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;owner&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ci run-id&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CI_RUN_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

cleanup&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$namespace&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; get pods,events &lt;span class="nt"&gt;-o&lt;/span&gt; wide &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; artifacts/k8s-state.txt &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
  &lt;/span&gt;kubectl delete namespace &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$namespace&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--ignore-not-found&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;trap &lt;/span&gt;cleanup EXIT

kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$namespace&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; apply &lt;span class="nt"&gt;-f&lt;/span&gt; email-fixture/
kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$namespace&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nb"&gt;wait&lt;/span&gt; &lt;span class="nt"&gt;--for&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;condition&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Ready pod &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mail-adapter &lt;span class="nt"&gt;--timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;120s
./run-email-test &lt;span class="nt"&gt;--run-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CI_RUN_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El artefacto debe incluir el tiempo de cada etapa, el estado del pod y el motivo del último reintento. No guardes tokens de verificación sin redacción. Si se necesita el mensaje completo para investigar, usa un artefacto de corta duración y limita quién puede leerlo.&lt;/p&gt;

&lt;p&gt;El aislamiento evita otro fallo común: una ejecución lenta lee el mensaje creado por una ejecución anterior. Para mantener el sistema limpio, asigna un TTL al namespace y borra el buzón de prueba junto con los recursos. La limpieza tiene que ser idempotente, porque una cancelación puede dejar el namespace a medias.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separa una alerta útil de una alerta fantasma
&lt;/h2&gt;

&lt;p&gt;Una alerta de producción no debería dispararse por un solo test lento. Primero define qué mide cada señal:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Error de solicitud:&lt;/strong&gt; la aplicación no pudo crear el intento.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retraso de cola:&lt;/strong&gt; el mensaje no salió a tiempo del productor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retraso de entrega:&lt;/strong&gt; salió, pero no fue aceptado en el plazo esperado.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retraso de observación:&lt;/strong&gt; el mensaje existe, pero el adaptador no lo encontró.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verificación vencida:&lt;/strong&gt; el enlace o el intento ya no era válido.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;La alerta puede combinar percentiles de latencia con una tasa mínima de ejecuciones. Una sola prueba con &lt;code&gt;tepm mail com&lt;/code&gt; o &lt;code&gt;temp gamil com&lt;/code&gt; escrito por accidente no debería cambiar el estado global del servicio. En cambio, varias ejecuciones con el mismo tramo lento sí merecen una investigación.&lt;/p&gt;

&lt;p&gt;Para los tests de onboarding, es útil &lt;a href="https://dev.to/hannahdev56/como-probar-correos-de-onboarding-en-un-saas-sin-ensuciar-metricas"&gt;mantener las métricas limpias al probar correos&lt;/a&gt;. Las ejecuciones sintéticas deben etiquetarse de forma que puedan excluirse de ciertos indicadores de negocio, pero no de los indicadores técnicos de entrega.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist para el próximo incidente
&lt;/h2&gt;

&lt;p&gt;Antes de cerrar un cambio en el flujo de email, revisa:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;¿Cada ejecución tiene un &lt;code&gt;run_id&lt;/code&gt; que viaja por todas las etapas?&lt;/li&gt;
&lt;li&gt;¿El presupuesto separa cola, entrega y observación?&lt;/li&gt;
&lt;li&gt;¿El test evita aceptar un mensaje de otra ejecución?&lt;/li&gt;
&lt;li&gt;¿Los logs redaccionan tokens y direcciones completas?&lt;/li&gt;
&lt;li&gt;¿El namespace y el buzón tienen una expiración definida?&lt;/li&gt;
&lt;li&gt;¿El artefacto conserva evidencia antes de la limpieza?&lt;/li&gt;
&lt;li&gt;¿Las alertas necesitan una cantidad mínima de muestras?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;El resultado buscado no es que todos los correos lleguen instantáneamente. Es que un retraso sea explicable. Con un presupuesto pequeño, etiquetas consistentes y limpieza automática, Kubernetes deja de ser una caja negra para las pruebas de email. Y SRE puede decidir si el problema pide capacidad, un timeout mejor o una corrección en la aplicación, en vez de perseguir otra alerta fantasma.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>sre</category>
      <category>devops</category>
      <category>testing</category>
    </item>
    <item>
      <title>Kubernetes: un runbook para emails lentos</title>
      <dc:creator>Alex Carter</dc:creator>
      <pubDate>Thu, 01 Oct 2026 02:24:31 +0000</pubDate>
      <link>https://dev.to/alexcarteruk/kubernetes-un-runbook-para-emails-lentos-1b0g</link>
      <guid>https://dev.to/alexcarteruk/kubernetes-un-runbook-para-emails-lentos-1b0g</guid>
      <description>&lt;p&gt;En Kubernetes, una alerta de email lenta se parece demasiado a un fallo completo. El pod de la aplicación termina correctamente, el proveedor SMTP responde, pero el mensaje de verificación tarda dos minutos en llegar. Para la persona que está probando el flujo, la experiencia es la misma: pulsa «reenviar» y acaba creando varios intentos.&lt;/p&gt;

&lt;p&gt;En una guardia aprendí a no empezar cambiando timeouts. Primero hay que separar tres tiempos: el que tarda la aplicación en aceptar el evento, el que tarda el proveedor en aceptar el mensaje y el que tarda el mensaje en aparecer en el buzón de prueba. Esa separación convierte un síntoma difuso en una investigación de SRE que se puede repetir.&lt;/p&gt;

&lt;h2&gt;
  
  
  El síntoma no siempre es un fallo de SMTP
&lt;/h2&gt;

&lt;p&gt;La métrica &lt;code&gt;email_verification_failed&lt;/code&gt; suele mezclar errores muy distintos. Un &lt;code&gt;5xx&lt;/code&gt; inmediato del proveedor no tiene el mismo significado que un &lt;code&gt;202 Accepted&lt;/code&gt; seguido de una entrega lenta. Tampoco es igual que el worker haya perdido el lease antes de publicar el mensaje.&lt;/p&gt;

&lt;p&gt;Conviene registrar un identificador de intento y cuatro marcas de tiempo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;attempt_created_at
provider_accepted_at
message_observed_at
attempt_finished_at
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No guardaría el asunto completo ni la dirección real en logs de producción. Para una prueba sintética basta con un identificador hasheado, el nombre del entorno y el resultado. El objetivo es observar la ruta, no coleccionar datos que luego nadie revisa.&lt;/p&gt;

&lt;p&gt;Si el proveedor acepta en 300 ms pero &lt;code&gt;message_observed_at&lt;/code&gt; llega 90 segundos después, el problema probablemente está en la entrega, el buzón o la forma de consultar. Si la aceptación ya tarda 20 segundos, hay que mirar la cola del worker, la resolución DNS, la conexión saliente y los límites del proveedor.&lt;/p&gt;

&lt;p&gt;Para profundizar en una investigación parecida, resulta útil &lt;a href="https://dev.to/alexcarteruk/sre-aislar-fallos-de-email-en-kubernetes-3470"&gt;aislar fallos de email en Kubernetes&lt;/a&gt; antes de tocar la configuración del despliegue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué evidencia recoger primero
&lt;/h2&gt;

&lt;p&gt;Antes de reiniciar pods, captura una pequeña fotografía del incidente:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Versión y entorno:&lt;/strong&gt; namespace, imagen desplegada y configuración efectiva (sin secretos).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ruta del evento:&lt;/strong&gt; nombre del worker, cola, proveedor y código de respuesta.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latencias separadas:&lt;/strong&gt; aplicación, aceptación SMTP y aparición del mensaje.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tamaño de la cola:&lt;/strong&gt; edad del mensaje más antiguo y número de reintentos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ventana temporal:&lt;/strong&gt; cuándo empezó y si coincide con un cambio o una saturación.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Un error frecuente es mirar solamente los logs del pod que envía. Eso deja fuera los mensajes que sí fueron aceptados pero nunca fueron visibles para la prueba. Tambien he visto dashboards que muestran la media; la media se ve bien aunque un porcentaje pequeño de usuarios espere demasiado. Usa p95 y p99 cuando la experiencia dependa de un enlace con caducidad.&lt;/p&gt;

&lt;p&gt;La comprobación sintética debe publicar un mensaje con una dirección de prueba aislada y consultar con una frecuencia limitada. Un &lt;a href="https://tempmailso.com" rel="noopener noreferrer"&gt;generador de correo desechable&lt;/a&gt; puede servir para crear datos efímeros en un entorno de QA, pero no debe convertirse en almacenamiento permanente ni en una dependencia oculta del camino crítico. En otro caso, un &lt;a href="https://tempmailso.com" rel="noopener noreferrer"&gt;generador de correo temporal&lt;/a&gt; es útil para verificar la entrega sin reutilizar una bandeja personal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Un runbook en cuatro pasos
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Confirma si el evento salió de Kubernetes
&lt;/h3&gt;

&lt;p&gt;Busca el &lt;code&gt;attempt_id&lt;/code&gt; en el worker y en la cola. Si no existe una aceptación del proveedor, el fallo está antes o durante la salida. Revisa el ServiceAccount, las políticas de red y el límite de conexiones, pero evita hacer tres cambios a la vez.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Compara aceptación y entrega
&lt;/h3&gt;

&lt;p&gt;Si existe un &lt;code&gt;provider_message_id&lt;/code&gt;, correlaciónalo con la consulta de la bandeja sintética. Una diferencia pequeña entre ambos puntos indica que el worker funciona; una diferencia grande apunta al proveedor, al buzón o al polling. Este corte ahorra horas de mirar métricas irrelevantes.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Revisa reintentos e idempotencia
&lt;/h3&gt;

&lt;p&gt;Un timeout de lectura no demuestra que el mensaje no fue aceptado. Si el worker reintenta sin una clave idempotente, puede crear duplicados y hacer que el test parezca todavía más lento. Marca el intento como «resultado desconocido» cuando no puedas saber si el proveedor lo recibió; luego resuelve ese estado con una consulta o reconciliación.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Decide con un límite explícito
&lt;/h3&gt;

&lt;p&gt;Define qué significa «lento» para ese flujo. Por ejemplo, si el objetivo de la prueba es observar una verificación de dos minutos, no dispares un alerta crítica a los diez segundos por una sola muestra. La regla debe considerar ventana, percentil y cantidad mínima de muestras, segun el tráfico esperado, no solo un booleano.&lt;/p&gt;

&lt;h2&gt;
  
  
  Diseñar alertas que no despierten por ruido
&lt;/h2&gt;

&lt;p&gt;Una alerta útil debe explicar qué hacer. «Email failed» no es un runbook. Un mensaje mejor podría decir: «p99 de entrega sintética sobre 120 s durante 10 minutos; aceptación del proveedor normal; revisar cola de entrega». Esa frase ya contiene la hipótesis inicial.&lt;/p&gt;

&lt;p&gt;Separa tres niveles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Informativo:&lt;/strong&gt; una muestra lenta, sin impacto confirmado.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Advertencia:&lt;/strong&gt; p95 elevado o cola creciendo durante una ventana estable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Crítico:&lt;/strong&gt; aceptación fallida, expiración de verificaciones o impacto confirmado en usuarios.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Las &lt;a href="https://dev.to/alexcarteruk/kubernetes-pruebas-sinteticas-sin-ruido-en-alertas-196l"&gt;pruebas sintéticas sin ruido en las alertas&lt;/a&gt; ayudan a decidir qué muestras deben abrir un incidente y cuales solo deben alimentar un dashboard. La prueba no necesita correr cada diez segundos si el flujo real dura varios minutos; esa frecuencia solo añade coste y ruido.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;¿Tenemos un &lt;code&gt;attempt_id&lt;/code&gt; que atraviesa aplicación, worker y proveedor?&lt;/li&gt;
&lt;li&gt;¿Separamos aceptación de entrega y de lectura del buzón?&lt;/li&gt;
&lt;li&gt;¿Medimos p95 o p99 además de la media?&lt;/li&gt;
&lt;li&gt;¿Podemos distinguir un timeout desconocido de un rechazo confirmado?&lt;/li&gt;
&lt;li&gt;¿La prueba sintética elimina sus mensajes y sus identificadores después?&lt;/li&gt;
&lt;li&gt;¿El runbook indica una acción concreta para cada nivel de alerta?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;El detalle mas importante es conservar la evidencia suficiente para decidir. Kubernetes no arregla por sí solo una ruta de email mal observada, y reiniciar el deployment casi nunca explica dónde se perdió el tiempo. Con tres marcas de tiempo, una cola visible y un límite razonable, el incidente deja de ser «el correo no llega» y pasa a ser una hipótesis verificable. A veces esta diferencia esta en un solo campo del evento, pero cambia por completo la respuesta.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>sre</category>
      <category>devops</category>
      <category>testing</category>
    </item>
    <item>
      <title>SRE: aislar fallos de email en Kubernetes</title>
      <dc:creator>Alex Carter</dc:creator>
      <pubDate>Wed, 30 Sep 2026 05:23:44 +0000</pubDate>
      <link>https://dev.to/alexcarteruk/sre-aislar-fallos-de-email-en-kubernetes-3470</link>
      <guid>https://dev.to/alexcarteruk/sre-aislar-fallos-de-email-en-kubernetes-3470</guid>
      <description>&lt;h1&gt;
  
  
  SRE: aislar fallos de email en Kubernetes
&lt;/h1&gt;

&lt;p&gt;Cuando un smoke test de registro falla en Kubernetes, el primer impulso suele ser culpar al clúster. A veces el problema está en el proveedor de correo, en un buzón temporal que expiró o en un selector demasiado estricto. Otras veces sí es un &lt;code&gt;NetworkPolicy&lt;/code&gt; que bloqueó la salida.&lt;/p&gt;

&lt;p&gt;La diferencia importa. Si se escala el Deployment cuando el fallo real está en el inbox de prueba, se añade ruido y se gasta tiempo del equipo. En una guardia aprendí a tratar cada prueba de email como un pequeño contrato operativo: debe decir qué espera, durante cuanto tiempo lo espera y qué evidencia deja si no ocurre.&lt;/p&gt;

&lt;h2&gt;
  
  
  El síntoma: correo fallido o clúster fallido
&lt;/h2&gt;

&lt;p&gt;El test normalmente tiene tres partes: crea un usuario, espera el mensaje y confirma un enlace o un código. Un resultado rojo solo nos dice que una de esas fases no terminó. No explica si falló la aplicación, la red, el proveedor o la lectura del buzón.&lt;/p&gt;

&lt;p&gt;Antes de tocar Kubernetes, separo las señales:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;La API devolvió un &lt;code&gt;202&lt;/code&gt; o un error de entrega.&lt;/li&gt;
&lt;li&gt;El worker tomó el evento de email y lo confirmó.&lt;/li&gt;
&lt;li&gt;La red permitió la conexión de salida.&lt;/li&gt;
&lt;li&gt;El buzón recibió un mensaje nuevo, no uno viejo.&lt;/li&gt;
&lt;li&gt;El test encontró el correo correcto y lo consumió una sola vez.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Este modelo también funciona para fixtures de datos. Las &lt;a href="https://dev.to/hannahdev56/saas-seed-lists-para-onboarding-4nfl"&gt;listas semilla para un onboarding SaaS&lt;/a&gt; ayudan a que el caso de prueba empiece con entradas conocidas, pero no sustituyen la observabilidad de la entrega.&lt;/p&gt;

&lt;h2&gt;
  
  
  Un contrato de prueba pequeño y observable
&lt;/h2&gt;

&lt;p&gt;Un smoke test no necesita conocer todos los detalles internos. Sí necesita un identificador único por ejecución, un destinatario aislado y un límite de tiempo explícito. Una dirección temporal o un buzón temporal puede servir para probar un flujo controlado, siempre que el entorno sea de pruebas y no se envíen datos sensibles.&lt;/p&gt;

&lt;p&gt;El contrato puede verse así:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test_id: signup-20260930-0522-8f31
recipient: único para esta ejecución
expected_subject: Confirmación de cuenta
deadline: 90 segundos
cleanup: eliminar usuario y fixture al terminar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El &lt;code&gt;test_id&lt;/code&gt; debe viajar en los logs del servicio de signup, del worker y del consumidor de correo. No hace falta imprimir el contenido completo del mensaje. Basta con registrar el hash del mensaje o un identificador de entrega, el estado y la hora. Un detalle que parece menor: el texto de prueba &lt;code&gt;tem email&lt;/code&gt; puede aparecer en fixtures antiguos; no debe convertirse en una condición especial del código.&lt;/p&gt;

&lt;h2&gt;
  
  
  Aislamiento con namespaces y permisos mínimos
&lt;/h2&gt;

&lt;p&gt;Para investigar un fallo, un entorno de prueba debe tener límites claros. Un namespace separado evita mezclar los pods del smoke test con los workers de producción. Un &lt;code&gt;ServiceAccount&lt;/code&gt; dedicado permite saber qué identidad hizo cada llamada y facilita retirar permisos después.&lt;/p&gt;

&lt;p&gt;La configuración mínima suele incluir:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Un &lt;code&gt;NetworkPolicy&lt;/code&gt; que permita solo el endpoint de correo y los servicios necesarios.&lt;/li&gt;
&lt;li&gt;Un &lt;code&gt;Secret&lt;/code&gt; montado únicamente en el worker que necesita autenticarse.&lt;/li&gt;
&lt;li&gt;Un &lt;code&gt;ResourceQuota&lt;/code&gt; para que una repetición accidental no consuma todo el nodo.&lt;/li&gt;
&lt;li&gt;Un &lt;code&gt;Job&lt;/code&gt; con &lt;code&gt;backoffLimit&lt;/code&gt; acotado y una política de limpieza definida.&lt;/li&gt;
&lt;li&gt;Un nombre de ejecución que conecte Kubernetes, CI y el buzón.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No conviene dar acceso de lectura a todos los secretos “para depurar más rápido”. Ese atajo hace más difícil distinguir una causa de una exposición. Si el test necesita un proveedor externo, prefiero una credencial de alcance pequeño y rotación automática. La revisión de permisos queda entonces dentro del mismo ticket que la prueba.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evidencia útil para SRE
&lt;/h2&gt;

&lt;p&gt;Cuando el test falla, guarda evidencia pequeña y suficiente: nombre del Job, razón de terminación, eventos del pod, latencia de cada etapa y estado de entrega. Evita guardar tokens, cuerpos de correo o URLs con credenciales.&lt;/p&gt;

&lt;p&gt;También conviene separar tres tiempos: publicación del evento, aceptación por el worker y aparición en el buzón. Si solo se mide el tiempo total, una regresión de 20 segundos puede parecer un simple timeout. Con las tres medidas, el equipo ve donde se desplazó la latencia.&lt;/p&gt;

&lt;p&gt;En el dashboard, una métrica como &lt;code&gt;email_smoke_test_failure_total&lt;/code&gt; debe tener etiquetas de baja cardinalidad: &lt;code&gt;stage&lt;/code&gt;, &lt;code&gt;provider&lt;/code&gt; y &lt;code&gt;reason&lt;/code&gt;. No uses el email completo, el &lt;code&gt;test_id&lt;/code&gt; o el nombre de usuario como etiqueta. Eso llena el sistema de métricas y hace más dificil encontrar la señal importante.&lt;/p&gt;

&lt;p&gt;La interfaz de resultados también debe ser honesta. Si una consola tarda en mostrar el correo, los &lt;a href="https://dev.to/silviutech/react-loaders-honestos-para-flujos-lentos-ggk"&gt;estados honestos para flujos lentos&lt;/a&gt; son preferibles a mostrar “enviado” cuando todavía solo se aceptó la solicitud.&lt;/p&gt;

&lt;h2&gt;
  
  
  Errores comunes durante el incidente
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Reiniciar todo el Deployment antes de comprobar el estado del proveedor.&lt;/li&gt;
&lt;li&gt;Reutilizar el mismo buzón para varios Jobs concurrentes.&lt;/li&gt;
&lt;li&gt;Buscar por asunto sin verificar el identificador de la ejecución.&lt;/li&gt;
&lt;li&gt;Aumentar el timeout sin medir cada etapa.&lt;/li&gt;
&lt;li&gt;Dejar Jobs y buzones vivos después del test.&lt;/li&gt;
&lt;li&gt;Copiar el contenido del mensaje en un canal de incidente.&lt;/li&gt;
&lt;li&gt;Confundir &lt;code&gt;tem email&lt;/code&gt; con una dirección legítima solo porque el test antiguo la usaba.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;El más costoso suele ser el segundo. Un correo de otra ejecución puede hacer que el test pase mientras una versión rota sigue desplegándose. La prueba verde resulta entonces más peligrosa que una roja.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist antes de cerrar el ticket
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] ¿El fallo está asignado a aplicación, red, proveedor o lectura del buzón?&lt;/li&gt;
&lt;li&gt;[ ] ¿Cada ejecución tiene un destinatario y un &lt;code&gt;test_id&lt;/code&gt; único?&lt;/li&gt;
&lt;li&gt;[ ] ¿Los logs conectan API, worker, Job y recepción?&lt;/li&gt;
&lt;li&gt;[ ] ¿El namespace tiene permisos y cuota mínimos?&lt;/li&gt;
&lt;li&gt;[ ] ¿Se borraron usuario, Job y fixture al terminar?&lt;/li&gt;
&lt;li&gt;[ ] ¿La evidencia excluye secretos y contenido sensible?&lt;/li&gt;
&lt;li&gt;[ ] ¿El cambio mejora una señal medible, no solo el timeout?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;El objetivo de un smoke test de email no es demostrar que Kubernetes funciona. Es producir una señal confiable sobre un flujo concreto. Cuando el contrato es pequeño, el aislamiento es real y la evidencia está conectada, el equipo SRE puede corregir la causa sin convertir cada correo perdido en un incidente de todo el clúster.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>sre</category>
      <category>devops</category>
      <category>testing</category>
    </item>
    <item>
      <title>Terraform: una cola segura para alertas de prueba</title>
      <dc:creator>Alex Carter</dc:creator>
      <pubDate>Tue, 29 Sep 2026 08:23:35 +0000</pubDate>
      <link>https://dev.to/alexcarteruk/terraform-una-cola-segura-para-alertas-de-prueba-4hg8</link>
      <guid>https://dev.to/alexcarteruk/terraform-una-cola-segura-para-alertas-de-prueba-4hg8</guid>
      <description>&lt;p&gt;Probar una alerta de infraestructura suele parecer fácil: cambiar un umbral, ejecutar &lt;code&gt;terraform apply&lt;/code&gt; y esperar un mensaje. En la práctica, el mensaje llega mas tarde, llega duplicado o no llega nunca. Luego alguien mira el panel de métricas, otro revisa el proveedor de correo y la guardia termina discutiendo si el problema fue Terraform o la notificación.&lt;/p&gt;

&lt;p&gt;En equipos SRE me ha resultado más útil tratar una alerta de prueba como una pequeña cadena de entrega. El objetivo no es solo comprobar que el recurso existe. Hay que demostrar que el evento se generó, que pasó por la ruta correcta y que una persona o un sistema pudo confirmarlo.&lt;/p&gt;

&lt;h2&gt;
  
  
  El síntoma: una alerta que llega tarde
&lt;/h2&gt;

&lt;p&gt;Imaginemos un cambio sencillo en una regla de CPU. El plan se revisa bien y el &lt;code&gt;apply&lt;/code&gt; termina sin errores. Sin embargo, el aviso aparece diez minutos después, cuando el valor ya volvió a la normalidad. El resultado visible es confuso: ¿la regla estaba mal, el canal estaba lento o el test usó un dato viejo?&lt;/p&gt;

&lt;p&gt;El error habitual es comprobar solo el último salto. Si vemos un correo, marcamos la prueba como correcta. Si no lo vemos, aumentamos el timeout y repetimos. Eso tapa la causa real y vuelve más caro cada incidente.&lt;/p&gt;

&lt;p&gt;Una buena prueba deja tres evidencias separadas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Generación:&lt;/strong&gt; la métrica o el evento cruzó el umbral esperado.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Entrega:&lt;/strong&gt; el proveedor de alertas aceptó y envió la notificación.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confirmación:&lt;/strong&gt; el receptor encontró el mensaje correcto y pudo identificarlo.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Esta separación permite saber que parte falló. Tambien evita que un mensaje antiguo convierta una prueba roja en verde.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separar generación, entrega y confirmación
&lt;/h2&gt;

&lt;p&gt;Antes de tocar HCL, definiría un identificador de prueba. Puede ser un valor corto como &lt;code&gt;tf-alert-20260929-01&lt;/code&gt;, incluido en las etiquetas del evento o en el asunto de la notificación. No hace falta meter datos privados; solo un correlativo que haga la evidencia buscable.&lt;/p&gt;

&lt;p&gt;Después, guardaría un registro por cada etapa:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test_id=tf-alert-20260929-01
generated_at=2026-09-29T08:20:00Z
delivery_status=accepted
delivered_at=2026-09-29T08:20:18Z
confirmed_at=2026-09-29T08:20:24Z
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El registro no tiene que vivir en Terraform. Terraform define la regla, el canal y las etiquetas; el script de verificación puede consultar el sistema de alertas y producir un recibo para CI. Así el código de infraestructura permanece declarativo y el test se concentra en el comportamiento.&lt;/p&gt;

&lt;p&gt;Para el buzón de prueba conviene usar una dirección aislada por ejecución. Un servicio como &lt;a href="https://tempmailso.com" rel="noopener noreferrer"&gt;tempmail so&lt;/a&gt; puede servir para una comprobación temporal, siempre que el contenido no tenga secretos ni datos de clientes. Para una prueba de producción, usaría un canal controlado por la organización y una política de retención explícita.&lt;/p&gt;

&lt;h2&gt;
  
  
  Una cola de prueba dentro del plan
&lt;/h2&gt;

&lt;p&gt;No recomiendo cambiar el umbral real cada vez que alguien quiere verificar el canal. Es más seguro disponer de una regla o un destino de prueba claramente etiquetado. El cambio debe poder identificarse en el plan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"example_alert_rule"&lt;/span&gt; &lt;span class="s2"&gt;"delivery_probe"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sre-delivery-probe"&lt;/span&gt;
  &lt;span class="nx"&gt;threshold&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="nx"&gt;labels&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"test"&lt;/span&gt;
    &lt;span class="nx"&gt;test_id&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tf-alert-20260929-01"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El proveedor concreto cambia, pero la idea se mantiene: una prueba pequeña, reversible y visible. En &lt;a href="https://dev.to/hannahdev56/saas-emails-de-trial-con-senales-utiles-48i5"&gt;señales útiles en emails de prueba&lt;/a&gt; se explica una preocupación parecida desde el lado del producto: un mensaje tiene más valor cuando lleva contexto que cuando solo dice “llegó”.&lt;/p&gt;

&lt;p&gt;También es importante que el test no dependa de un &lt;code&gt;sleep&lt;/code&gt; fijo. Mejor hacer polling con un límite total, guardar cada intento y detenerse cuando el &lt;code&gt;test_id&lt;/code&gt; aparece. Si el sistema usa una cola, registrar el momento de aceptación ayuda a distinguir retraso de entrega de retraso de consumo.&lt;/p&gt;

&lt;p&gt;Los logs queda más utiles cuando incluyen la causa de salida: &lt;code&gt;confirmed&lt;/code&gt;, &lt;code&gt;timeout&lt;/code&gt;, &lt;code&gt;wrong-recipient&lt;/code&gt; o &lt;code&gt;stale-message&lt;/code&gt;. Un texto generico como “email no encontrado” obliga a repetir la investigación durante la guardia.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cómo leer el resultado en una guardia
&lt;/h2&gt;

&lt;p&gt;Cuando la prueba falla, el recibo debería responder cuatro preguntas sin abrir cinco paneles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;¿Qué cambio de Terraform activó el test?&lt;/li&gt;
&lt;li&gt;¿Qué &lt;code&gt;test_id&lt;/code&gt; esperaba el verificador?&lt;/li&gt;
&lt;li&gt;¿En qué etapa dejó de avanzar la cadena?&lt;/li&gt;
&lt;li&gt;¿Cuál fue el último timestamp observado?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Si la generación existe pero la entrega no fue aceptada, revisaría la integración o sus permisos. Si la entrega fue aceptada pero no hay confirmación, miraría el buzón, el filtro y la latencia de lectura. Si aparece un mensaje con otro identificador, probablemente hay contaminación entre ejecuciones. Depronto el sistema está sano, pero la prueba no está aislada.&lt;/p&gt;

&lt;p&gt;Para no confundir al equipo, la salida de CI debe incluir un enlace al recibo y el estado final. Un fallo con evidencia es accionable; un fallo que solo dice “timeout” es otra tarea de detective.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  ¿Debo ejecutar estas pruebas en cada &lt;code&gt;apply&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;No necesariamente. En entornos tranquilos puede bastar con una ejecución programada o después de cambios en el canal. En incidentes, conviene lanzarla manualmente con un &lt;code&gt;test_id&lt;/code&gt; nuevo.&lt;/p&gt;

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

&lt;p&gt;Solo si cada ejecución tiene un identificador único y el verificador descarta mensajes viejos. Un buzón compartido sin limpieza hace que los resultados sean dificiles de interpretar.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Qué hago con errores como &lt;code&gt;dummy e mail&lt;/code&gt; o &lt;code&gt;tem email&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;Trátalos como texto de entrada inválido, no como señal de entrega. Los términos raros pueden aparecer en datos de prueba, pero no deben convertirse en reglas de enrutamiento ni en anclas de enlaces.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;[ ] La regla de prueba está separada de la alerta de producción.&lt;/li&gt;
&lt;li&gt;[ ] Cada ejecución tiene un &lt;code&gt;test_id&lt;/code&gt; único.&lt;/li&gt;
&lt;li&gt;[ ] Generación, entrega y confirmación producen evidencia distinta.&lt;/li&gt;
&lt;li&gt;[ ] El polling tiene límite total y conserva sus intentos.&lt;/li&gt;
&lt;li&gt;[ ] El receptor no reutiliza mensajes viejos.&lt;/li&gt;
&lt;li&gt;[ ] El recibo indica la etapa exacta del fallo.&lt;/li&gt;
&lt;li&gt;[ ] El cambio se puede revertir sin tocar el tráfico real.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;La prueba de una alerta no termina cuando Terraform dice que el recurso fue creado. Termina cuando podemos seguir el evento desde la condición inicial hasta una confirmación inequívoca. Separar esas etapas reduce los falsos positivos, hace más corta la investigación y deja un runbook que otro compañero puede seguir sin adivinar.&lt;/p&gt;

&lt;p&gt;Si el equipo ya tiene &lt;a href="https://dev.to/alexcarteruk/kubernetes-pruebas-sinteticas-sin-ruido-en-alertas-196l"&gt;pruebas sintéticas sin ruido en alertas&lt;/a&gt;, el siguiente paso natural es darles un recibo pequeño y reutilizable. No hace falta una plataforma nueva: basta un identificador, polling con límites y evidencia que cuente la historia completa.&lt;/p&gt;

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