<?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: Silviu Technology</title>
    <description>The latest articles on DEV Community by Silviu Technology (@silviutech).</description>
    <link>https://dev.to/silviutech</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%2F4013497%2F7d1c82f4-cd78-412d-908d-981280990b57.png</url>
      <title>DEV Community: Silviu Technology</title>
      <link>https://dev.to/silviutech</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/silviutech"/>
    <language>en</language>
    <item>
      <title>Cómo aislar emails de agentes LLM en flujos automatizados sin perder trazabilidad</title>
      <dc:creator>Silviu Technology</dc:creator>
      <pubDate>Sat, 04 Jul 2026 00:55:42 +0000</pubDate>
      <link>https://dev.to/silviutech/como-aislar-emails-de-agentes-llm-en-flujos-automatizados-sin-perder-trazabilidad-26ac</link>
      <guid>https://dev.to/silviutech/como-aislar-emails-de-agentes-llm-en-flujos-automatizados-sin-perder-trazabilidad-26ac</guid>
      <description>&lt;p&gt;Cuando un agente LLM empieza a abrir tickets, disparar aprobaciones o enviar resúmenes por correo, el problema ya no es solo "si el prompt funciona". El sistema completo pasa a depender de tres capas distintas: decisión, ejecución y verificación. Si esas capas quedan pegadas, el equipo termina mirando una bandeja y adivinando qué hizo realmente el agente.&lt;/p&gt;

&lt;p&gt;Ese patrón aparece mucho en workflows de Automation porque el correo parece el último paso, cuando en realidad es el primer lugar donde se ve el fallo. Un agente puede clasificar bien una solicitud y aun así mandar el mensaje al destinatario equivocado, repetir un envío o usar un enlace vencido. Ahi nace la necesidad de aislar pruebas y trazas, no solo prompts.&lt;/p&gt;

&lt;p&gt;También influye algo bastante humano: cuando alguien del equipo busca temp mail mail o disposable mail address, casi nunca está buscando una herramienta "de growth". Está buscando una superficie limpia para observar el sistema. Quiere comprobar qué produjo el agente sin ruido de mensajes viejos, y eso es totalmente razonable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por qué este problema aparece cuando un LLM toca correo
&lt;/h2&gt;

&lt;p&gt;En un flujo clásico, una API recibe un evento, lo pasa a una cola y un worker envía el email. Con un LLM en medio, aparece otra fase: una decisión probabilística que puede cambiar asunto, prioridad, template o incluso si el correo debe salir. Esa fase no es mala, pero sí obliga a separar responsabilidades con más cuidado.&lt;/p&gt;

&lt;p&gt;Yo suelo pensarlo como un diagrama en palabras:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;El evento de negocio entra.&lt;/li&gt;
&lt;li&gt;Un componente determinista prepara contexto y reglas.&lt;/li&gt;
&lt;li&gt;El LLM decide o redacta dentro de límites claros.&lt;/li&gt;
&lt;li&gt;Un ejecutor transforma esa salida en un comando verificable.&lt;/li&gt;
&lt;li&gt;El sistema de correo entrega el mensaje.&lt;/li&gt;
&lt;li&gt;La prueba confirma contenido, destinatario y efecto final.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Si mezclas esos pasos en un solo test, el fallo se vuelve opaco. No sabes si el error está en el modelo, en la política de herramientas, en el worker o en la bandeja usada para validar. Por eso me gusta medir cada borde del flujo con un &lt;code&gt;trace_id&lt;/code&gt; compartido desde el evento inicial hasta el clic final.&lt;/p&gt;

&lt;h2&gt;
  
  
  Un diseño de flujo que separa decision, envio y verificacion
&lt;/h2&gt;

&lt;p&gt;El diseño más estable no intenta "testear la inteligencia" de una sola vez. Divide el sistema en contratos pequeños:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Contrato de entrada: qué datos puede usar el agente y qué acción está autorizado a pedir.&lt;/li&gt;
&lt;li&gt;Contrato de ejecución: cómo se convierte esa acción en un envío de correo concreto.&lt;/li&gt;
&lt;li&gt;Contrato de observabilidad: cómo enlazas logs, mensaje recibido y estado final del sistema.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;En este punto ayuda mucho copiar buenas prácticas de guías cercanas, como &lt;a href="https://dev.to/silviutech/como-probar-emails-transaccionales-en-fastapi-sin-mezclar-bandejas-ni-eventos-5f0m"&gt;probar emails transaccionales en FastAPI&lt;/a&gt;. El stack puede cambiar, pero la idea de fondo sigue igual: una bandeja por escenario, una intención por prueba y una forma simple de reconstruir la secuencia.&lt;/p&gt;

&lt;p&gt;Para agentes LLM, yo dejaría el correo fuera del prompt libre. El modelo puede sugerir &lt;code&gt;send_followup_email&lt;/code&gt;, pero no debería decidir directamente headers, destinatarios alternativos o políticas de reintento. Esa traducción tiene que vivir en código determinista. Suena menos "mágico", aunque en la practica baja bastante el riesgo operacional.&lt;/p&gt;

&lt;p&gt;Una implementación minima puede verse así:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_agent_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;trace_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;send_followup_email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reason&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unsupported_action&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;template&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;followup_v2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trace_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;trace_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;dispatch_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No resuelve todo, pero deja una frontera clara: el LLM propone, el sistema valida y el ejecutor envía. Ese pequeño detalle evita un monton de debugging confuso despues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué observar para no confundir un fallo del agente con un fallo del correo
&lt;/h2&gt;

&lt;p&gt;La observabilidad útil aquí no es enorme; solo tiene que unir los puntos correctos. Yo revisaría cuatro señales:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;La decisión generada por el agente y el contexto que la produjo.&lt;/li&gt;
&lt;li&gt;El comando final que se entregó al ejecutor de correo.&lt;/li&gt;
&lt;li&gt;El mensaje recibido en una bandeja aislada.&lt;/li&gt;
&lt;li&gt;El efecto final tras abrir el enlace o confirmar la acción.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cuando falta una de esas piezas, el equipo se inventa explicaciones. "Seguro fue el modelo". "Seguro fue el proveedor". A veces sí, pero muchas veces es una condición de carrera o una bandeja compartida. En entornos donde hay varios flujos de alta, renovación o trial, el ruido se parece bastante a lo que se cuenta en estas &lt;a href="https://dev.to/hannahdev56/como-probar-correos-de-onboarding-en-un-saas-sin-ensuciar-metricas-pjn"&gt;pruebas de onboarding en SaaS&lt;/a&gt;: si una bandeja recibe mensajes de varios escenarios, la confianza del test cae rapido.&lt;/p&gt;

&lt;p&gt;También conviene registrar términos y atajos que usa el equipo en tickets o runbooks. He visto notas con tepm mail com o temp mailid escritas al vuelo. No es grave, pero suele indicar que la operación diaria depende más de memoria informal que de un procedimiento repetible. Cuando el sistema crece, eso te pasa factura.&lt;/p&gt;

&lt;p&gt;Si quieres una validación más fuerte, añade un paso que compare la acción pedida por el agente con la acción realmente ejecutada. Si el modelo propuso "recordatorio de pago" y terminó saliendo "bienvenida", ya encontraste la zona del fallo sin mirar diez dashboards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tradeoffs y checklist antes de meterlo en produccion
&lt;/h2&gt;

&lt;p&gt;Separar contratos agrega algo de fricción. Hay más logs, más ids y un poco más de trabajo de integración. Pero ese costo compra algo muy valioso: capacidad de explicar por qué un correo salió, no salió o salió mal. Para sistemas con LLMs, esa explicabilidad es casi parte del producto, aunque a veces no lo parezca.&lt;/p&gt;

&lt;p&gt;Yo evaluaría estos tradeoffs antes de subir el flujo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Más control determinista reduce libertad del agente, pero mejora auditoría.&lt;/li&gt;
&lt;li&gt;Bandejas aisladas por escenario cuestan algo más de operación, pero bajan falsos positivos.&lt;/li&gt;
&lt;li&gt;Reintentos automáticos ayudan a entrega, pero pueden esconder duplicados si no hay idempotencia.&lt;/li&gt;
&lt;li&gt;Tests end to end son más lentos, pero detectan errores que un mock nunca va a mostrar.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Checklist corto:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cada ejecución tiene &lt;code&gt;trace_id&lt;/code&gt; propio.&lt;/li&gt;
&lt;li&gt;El LLM solo puede pedir acciones dentro de un esquema valido.&lt;/li&gt;
&lt;li&gt;El ejecutor de correo revalida destinatario, template y contexto.&lt;/li&gt;
&lt;li&gt;La bandeja de prueba pertenece a un solo escenario.&lt;/li&gt;
&lt;li&gt;El clic final confirma el cambio de estado esperado.&lt;/li&gt;
&lt;li&gt;Los logs permiten seguir el caso sin adivinar demasiado.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No hace falta perseguir perfección total desde el día uno. Hace falta que el equipo pueda repetir el flujo mañana, entender qué pasó y corregirlo sin drama. Ese punto, aunque suene simple, ya separa un experimento bonito de una automatización util.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  ¿Debo dejar que el LLM redacte el cuerpo completo del email?
&lt;/h3&gt;

&lt;p&gt;Depende del riesgo. Para correos sensibles, prefiero que el modelo rellene bloques acotados y que el template principal siga en código o en un CMS controlado.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Qué pruebo primero si el agente "hizo todo bien" pero el usuario no recibió lo esperado?
&lt;/h3&gt;

&lt;p&gt;Primero compararía la acción propuesta con el comando realmente ejecutado. Luego revisaría la bandeja aislada y el enlace final. Muchas veces el problema está en esa traducción intermedia.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Necesito pruebas reales si ya tengo unit tests del prompt y del worker?
&lt;/h3&gt;

&lt;p&gt;Sí. Los unit tests sirven, pero no capturan del todo la cadena completa entre decisión, envío y verificación. En sistemas con LLMs, ese borde entre componentes es justo donde se esconden varios fallos raros.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>automation</category>
      <category>testing</category>
    </item>
    <item>
      <title>Cómo probar emails transaccionales en FastAPI sin mezclar bandejas ni eventos</title>
      <dc:creator>Silviu Technology</dc:creator>
      <pubDate>Fri, 03 Jul 2026 20:25:29 +0000</pubDate>
      <link>https://dev.to/silviutech/como-probar-emails-transaccionales-en-fastapi-sin-mezclar-bandejas-ni-eventos-5f0m</link>
      <guid>https://dev.to/silviutech/como-probar-emails-transaccionales-en-fastapi-sin-mezclar-bandejas-ni-eventos-5f0m</guid>
      <description>&lt;p&gt;Cuando un flujo de registro, invitación o recuperación depende de email, una prueba floja puede romper dos cosas al mismo tiempo: la confianza del equipo y la lectura de métricas. En proyectos de Python con FastAPI esto pasa seguido porque el endpoint responde bien, pero el job de fondo, el proveedor de correo y la bandeja de prueba no siempre quedan alineados.&lt;/p&gt;

&lt;p&gt;Yo intentaría separar el problema en piezas chicas. Primero validás que FastAPI genera el evento correcto. Luego comprobás que el worker arma el mensaje esperado. Al final revisás que el enlace recibido coincide con el estado real de la cuenta. Parece obvio, pero cuando todo corre deprisa en staging es facil mezclar usuarios, tokens y aperturas.&lt;/p&gt;

&lt;p&gt;Además, si alguien del equipo anda buscando temp mail mail para resolver el test rápido, normalmente no está pidiendo una herramienta mágica. Está pidiendo aislamiento. Necesita una bandeja temporal que no recicle mensajes viejos y que permita ver con claridad qué correo pertenece a qué escenario.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dónde suelen romperse estas pruebas en FastAPI
&lt;/h2&gt;

&lt;p&gt;El primer fallo aparece cuando tratamos email como si fuera solo una salida del endpoint. En FastAPI casi siempre hay algo más: una tarea en segundo plano, una cola, un scheduler o un worker que cambia el momento real del envío. Si el test solo confirma &lt;code&gt;200 OK&lt;/code&gt;, deja fuera la mitad del sistema.&lt;/p&gt;

&lt;p&gt;Tambien se rompe cuando una misma bandeja recibe correos de varios casos. Ahí ya no sabes si el enlace que abriste viene del escenario actual o de un envío anterior. Ese ruido es casi el mismo problema que explica este post sobre &lt;a href="https://dev.to/hannahdev56/como-probar-correos-de-onboarding-en-un-saas-sin-ensuciar-metricas-pjn"&gt;pruebas de onboarding en SaaS&lt;/a&gt;: la bandeja compartida hace que una verificación aparentemente simple se vuelva poco confiable.&lt;/p&gt;

&lt;p&gt;Un tercer error, más silencioso, es no conservar el rastro entre API, worker y click final. Si producto dice "el correo llegó" y backend dice "el evento salió bien", pero nadie puede conectar esos dos puntos, el resultado sigue siendo fragil.&lt;/p&gt;

&lt;h2&gt;
  
  
  Un flujo simple para probar correos sin contaminar datos
&lt;/h2&gt;

&lt;p&gt;La versión que mejor me funciona en equipos chicos es muy directa:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Crear un usuario de prueba por escenario.&lt;/li&gt;
&lt;li&gt;Disparar el endpoint o el evento que debe mandar el correo.&lt;/li&gt;
&lt;li&gt;Esperar el worker real, no un mock demasiado optimista.&lt;/li&gt;
&lt;li&gt;Leer el mensaje desde una bandeja exclusiva.&lt;/li&gt;
&lt;li&gt;Abrir el enlace y comprobar el estado final en la app o en la base de datos.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;En un stack de Python/FastAPI, eso suele bastar para detectar si el problema está en serialización, plantillas, expiración de tokens o rutas de destino. Cuando hace falta una bandeja rápida para ese escenario, a veces uso una &lt;a href="https://tempmailso.com" rel="noopener noreferrer"&gt;disposable email address generator&lt;/a&gt; y documento cuál caso está cubriendo. El punto no es la marca, sino que cada prueba tenga su propio recipiente y no arrastre mensajes previos.&lt;/p&gt;

&lt;p&gt;Si estás probando invitaciones sociales o accesos con cuentas desechables, también puede aparecer el caso de &lt;a href="https://tempmailso.com" rel="noopener noreferrer"&gt;temp mail for facebook&lt;/a&gt;. Menciono eso porque muchos equipos validan primero los correos de alta o recuperación ligados a cuentas externas y luego reutilizan la misma lógica para onboarding interno. Conviene dejar claro cuál flujo estás cubriendo, si no se mezclan expectativas muy rapido.&lt;/p&gt;

&lt;p&gt;Algo que ayuda bastante es guardar un &lt;code&gt;trace_id&lt;/code&gt; desde el momento en que FastAPI acepta la solicitud. Ese mismo valor puede viajar en logs del worker, en metadatos del proveedor y, si tu sistema lo permite, en el registro del clic. No hace falta una plataforma enorme; con un identificador consistente y consultas simples ya puedes reconstruir el recorrido.&lt;/p&gt;

&lt;p&gt;También vale la pena anotar alias internos. He visto equipos escribir tamp mail com o tempail mail en notas rápidas, tickets o mensajes de chat. No es grave, pero sí muestra que el procedimiento aun no está bien estandarizado y que cualquiera puede terminar abriendo la bandeja equivocada.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cómo revisar eventos y enlaces antes de publicar
&lt;/h2&gt;

&lt;p&gt;Antes de dar el flujo por bueno, reviso tres piezas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;El evento de salida desde FastAPI contiene el usuario correcto y el template correcto.&lt;/li&gt;
&lt;li&gt;El worker registra un solo envío para ese escenario.&lt;/li&gt;
&lt;li&gt;El enlace dentro del correo aterriza en la pantalla que promete el asunto.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ese último punto parece menor y no lo es. Un correo puede estar perfectamente renderizado y aun así llevar a una vista vieja, a un token vencido o a una cuenta que ya no coincide con la sesión. En ese caso el test "pasa" para infraestructura, pero falla para la persona que recibirá el mensaje.&lt;/p&gt;

&lt;p&gt;También me sirve comparar el flujo con artículos cercanos del mismo idioma, como esta guía para &lt;a href="https://dev.to/hannahdev56/como-validar-correos-de-reactivacion-de-trial-en-un-saas-sin-mezclar-cohortes-4hne"&gt;validar cohortes de reactivacion&lt;/a&gt;. Aunque el caso de uso cambie, la idea central se repite: una bandeja por escenario, una intención por prueba y una lectura limpia de eventos.&lt;/p&gt;

&lt;p&gt;Si necesitas un ejemplo muy pequeño, la lógica suele verse así:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BackgroundTasks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_invite_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;trace_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Aqui llamarías a tu proveedor y guardarías logs del envío.
&lt;/span&gt;    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/invite&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;invite_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;background_tasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BackgroundTasks&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;trace_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invite:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;background_tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;send_invite_email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;trace_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trace_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;trace_id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No pretende ser un sistema completo, pero sí recordar dónde enganchar observabilidad minima para que la prueba no termine en adivinanzas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist corto para dejar el flujo estable
&lt;/h2&gt;

&lt;p&gt;Antes de cerrar la tarea, revisaría esto:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Un usuario de prueba por tipo de correo.&lt;/li&gt;
&lt;li&gt;Una bandeja por usuario de prueba.&lt;/li&gt;
&lt;li&gt;Un &lt;code&gt;trace_id&lt;/code&gt; visible en API y worker.&lt;/li&gt;
&lt;li&gt;Un solo clic final por escenario para no inflar eventos.&lt;/li&gt;
&lt;li&gt;Confirmación de que el enlace llega a la pantalla correcta.&lt;/li&gt;
&lt;li&gt;Limpieza basica de cuentas o tokens expirados tras la prueba.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Con ese nivel de disciplina ya bajas bastante el ruido. No hace falta diseñar un laboratorio enorme; hace falta que el equipo pueda repetir la prueba mañana y obtener casi el mismo resultado. cuando eso ocurre, los cambios de copy, plantillas o reglas de envío dejan de dar miedo.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  ¿Debo mockear el proveedor de email en todas las pruebas?
&lt;/h3&gt;

&lt;p&gt;No. Para pruebas unitarias sí tiene sentido mockear, pero para validar el flujo transaccional completo conviene ejecutar al menos una ruta real con bandeja aislada.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿FastAPI &lt;code&gt;BackgroundTasks&lt;/code&gt; alcanza para esto?
&lt;/h3&gt;

&lt;p&gt;Alcanza para casos sencillos. Si el volumen sube o necesitas reintentos, colas y workers dedicados suelen dar mejor trazabilidad y control.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Qué reviso primero si el correo llegó pero el test igual falla?
&lt;/h3&gt;

&lt;p&gt;Primero el enlace final y el estado del usuario. Muchas veces el envío está bien, pero el token, la ruta o la cuenta de destino ya no coinciden con el escenario que querías validar.&lt;/p&gt;

</description>
      <category>python</category>
      <category>fastapi</category>
      <category>backend</category>
      <category>automation</category>
    </item>
    <item>
      <title>How to Stop Playwright Email Tests From Flaking Across Parallel Workers</title>
      <dc:creator>Silviu Technology</dc:creator>
      <pubDate>Fri, 03 Jul 2026 18:45:03 +0000</pubDate>
      <link>https://dev.to/silviutech/how-to-stop-playwright-email-tests-from-flaking-across-parallel-workers-ok8</link>
      <guid>https://dev.to/silviutech/how-to-stop-playwright-email-tests-from-flaking-across-parallel-workers-ok8</guid>
      <description>&lt;p&gt;Parallel Playwright runs are great until email assertions join the party. A suite passes on one worker, fails on four workers, then passes again on retry. In most teams I have helped debug, the real problem is not Playwright itself. The problem is a shared inbox, weak message filtering, or a retry that reuses data from the first attempt.&lt;/p&gt;

&lt;p&gt;When I review flaky end-to-end suites, email verification is one of the first places I look. The fix is usually boring in a good way: one inbox per test worker or per test run, explicit message matching, and short-lived cleanup. If you need a quick non-production inbox source, a &lt;a href="https://tempmailso.com" rel="noopener noreferrer"&gt;tempmailso&lt;/a&gt; workflow can support that pattern, but the reliability gain comes from isolation and traceability, not from any single tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why parallel workers make email tests flaky
&lt;/h2&gt;

&lt;p&gt;Playwright runs tests in isolated workers and can retry failures in a fresh worker process, which is exactly what you want for stable automation: &lt;a href="https://playwright.dev/docs/test-parallel" rel="noopener noreferrer"&gt;https://playwright.dev/docs/test-parallel&lt;/a&gt; and &lt;a href="https://playwright.dev/docs/test-retries" rel="noopener noreferrer"&gt;https://playwright.dev/docs/test-retries&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The catch is that your app under test does not know or care about Playwright worker boundaries. If worker 1 and worker 3 both write signup emails into the same mailbox, your assertion layer can easily grab the wrong message. That creates three common failure modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a test reads an older email that still matches the subject&lt;/li&gt;
&lt;li&gt;a retry passes because the first failed attempt already triggered the message&lt;/li&gt;
&lt;li&gt;two workers race on the same account and nobody can prove which link belonged to which run&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why a burner email generator only helps when you connect it to worker identity. If the generated inbox is still shared by several tests, the flake just moves around. I have seen teams keep a fallback note like "check tamp mail com if the first inbox looks empty," and that is usually the moment the investigation starts getting muddy.&lt;/p&gt;

&lt;p&gt;Related patterns show up in &lt;a href="https://dev.to/sophiax99/how-to-test-oauth-recovery-emails-without-exposing-real-inboxes-hni"&gt;oauth recovery inbox isolation&lt;/a&gt; and &lt;a href="https://dev.to/ryanlee91/how-to-test-react-invite-emails-in-preview-environments-without-inbox-collisions-3mnp"&gt;preview invite email checks&lt;/a&gt;. Different flows, same root cause: inbox collisions hide whether the product or the test is broken.&lt;/p&gt;

&lt;h2&gt;
  
  
  The inbox pattern that keeps retries honest
&lt;/h2&gt;

&lt;p&gt;The most reliable setup is simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generate a unique application user for the test.&lt;/li&gt;
&lt;li&gt;Generate a unique inbox alias for that exact test or worker.&lt;/li&gt;
&lt;li&gt;Trigger one product action that should send one email.&lt;/li&gt;
&lt;li&gt;Poll only that inbox for a narrow time window.&lt;/li&gt;
&lt;li&gt;Assert on recipient, subject, and message body before you click anything.&lt;/li&gt;
&lt;li&gt;Delete or expire the inbox after the check ends.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I prefer making the alias include something like the worker index, test id, and timestamp. That way, if a failure happens, you can map the email back to the exact run without guesswork. It sounds tiny, but it saves a lot of QA time later.&lt;/p&gt;

&lt;p&gt;Another important detail is retry behavior. If a test fails after triggering the email, the retry should create a new identity and a new inbox instead of reusing the first one. Reusing the same address makes the second attempt hard to reason about, because now the mailbox contains mixed evidence. Teams sometmes blame the enviroment for this, but the test data design is the real issue.&lt;/p&gt;

&lt;p&gt;You should also store the inbox id in your test logs, not the full confirmation link. Logging the link can leak secrets into CI output. Logging the inbox id keeps the run auditable while still being safer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to assert in Playwright before you trust the email
&lt;/h2&gt;

&lt;p&gt;Once the right message is isolated, I like checking more than "email received":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the recipient matches the test identity exactly&lt;/li&gt;
&lt;li&gt;the message was created after the test started&lt;/li&gt;
&lt;li&gt;the subject line reflects the triggered action&lt;/li&gt;
&lt;li&gt;the confirmation URL points at the expected domain and env&lt;/li&gt;
&lt;li&gt;the token or code works once and fails after reuse&lt;/li&gt;
&lt;li&gt;no second unexpected email appears for the same action&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This matters for signup verification, passwordless login, invite acceptance, and tem email validation flows alike. A passing inbox check is not enough if the link is stale, duplicated, or pointing to the wrong place.&lt;/p&gt;

&lt;p&gt;For Playwright, keep the email polling helper separate from UI steps. I want the UI action to trigger the send, then a helper to fetch and parse the message, then a separate assertion block for the link or code. That separation makes failures easier to classify. Was the issue send timing, mailbox lookup, or product behavior after click? If all three are jammed into one helper, the stack trace gets noisy real fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  A repeatable debugging checklist for flaky email runs
&lt;/h2&gt;

&lt;p&gt;When a suite flakes, I go through this checklist before changing timeouts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;confirm every parallel test gets a seprate inbox&lt;/li&gt;
&lt;li&gt;confirm retries generate new inboxes instead of reusing old ones&lt;/li&gt;
&lt;li&gt;check subject filters are specific enough for the feature under test&lt;/li&gt;
&lt;li&gt;verify old messages are purged between runs&lt;/li&gt;
&lt;li&gt;record send timestamp, inbox id, and worker id in logs&lt;/li&gt;
&lt;li&gt;validate that cleanup happens even after assertion failures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If those six items are solid, most email flakes become easier to reproduce or disappear entirely. If they are not solid, adding sleeps usually just hides the bug for a week.&lt;/p&gt;

&lt;p&gt;One more caution: avoid creating a "shared diagnostic inbox" that engineers manually inspect after failures. It feels practical, but it quietly reintroduces the same ambiguity you were trying to remove. Thats why I prefer automated capture plus short retention over manual mailbox archaeology.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Should I use one inbox per worker or one inbox per test?
&lt;/h3&gt;

&lt;p&gt;One inbox per test is safest. One inbox per worker can still work when each worker operates on fully unique data and only one email-producing action happens at a time, but per-test isolation is easier to trust.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need a different inbox on every retry?
&lt;/h3&gt;

&lt;p&gt;Yes. Otherwise the retry can pass against leftovers from the first attempt, which makes the result less believable.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the first signal that a flaky email test has a data-isolation problem?
&lt;/h3&gt;

&lt;p&gt;The biggest clue is a test that fails only in parallel or only on retry. When that happens, inspect inbox ownership before you touch timeout values, because the timing problem is often only a sympton.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>playwright</category>
      <category>qa</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
