Cuando un SaaS empieza a crecer, el correo de bienvenida suele parecer un detalle pequeño. En realidad, es una pieza de producto: si llega tarde, se duplica o falla sin explicación, la activación también se vuelve dificil de entender.
En una etapa temprana no necesitas una plataforma enorme. Necesitas una cola de email con estados claros, reintentos limitados y suficiente contexto para saber qué ocurrió. Este enfoque me ha resultado útil para separar el registro del usuario del trabajo lento de enviar mensajes.
El problema: enviar no significa activar
Un endpoint de registro no debería esperar a que un proveedor de correo responda. Si lo hace, cada latencia externa se convierte en una pantalla que parece rota. Además, un timeout puede dejar una duda incómoda: ¿el mensaje no salió o salió pero la respuesta se perdió?
La solución básica es guardar una tarea después de crear el usuario:
usuario creado -> email pendiente -> enviando -> enviado
\-> reintento -> fallido
La tarea debe guardar un user_id, el tipo de mensaje, un número de intento y un next_attempt_at. No guardes contraseñas ni el contenido completo si no lo necesitas. El objetivo es poder reconstruir la historia sin crear otro problema de privacidad.
Diseña una cola pequeña
Una tabla en PostgreSQL puede ser suficiente al principio. Sus columnas más importantes son:
-
id: identificador de la tarea. -
status:pending,sending,sentofailed. -
attempts: cuántas veces se intentó. -
last_error: una explicación corta y segura. -
locked_until: evita que dos workers tomen el mismo trabajo. -
created_atysent_at: permiten medir el recorrido.
El worker busca tareas pendientes cuyo next_attempt_at ya pasó, las bloquea por unos segundos y las procesa. Una consulta con FOR UPDATE SKIP LOCKED puede ayudar cuando hay varios workers, aunque conviene empezar con una implementación sencilla y medir antes de añadir complejidad.
También separaría el evento de producto del proveedor. La cola sabe que debe enviar welcome_email; otra capa sabe cómo hablar con el proveedor. Así cambiar de proveedor no obliga a reescribir el onboarding entero.
Estados, reintentos y métricas
Un fallo temporal no es igual que una dirección inválida. Para un timeout de red puedes reintentar con una espera creciente, por ejemplo 1, 5 y 15 minutos. Para un rechazo permanente, marca la tarea como failed y deja una razón accionable.
No reintentes para siempre. Un límite de tres intentos suele ser un punto de partida razonable, pero el valor real depende del proveedor y de tu producto. Registra el resultado de cada intento, no solamente el resultado final.
Las primeras métricas que añadiría son:
- Tiempo entre
pendingysent. - Porcentaje de tareas que requieren un reintento.
- Tasa de fallos permanentes por dominio.
- Mensajes duplicados por cada mil envíos.
Con esas señales puedes descubrir si el problema está en tu worker, en el proveedor o en la calidad de los datos. Para probar flujos, también es útil un correo de prueba para onboarding y una política clara sobre cuándo usar un temp mailbox durante una verificación controlada.
Errores comunes
El primer error es publicar el evento y enviar el email en la misma petición. Funciona en local, pero se vuelve frágil con tráfico real. El segundo es ocultar todos los errores detrás de email failed; sin una causa y un identificador de tarea, depurar es casi adivinar.
Otro problema es no hacer el worker idempotente. Si el proceso se cae justo después del envío, puede repetir el mensaje. Usa una clave de idempotencia cuando el proveedor la soporte y guarda la respuesta externa. A veces un tempail mail aparece en pruebas manuales, o alguien escribe temp gamil com por accidente: eso no debería tumbar el worker ni contaminar tus métricas de infraestructura.
Finalmente, no mezcles la experiencia visual con el detalle técnico. El usuario puede ver “Estamos preparando tu email” mientras tu equipo conserva estados precisos. Si el formulario necesita validación, esta guía sobre validar formularios sin romper la experiencia muestra por qué los estados intermedios importan.
Checklist final
- [ ] El registro no espera al proveedor de email.
- [ ] Cada tarea tiene estado, intentos y timestamps.
- [ ] Los fallos temporales y permanentes se tratan diferente.
- [ ] Existe un límite de reintentos.
- [ ] El worker puede ejecutar la misma tarea de forma segura.
- [ ] Hay métricas de latencia, reintentos y duplicados.
- [ ] Los logs no exponen tokens ni contenido sensible.
Preguntas rápidas
¿Necesito Redis desde el primer día?
No necesariamente. PostgreSQL puede cubrir una cola pequeña si las consultas y los bloqueos están bien diseñados. Cambia de herramienta cuando tengas una necesidad observada, no por anticipación.
¿Debo bloquear a un usuario cuyo correo falló?
Normalmente no. Permite reenviar, muestra un estado honesto y registra el fallo. Bloquear sin explicar crea más soporte y menos confianza.
¿Qué hace que una cola sea realmente útil?
Que una persona pueda responder tres preguntas: qué tarea falló, por qué falló y qué ocurrirá después. Esa claridad vale más que añadir muchas piezas a la arquitectura.
Una cola de email no tiene que ser sofisticada para ser confiable. Con estados pequeños, reintentos conscientes y métricas básicas, tu SaaS puede mejorar la activación sin convertir cada correo en una investigación.
Top comments (0)