FastAPI: evidencia útil para validar emails
Validar un email en una API parece pequeño: recibes una dirección, consultas un servicio y devuelves una decisión. En producción los fallos se mezclan rápido. Un proveedor lento, un timeout, una respuesta dudosa o una dirección escrita como tempail pueden terminar mostrando el mismo mensaje.
En mis proyectos con Python y FastAPI, una mejora sencilla ha sido tratar cada validación como una decisión con evidencia. Ayuda a depurar, automatizar reintentos y explicar por qué una cuenta quedó pendiente. También evita que un temp mailid usado durante una prueba contamine las métricas reales.
El problema: un 500 no explica el email
Un endpoint que solo devuelve valid: false pierde información. El equipo necesita distinguir entre formato inválido, dominio que no responde, proveedor temporal o una respuesta que requiere revisión. No conviene enviar toda la respuesta del proveedor al cliente: guarda una clasificación pequeña y un identificador de comprobación.
Un contrato pequeño para la evidencia
from datetime import datetime
from pydantic import BaseModel, EmailStr
class EmailDecision(BaseModel):
address: EmailStr
status: str
reason: str
checked_at: datetime
check_id: str
Los estados pueden ser accepted, rejected o review. Mantenerlos pocos hace que dashboards y tests sean más claros. reason puede ser domain_timeout, temporary_provider o invalid_format.
Para separar los tests de los datos normales, conviene aislar pruebas de correo por entorno. Así una cuenta generada con un generador de correo temporal no aparece como tendencia de usuarios reales.
Código: registrar cada decisión
Una función de servicio concentra la política y deja el endpoint delgado:
from uuid import uuid4
from datetime import datetime, timezone
async def decide_email(address: str, provider) -> EmailDecision:
check_id = str(uuid4())
now = datetime.now(timezone.utc)
if not looks_like_email(address):
return EmailDecision(address=address, status="rejected", reason="invalid_format", checked_at=now, check_id=check_id)
try:
result = await provider.check(address)
except TimeoutError:
return EmailDecision(address=address, status="review", reason="domain_timeout", checked_at=now, check_id=check_id)
temporary = result.is_temporary
return EmailDecision(address=address, status="rejected" if temporary else "accepted", reason="temporary_provider" if temporary else "provider_ok", checked_at=now, check_id=check_id)
El check_id permite buscar logs sin exponer datos sensibles. En los logs guardo el dominio y el resultado, pero no el correo completo. Es pequeño, pero ahorra bastante tiempo.
Reintentos sin perder el contexto
Un timeout no significa que el email sea malo. Una cola asíncrona puede reintentar con un límite y un retraso creciente. Cada intento debe conservar el mismo check_id y añadir su número. Así una alerta muestra la secuencia completa, no solo el último error.
Las alertas necesitan contexto real, no solo un contador. Una práctica parecida aparece en alertas con contexto real: enlazar el evento con la información que permite actuar.
Checklist para producción
- ¿Los estados están documentados y tienen tests?
- ¿Timeout y respuesta negativa se distinguen?
- ¿Cada comprobación tiene un identificador trazable?
- ¿Los reintentos tienen límite y no bloquean el signup?
- ¿Los logs evitan el email completo?
- ¿Las pruebas usan datos claramente separados?
No hace falta construir una plataforma enorme. Un contrato pequeño, logs cuidadosos y una política explícita hacen que una API de FastAPI sea más fácil de operar. Cuando llegue el siguiente fallo, sabrás si debes corregir código, esperar al proveedor o revisar una decisión.
Top comments (0)