DEV Community

Silviu Technology
Silviu Technology

Posted on

FastAPI: depura reintentos de correo sin ruido

En muchos backends con FastAPI, el envio de correo falla menos por SMTP que por trazabilidad. El primer intento sale, el segundo reintento también, y de pronto nadie sabe cuál mensaje corresponde a cuál evento. Cuando eso pasa en staging o en un entorno preview, el equipo pierde tiempo mirando bandejas mezcladas y logs medio utiles.

Hace un tiempo empecé a tratar estos flujos como una cadena completa: request, tarea en segundo plano, proveedor de correo y bandeja de validación. Si una pieza no conserva el mismo identificador, depurar se vuelve bastante incomodo. También aparecen notas raras en scripts viejos como tem email o temp org mail, que suelen indicar que el proceso fue creciendo sin una convención clara.

Dónde se rompen los reintentos de correo en FastAPI

El problema comun no es "el correo no llegó". El problema real es "llegaron dos correos parecidos y no sé cuál validó mi prueba". En FastAPI eso aparece mucho cuando:

  • el endpoint genera el email y la cola usa otro job_id
  • el worker reintenta pero no guarda el trace_id
  • el test espera por asunto y no por contexto
  • varios escenarios comparten la misma bandeja

Ese ultimo punto parece menor, pero crea ruido muy rapido. Si el asunto siempre dice algo como "Confirma tu acceso", cualquier retry viejo puede hacer pasar una prueba que en realidad deberia fallar.

Qué conviene trazar desde la API hasta la bandeja de prueba

Mi regla simple es esta: el mismo identificador debe vivir en la API, en la tarea y en el correo final. No hace falta exponer datos sensibles; basta con una referencia estable que puedas buscar en logs y contenido del mensaje.

Un payload pequeño ya ayuda bastante:

from pydantic import BaseModel


class EmailCommand(BaseModel):
    trace_id: str
    user_id: str
    template: str
    retry_count: int = 0
Enter fullscreen mode Exit fullscreen mode

Cuando el endpoint crea la tarea, guarda trace_id en la base de datos o en la cola. Luego el worker lo vuelve a escribir en logs estructurados y también en un header o comentario visible del email. No es elegante a nivel literario, pero si te ahorra 20 minutos de investigación, ya pagó su costo.

Si ya estás ordenando pruebas de notificaciones, esta guía sobre probar emails transaccionales en FastAPI conecta muy bien con el mismo objetivo: aislar eventos para que cada mensaje tenga una lectura clara.

Un patrón corto con FastAPI y una cola simple

No siempre hace falta meter Celery, Kafka o una arquitectura enorme. Para muchos equipos basta con un patrón corto y honesto:

  1. El endpoint registra trace_id y retry_count=0.
  2. La tarea de envio captura errores transitorios.
  3. Si hay retry, incrementa retry_count pero mantiene el mismo trace_id.
  4. La prueba busca el correo por esa referencia, no solo por destinatario.

Una version reducida se ve así:

from fastapi import FastAPI, BackgroundTasks
from uuid import uuid4

app = FastAPI()


def send_email(trace_id: str, user_email: str, retry_count: int = 0) -> None:
    # Aqui iria tu cliente SMTP o API externa.
    print({"trace_id": trace_id, "user_email": user_email, "retry_count": retry_count})


@app.post("/signup")
def signup(user_email: str, background_tasks: BackgroundTasks):
    trace_id = f"signup-{uuid4()}"
    background_tasks.add_task(send_email, trace_id, user_email, 0)
    return {"ok": True, "trace_id": trace_id}
Enter fullscreen mode Exit fullscreen mode

Lo util de este enfoque no es el snippet. Lo util es que el test puede arrancar desde la respuesta HTTP y seguir el mismo trace_id hasta el inbox de prueba. Ahí dejas de perseguir fantasmas.

Otro aprendizaje: separa la bandeja por escenario o por suite. Si corres smoke tests de signup y recuperación sobre la misma casilla, tarde o temprano tendrás un falso positivo. Ese mismo criterio operativo también aparece cuando otros equipos necesitan probar correos de handoff en guardias SRE: menos mezcla, menos lectura ambigua, menos desgaste.

Checklist para no perseguir falsos positivos

Esta checklist me funciona bien en repos pequeños y medianos:

  • Cada request crea un trace_id que sobrevive al retry.
  • El worker registra retry_count en logs y métricas.
  • El correo de prueba incluye una referencia visible del flujo.
  • La suite valida contenido y correlación, no solo llegada.
  • Cada escenario usa una bandeja aislada o un filtro propio.
  • Los reintentos viejos se limpian despues de cada corrida.

No suena muy sofisticado, lo sé. Pero cuando una API empieza a enviar correos desde varios puntos del producto, este orden basico evita varios dolores de cabeza.

Preguntas frecuentes

¿Debo probar todos los reintentos en cada deploy?

No. Yo dejaría un smoke test para el camino feliz y una prueba dedicada para el retry cuando cambian colas, templates o timeouts. Si lo haces todo en cada push, la suite se vuelve lenta sin ganar mucha señal.

¿Qué suele romperse primero?

La correlación. El correo llega, pero el identificador no coincide con el evento que disparó el envio. En ese punto el test queda verde por accidente, que es peor que un fallo honesto.

¿Vale la pena guardar el trace_id dentro del email?

Sí, aunque sea en una parte discreta del cuerpo o en un header interno. Es una ayuda pequeñita, pero vuelve la depuración mucho más directa cuando algo sale raro.

Top comments (0)