Cuando un equipo empieza a usar entornos preview en serio, casi siempre aparece el mismo problema: los correos de una branch terminan mezclados con los de otra. En FastAPI esto pasa mucho cuando el servicio de email vive fuera del request principal y nadie le pasa contexto suficiente. El resultado no suele romper prod, pero si rompe la confianza en las pruebas.
En un proyecto reciente vi algo muy parecido. Cada pull request levantaba su API, ejecutaba tests y disparaba notificaciones de registro, invitacion y reset de password. Todo parecia estable, pero una de cada tantas corridas fallaba por una razon bastante tonta: la prueba leia un email valido, solo que pertenecia a otra branch.
Si trabajas con una direccion de correo desechable, esta separacion importa bastante. Tambien ayuda cuando alguien del equipo usa una bandeja temporal para reproducir un bug puntual y no quiere perseguir mensajes cruzados. Incluso para casos mas improvisados, como una prueba rapida con tempail mail, tener aislamiento ahorra tiempo y evita conclusiones medio raras.
Por que cada branch necesita su propia bandeja
Hay tres causas que veo una y otra vez:
- El worker de correo no recibe el
run_ido el nombre de la branch. - Los tests solo buscan "el ultimo email" y asumen que es suyo.
- La limpieza de bandejas ocurre tarde, o peor, no ocurre nunca.
Eso vuelve fragil cualquier suite. Si ya has leido sobre pruebas de correos de handoff, el patron es parecido: la señal util aparece cuando relacionas el mensaje con el evento correcto, no cuando buscas un correo bonito en una inbox compartida.
Mi regla es simple: una branch, una identidad observable. No hace falta montar una plataforma enorme. Con que cada ejecucion tenga un run_id, una etiqueta de entorno y una inbox asociada, ya quitas mucho ruido. Es un cambio chico, pero pega fuerte en la estabilidad.
El patron simple: run_id + inbox por entorno
La implementacion minima se ve asi:
- Genera un
run_idal inicio del pipeline. - Propaga ese valor al backend, al worker y al test runner.
- Construye el destinatario o alias con ese mismo identificador.
- Filtra los mensajes por
run_idantes de validar asunto, cuerpo o links.
Si tu equipo trabaja con contratos de eventos, te conviene pensar el email como otra salida verificable de la API. Ese enfoque se parece bastante a estos contratos de correo que si se pueden probar: primero identificas el evento correcto, despues revisas el contenido.
Un detalle que aveces se olvida: el run_id debe vivir tambien en logs y metricas. Cuando una QA te dice "el email no llego", quieres poder buscar ese identificador en cola, app y proveedor sin adivinar nada.
Ejemplo pequeno con FastAPI
Yo suelo pasar el contexto de email como parte explicita del comando de negocio, no como variable global. Asi queda mas facil de probar y menos propenso a drift.
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class SignupPayload(BaseModel):
email: str
run_id: str
branch: str
async def send_signup_email(email: str, run_id: str, branch: str) -> None:
inbox_tag = f"{branch}-{run_id}"
subject = f"Confirma tu cuenta [{inbox_tag}]"
# Aqui llamarias a tu proveedor real o a un adapter testeable.
print({"to": email, "subject": subject, "run_id": run_id})
@app.post("/signup")
async def signup(payload: SignupPayload):
await send_signup_email(
email=payload.email,
run_id=payload.run_id,
branch=payload.branch,
)
return {"ok": True}
Lo importante no es el print, claro. Lo importante es que el contexto no se pierde. Luego, en tu prueba, buscas el mensaje por run_id y confirmas que el asunto y el enlace correspondan a esa ejecucion. Parece obvio, pero muchas apps todavia esconden ese dato en middleware o en variables de proceso que nadie valida bien.
Que revisar en CI antes de aprobar
Cuando este patron ya existe, mi checklist suele ser corta:
- Cada job crea un
run_idunico. - El backend lo guarda o lo reenvia sin mutarlo.
- El worker lo incluye en logs y metadata del mensaje.
- El test filtra por ese
run_idantes de leer contenido. - La limpieza de inboxs temporales corre al final, incluso si el job falla.
La limpieza importa mas de lo que parece. Segun el reporte de calidad de software de CISQ, el costo del software pobre en EE. UU. llego a 2.41 trillion dollars en 2022. No todo eso viene de emails, obvio, pero si recuerda algo util: los defectos pequenos y repetidos terminan saliendo caros.
Tambien vale revisar tiempos. Si tu proveedor entrega mensajes con latencia variable, no conviertas un retraso normal en falso negativo. Un poll corto con timeout razonable suele ser mejor que dormir el test 30 segundos "por si acaso". Ese truco funciona... hasta que deja de funcionar.
Preguntas comunes
Necesito una inbox distinta por cada branch siempre?
No necesariamente una inbox fisica distinta. A veces basta con alias, labels o filtros por metadata. Lo que si necesitas es separacion observable y consistente.
Esto no complica demasiado una API pequena?
Un poco, pero menos que depurar flakes durante semanas. Ademas, una vez modelado el contexto de email, el resto del backend queda bastante mas claro.
Conviene meter el nombre de la branch en el asunto?
Para entornos no productivos, si. Ayuda mucho al debug manual. Solo intenta que no se convierta en dependencia principal del test; el filtro serio deberia seguir siendo el run_id.
Si hoy tus pruebas de email en FastAPI dependen de "buscar el ultimo mensaje", yo empezaria por aqui. No es una arquitectura exotica ni perfecta, pero si es una mejora real que suele notarse en el mismo dia.
Top comments (0)