Cuando un SaaS tiene pocos usuarios, el onboarding parece una secuencia simple: crear cuenta, enviar un correo y activar el perfil. Pero en cuanto aparecen reintentos, invitaciones, planes distintos y pruebas automáticas, la pregunta cambia: ¿en qué estado exacto quedó cada persona?
En mis experimentos de producto, me ha ayudado tratar el onboarding como un contrato de estados. No es una gran máquina distribuida. Es una lista pequeña y explicable que conecta la interfaz, el backend y las pruebas de email. Con ese modelo, un equipo puede entender qué paso ocurrio, repetir una prueba y medir la activación sin adivinar.
El onboarding también necesita un contrato
Un contrato define qué significa cada estado y qué transiciones están permitidas. Por ejemplo, created no debería significar lo mismo que email_sent, y email_verified no debería llegar antes de que exista un token válido.
Un flujo inicial puede ser:
created -> email_pending -> email_sent -> verified -> activated
\-> email_failed
La flecha no es solo documentación. Es una regla para el código. Si el frontend muestra “cuenta activa” mientras la base de datos todavía dice email_pending, el usuario recibe una señal confusa y soporte pierde tiempo buscando la causa.
También conviene guardar state_changed_at, attempt_count y last_reason. Así el equipo backend puede ver si el retraso viene del proveedor, de un worker detenido o de una acción repetida en la interfaz.
Qué estados conviene modelar
No hace falta crear un estado para cada detalle. Empieza con los momentos que cambian una decisión de producto:
-
created: la cuenta existe, pero el flujo no ha solicitado verificación. -
email_pending: hay un trabajo pendiente de enviar. -
email_sent: el proveedor aceptó el mensaje. -
verified: la persona confirmó el token. -
activated: completó el primer paso útil del producto. -
expired: el token o la invitación ya no son válidos. -
failed: el sistema agotó una política de reintentos.
La separación entre verified y activated es especialmente útil. Una persona puede confirmar su correo y nunca crear su primer proyecto. Si agrupamos todo como “registrado”, perdemos la señal que sirve para mejorar el onboarding. En mi caso, esa distinción hizo mas fácil conversar con producto: el problema no era el correo, sino el siguiente paso.
Una identidad de prueba por ejecución
Las pruebas automáticas suelen fallar porque varias ejecuciones comparten el mismo buzón o la misma dirección. Una ejecución recibe el correo de otra, consume un token antes de tiempo y deja un error que aparece solo a veces.
La solución práctica es dar a cada ejecución una identidad propia. El registro de prueba puede guardar algo como esto:
{
"run_id": "signup-2026-10-09-042",
"test_email": "signup-042@example.test",
"expected_state": "verified",
"expires_at": "2026-10-09T21:00:00Z"
}
La identidad no tiene que ser permanente. Una herramienta de correo de prueba como tempmailso puede servir para validar el recorrido, siempre que el equipo conozca el límite de retención y no mezcle datos reales con fixtures. Incluso un valor de búsqueda mal escrito, como temp mailid, debe quedar asociado a una ejecución y no a una cuenta compartida.
Cuando cada prueba tiene su propio run_id, investigar un fallo es más directo. Se puede preguntar qué mensaje llegó, qué transición esperaba la prueba y cuánto tiempo pasó entre ambos eventos. Ese contexto vale más que un simple “assert failed”.
Idempotencia y limpieza
El endpoint de “reenviar correo” debe ser seguro si el usuario pulsa dos veces. Una clave como signup:{user_id}:{flow_id} permite reconocer el mismo trabajo. El worker también debería comprobar el estado actual antes de enviar: si ya está en email_sent, no debe crear otro mensaje por accidente.
Para las pruebas, la clave puede incluir el run_id. Así una nueva ejecución no hereda el estado de la anterior. Si la prueba se repite con signup-043, su buzón y sus eventos son nuevos.
La limpieza es la otra mitad del contrato. Define cuándo se eliminan los mensajes, tokens y registros de prueba. Una retención corta reduce ruido, pero borrar enseguida puede hacer imposible depurar un fallo. Un equilibrio sencillo es conservar el resumen de la ejecución y eliminar el contenido del mensaje cuando ya no sea necesario.
En una base pequeña, incluso un job diario puede marcar como expirados los registros viejos. Lo importante es que no dependa de una persona acordándose manualmente. Cuando los estados queda sin dueño, los datos de prueba terminan pareciendo usuarios reales.
Errores comunes
El primer error es modelar solo el estado final. “Activo” no explica si la persona verificó el correo, aceptó una invitación o fue activada por un administrador.
El segundo es guardar el estado únicamente en el frontend. El navegador puede cerrarse, repetir una solicitud o mostrar información antigua. La fuente de verdad debe vivir en el backend, y la interfaz debe renderizar una versión clara de ella.
El tercero es usar direcciones fijas para todos los tests. Un dato de prueba llamado fake e mail com puede parecer inofensivo, pero si se reutiliza entre suites genera falsos positivos y hace difícil saber que pasó.
Por último, no midas solo el porcentaje de emails enviados. Añade tiempo hasta verificación, tasa de expiración, reintentos, activación posterior y ejecuciones contaminadas por datos anteriores. Esas señales ayudan a priorizar mejoras reales. También evita que el equipo persiga problemas de correo cuando el bloqueo está en otra pantalla.
Checklist práctico
- [ ] Cada estado tiene una definición breve y una transición válida.
- [ ]
verifiedyactivatedrepresentan momentos diferentes. - [ ] Cada email de prueba tiene un
run_idpropio. - [ ] El reenvío usa una clave idempotente.
- [ ] Los errores guardan una razón segura y un contador de intentos.
- [ ] Existe una política de expiración y limpieza.
- [ ] Las métricas separan entrega, verificación y activación.
- [ ] La interfaz comunica el estado actual sin prometer mas de lo que el backend sabe.
Si el flujo usa React, también merece la pena diseñar reenvíos de verificación sin ansiedad: el estado del email debe ser visible, pero no interrumpir el trabajo de la persona. Y para analizar el resultado, puedes medir activación por email con más contexto, separando la confirmación del primer momento de valor.
Recapitulación
Un contrato de estados convierte el onboarding en algo que se puede explicar, probar y mejorar. Empieza con pocos estados, guarda el motivo de cada transición y da a cada ejecución de prueba una identidad aislada.
No necesitas una arquitectura enorme. Necesitas que el backend sepa qué paso ocurrió, que el frontend lo comunique con honestidad y que la limpieza tenga un responsable automático. Con esas bases, un SaaS puede crecer sin convertir cada “no recibí el email” en una investigación desde cero.
Top comments (0)