La verificación por correo no funciona: arregla enlaces, tokens y la lógica de reenvío
¿La verificación de correo no funciona? Aprende por qué fallan enlaces, tokens y flujos de reenvío en apps generadas por IA, y soluciones sencillas para fiabilidad y resistencia al abuso.

Cómo se manifiesta cuando la verificación falla
Cuando la verificación de correo no funciona en tu proceso de registro, rara vez falla de forma clara y obvia. Los usuarios simplemente se quedan atascados. Se registraron, hicieron lo que pediste, y tu app sigue diciendo que no están verificados.
La experiencia más común es el silencio: no llega ningún correo. La gente revisa la carpeta de spam, espera unos minutos, pulsa reenviar y luego se rinde. Otros sí reciben un correo, pero el botón lleva a una página en blanco, a un error alarmante, o redirige de vuelta a la pantalla de inicio de sesión sin explicación.
La peor versión es el bucle:
“Por favor verifica tu correo” -> “Reenviar” -> “Correo enviado” -> clic -> sigue “Por favor verifica tu correo.”
Estos son los patrones que los usuarios suelen reportar (a menudo con capturas y frustración):
- No llega el correo de verificación (o llega horas después)
- El enlace abre pero muestra “Token inválido” o “Enlace de verificación expirado”
- El enlace funciona una vez y luego nunca más, incluso para un nuevo registro
- Tras verificar, la app aún los trata como no verificados
- El reenvío parece funcionar, pero solo funciona el correo más nuevo o el más antiguo (no ambos)
Esto no son solo unos pocos registros bloqueados. Cada flujo de verificación roto genera tickets de soporte, reembolsos y reseñas negativas. También puede crear riesgo: si el reenvío puede ser forzado, tu sistema puede enviar miles de correos rápidamente, lo que perjudica la entregabilidad y puede parecer spam.
Los flujos de incorporación generados por IA a menudo fallan porque las piezas están pegadas con suposiciones de camino feliz. Causas comunes incluyen tokens guardados en memoria en lugar de en la base de datos, enlaces construidos con el dominio equivocado, estado de verificación no guardado de forma fiable, o un botón de reenvío que crea un token nuevo pero nunca invalida el anterior.
El objetivo es simple: la verificación debe ser fiable para usuarios reales (incluidas personas que hacen clic en el correo desde otro dispositivo), clara cuando algo falla, y lo suficientemente estricta para que no pueda ser abusada. Si heredaste un prototipo generado por IA de herramientas como Lovable, Bolt, v0, Cursor o Replit, estos problemas son comunes y generalmente solucionables con una auditoría enfocada y unos pocos cambios cuidadosos.
Síntomas comunes que reconocer rápido
Algunos bugs de verificación son ruidosos (un error claro). Otros son silenciosos y solo aparecen como “la gente no puede registrarse”. La forma más rápida de depurar es nombrar el síntoma exacto, porque cada uno apunta a una capa diferente.
1) No llega el correo
Si los usuarios dicen que falta el correo, no asumas que tu app no lo envió. A menudo sí lo hizo, pero el proveedor lo bloqueó, aterrizó en spam, o llegó a la dirección equivocada.
Indicadores típicos: solo fallan algunos dominios (por ejemplo, bandejas corporativas), los mensajes aparecen en spam, o no hay nada en los registros del proveedor de correo. También comprueba lo básico: errores tipográficos en el correo del usuario, un dominio remitente incorrecto, o variables de entorno que aún apuntan a un servicio de correo de pruebas en producción.
2) El enlace abre, pero muestra “inválido” o “expirado”
Esta es la queja clásica de “la verificación de correo no funciona”. Normalmente significa que el token en la URL no coincide con lo que el backend espera, o que la lógica de expiración es demasiado estricta.
Patrones comunes:
- El token está URL-encoded de forma incorrecta.
- Tu backend hashea tokens pero los compara como texto plano.
- Rota secretos entre despliegues, así que tokens previamente emitidos nunca se validan.
- Deriva de reloj o lógica de zona horaria marca tokens como expirados de inmediato.
3) El enlace dice verificado, pero el usuario sigue sin iniciar sesión
Los usuarios hacen clic en el enlace, ven un mensaje de éxito y luego son devueltos a la pantalla de inicio de sesión como si no hubiera pasado nada.
Esto suele ocurrir cuando la verificación y el inicio de sesión se tratan como pasos separados pero la UI da a entender que son el mismo. Otras causas comunes: cookies de sesión no establecidas (dominio equivocado, flags secure faltantes, cookies de terceros bloqueadas), o el endpoint de verificación actualiza la base de datos pero el frontend sigue usando un estado de usuario obsoleto.
4) Reenviar funciona, pero los enlaces antiguos siguen funcionando (o nada funciona)
Los flujos de reenvío a menudo se comportan de forma extraña en incorporaciones generadas por IA. Puedes ver reenvíos que siguen generando correos mientras cada enlace permanece válido para siempre, o lo contrario donde cada correo nuevo falla al instante.
Un flujo de reenvío fiable necesita una regla clara: ¿un nuevo token invalida al anterior, o cualquier token activo puede seguir funcionando?
- Si no revocas tokens antiguos, los atacantes pueden usar cualquier enlace filtrado.
- Si revocas demasiado agresivamente sin avisar al usuario, hará clic en un correo anterior y verá “expirado” aunque acabe de solicitar un reenvío.
5) Cuentas múltiples, duplicados y condiciones de carrera
Los clics repetidos y los intentos de registro múltiples pueden crear casos límite: dos filas de usuario para el mismo correo, una bandera de verificado que cambia de ida y vuelta, o errores de “ya verificado” que aún no desbloquean el inicio de sesión.
Busca carreras como: un usuario se registra dos veces antes de que la primera verificación se complete, dos peticiones de verificación se ejecutan al mismo tiempo, o un reenvío crea un segundo registro pendiente. Esto aparece más con usuarios impacientes en móvil que tocan el enlace varias veces.
Si estos síntomas aparecen juntos, a menudo significa que el flujo se armó con fragmentos sin una única fuente de verdad para tokens, expiración y estado de usuario.
Dónde suelen vivir las fallas (entrega, app, base de datos)
Cuando la verificación de correo no funciona, los equipos suelen tocar el lugar equivocado primero. Trátalo como una carrera de relevos: el proveedor de correo debe entregar el mensaje, la app debe aceptar el clic y la base de datos debe registrar el nuevo estado.
Divide el problema en tres zonas
La mayoría de las roturas encajan en una de estas zonas:
- Entrega: el correo no llega, cae en spam, o el cliente de correo cambia el enlace (envuelto, truncado o reescrito por "safe-link")
- App: el endpoint de verificación rechaza la petición, el frontend muestra el mensaje equivocado, o el endpoint verifica pero la UI se queda atascada
- Base de datos: el registro del token falta u es sobrescrito, las marcas de tiempo son erróneas, o la bandera de verificado del usuario nunca se actualiza
Empieza con una pregunta simple: ¿llegó el clic a tu backend? Si tienes una entrada de log para la petición de verificación, la entrega probablemente está bien y el problema está en la app o la base de datos.
El cambio de estado debe ocurrir en el backend
Un bug común en incorporaciones generadas por IA: el frontend finge que la verificación tuvo éxito (muestra una pantalla de éxito) pero no hay ningún cambio de estado en el backend. La verificación debe ser una acción del servidor que actualice registros reales: estado del usuario, token usado o no, y la hora en que ocurrió.
Tu modelo de base de datos debería responder claramente estas preguntas:
- ¿A qué usuario pertenece este token?
- ¿Cuándo fue creado y cuándo expira?
- ¿Ya fue utilizado?
- ¿Cuál es el estado de verificación actual del usuario?
Si falta cualquiera de estas, verás comportamientos inconsistentes como “funciona una vez”, “solo funciona en móvil” o “sigue diciendo enlace expirado”.
Desajustes de entorno (dev vs prod)
Aunque el código sea correcto, la configuración puede cambiar el comportamiento entre entornos. Un backend puede generar enlaces con el host equivocado o usar secretos distintos para firmar tokens en producción. Otro clásico: producción ejecuta múltiples instancias, pero los tokens se guardan en memoria, así que la mitad de las peticiones no puede validarlos.
Si heredaste un prototipo generado por IA (Lovable/Bolt/v0/Cursor/Replit), la causa raíz a menudo no es un bug único, sino unos pocos desajustes pequeños en entrega, app y base de datos que solo aparecen en producción.
Errores en enlaces y tokens que rompen la verificación
Cuando la verificación de correo no funciona, el bug suele no estar en el correo en sí. Está en cómo se crea, almacena y comprueba el token del enlace.
Un enlace de verificación es tan fiable como las reglas detrás de su token. Si esas reglas son vagas (o faltan), obtendrás fallos aleatorios para usuarios reales y abuso fácil para atacantes.
Errores de token que rompen el flujo silenciosamente
Estos son los problemas que más aparecen en código de incorporación generado por IA:
- El token no se almacena en el servidor, así la app no puede validarlo después. Algunos prototipos ponen el token en estado del cliente (por ejemplo localStorage) y lo comparan consigo mismo, lo que no prueba nada.
- El token se filtra en logs. Si registras URLs completas o cuerpos de petición, tu token puede acabar en logs del servidor, analytics, rastreadores de errores o capturas de soporte.
- El token se guarda en texto plano. Si tu base de datos se filtra, los atacantes pueden verificar cuentas inmediatamente. Patrón más seguro: guarda un hash del token, igual que harías con una contraseña.
- La expiración es incorrecta. Muy corta significa que retrasos normales causan “enlace de verificación expirado”. Nunca expirar significa que enlaces antiguos pueden usarse para siempre.
- El token está ligado a la cosa equivocada. Desajuste común: el token se genera para un user ID, pero el endpoint busca por correo, o el enlace apunta al host de API equivocado.
- El endpoint no verifica propósito y estado. Un token debe ser válido solo para un propósito (verificar correo) y solo cuando la cuenta no esté ya verificada.
- Existen múltiples tokens válidos con reglas poco claras. Si permites tokens activos ilimitados, necesitas una política clara: gana el más nuevo, o cualquier token activo funciona.
Un ejemplo pequeño y realista: alguien se registra, solicita dos reenvíos y luego hace clic en el primer correo. Tu sistema acepta solo el token más reciente, así que devuelve “token inválido.” El usuario no hizo nada mal. Tus reglas y mensajes no coincidían con lo que pasó.
Un conjunto de reglas que funciona en la práctica: genera un token aleatorio, guarda solo su hash, establece una expiración razonable (horas, no minutos), átalo al usuario más propósito, y elige una política para múltiples tokens (a menudo “gana el más nuevo” revocando los anteriores).
Lógica de reenvío que funciona y no invita al abuso
Cuando la verificación falla, los usuarios prueban reenviar primero. Si el reenvío es débil, se convierte en un bucle infinito para usuarios reales y en una herramienta gratuita para malos actores.
Un buen botón de reenvío hace tres cosas cada vez: crea un token nuevo, hace inútiles los tokens antiguos, y frena solicitudes repetidas. Generar un token nuevo sin invalidar el viejo lleva a estados confusos como “ya usado” o “token no coincide”, dependiendo de qué correo abra el usuario.
Mantén el comportamiento de reenvío predecible:
- Emite un token nuevo y revoca o expira los tokens antiguos sin usar
- Limita la tasa por cuenta y por IP (un enfriamiento corto más un tope diario)
- Devuelve el mismo mensaje tanto si el correo existe como si no (evita descubrimiento de cuentas)
- Guarda hashes de tokens (no tokens en bruto) y usa un tiempo de expiración claro
- Muestra un estado UI claro como “Correo enviado. Intenta de nuevo en 30 segundos.”
Evita un bucle de reenvío usando una única fuente de verdad para “verificación pendiente”. No la bases en flags del frontend o almacenamiento local. Bázala en el estado del servidor (por ejemplo, un registro de usuario con estado de verificación). La UI debe leer ese estado y ofrecer solo acciones que tengan sentido: reenviar, cambiar correo o contactar soporte.
Manejar usuarios que cambian su correo antes de verificar es otro punto común de fallo. Trátalo como un flujo de verificación nuevo: actualiza el correo pendiente, revoca todos los tokens previos y requiere verificación de la nueva dirección. Si no, corres el riesgo de verificar el correo equivocado o dejar múltiples enlaces válidos flotando.
Los logs importan, pero registra de forma segura. Anota eventos y marcas de tiempo (verification_sent, verification_clicked, verification_succeeded, verification_failed) más metadatos gruesos como user ID y códigos de respuesta del proveedor. Nunca registres tokens en bruto, URLs completas de verificación o contenidos de correos. Si necesitas trazas, registra un identificador de token (por ejemplo, los primeros 6 caracteres del hash del token) y mantenlo fuera de mensajes visibles al cliente.
Paso a paso: hacer la verificación fiable de extremo a extremo
Si tu problema de verificación aparece solo a veces, normalmente es porque el flujo está dividido en demasiados lugares. Hazlo aburrido: un token, un endpoint, un cambio de estado claro.
1) Construye un flujo simple y repetible
Decide cómo funcionan los tokens antes de tocar la UI.
- Genera un token que no se pueda adivinar. Usa un valor largo y aleatorio (no un código corto, no un user id, no un JWT predecible a menos que sepas exactamente lo que haces).
- Almacena solo lo necesario. Guarda un hash del token (no el token en bruto), más
user_id,expires_atyused_at(o un simplestatus). - Envía un único enlace de verificación que apunte a un solo endpoint backend, por ejemplo:
GET /verify-email?token=.... - Verifica una vez y establece una fuente de verdad. Cuando el token sea válido, marca al usuario como verificado (por ejemplo,
users.email_verified_at = now()), y marca el token como usado. - Maneja la expiración con un camino amigable. Si el token está expirado, muestra un mensaje claro y ofrece una forma segura de pedir un nuevo correo.
2) Haz que el reenvío y los clics repetidos sean seguros
Dos reglas mantienen esto fiable: invalida tokens antiguos y haz que cada acción sea idempotente.
Cuando un usuario hace clic en el mismo enlace dos veces (o su cliente de correo lo precarga), nada debería romperse. Si el usuario ya está verificado, el endpoint debería devolver “Estás verificado” y detenerse.
Para reenvíos, evita múltiples tokens activos por usuario. Cuando emitas un token nuevo, invalida cualquier token anterior sin usar para ese usuario. Eso evita casos donde un correo antiguo llega después y confunde al usuario.
Una checklist rápida que atrapa la mayoría de errores de onboarding generados por IA:
- Un token activo por usuario, inválido después de un reenvío
- Un endpoint de verificación que hace el cambio de estado completo (no a medias en el frontend)
- Actualización atómica: verifica al usuario y consume el token en la misma operación
- Ventana de expiración clara (por ejemplo, 15 minutos a 24 horas) y un camino limpio para “enviar de nuevo”
- Respuestas idempotentes para tokens ya verificados y ya usados
Ejemplo: un fundador prueba el registro, hace clic en el enlace y funciona. Más tarde, un compañero usa el mismo enlace y obtiene “token inválido”. Eso puede estar bien, pero solo si el endpoint responde con “Ya verificado” en lugar de un error. A menudo el estado es correcto, pero faltan mensajes e idempotencia, por eso parece roto.
Resistencia al abuso y verificaciones de seguridad para añadir pronto
Los equipos suelen centrarse en la entregabilidad y la UI. Pero muchos flujos “rotos” están en realidad bloqueados por revisiones de seguridad faltantes, o están funcionando pero son fáciles de abusar. Añade unas cuantas defensas pronto para que la verificación siga siendo fiable bajo tráfico real.
Limitar reenvíos e intentos de verificación
Los botones de reenvío atraen abuso. Si un bot dispara cientos de correos por minuto, tu proveedor puede retrasar o rechazar mensajes, y los usuarios reales se quedan bloqueados.
Límites que suelen funcionar sin perjudicar a usuarios reales:
- Límite de reenvío por cuenta (por ejemplo, 1 por minuto con un tope diario corto)
- Límite de intentos de verificación por token (detener tras unos pocos intentos erróneos)
- Throttles por IP o dispositivo para registros y reenvíos repetidos
- Enfriamientos progresivos donde cada reenvío aumenta la espera
- Retroalimentación clara al usuario (una cuenta atrás supera un botón que se puede clicar sin fin)
Un detalle que importa: no crees un token totalmente nuevo en cada clic sin límites. Eso puede llenar tu base de datos y dificulta el soporte (“¿cuál correo es el correcto?”).
Prevenir replay y rutas comunes de takeover
Los tokens deben ser de un solo uso y de corta duración. Tras una verificación exitosa, marca el token como usado y recházalo para siempre.
También vigila comportamientos que facilitan takeover. La verificación no debería cambiar silenciosamente el correo de una cuenta solo porque alguien hizo clic en un enlace. Un patrón más seguro es: solicitar cambio -> enviar enlace al nuevo correo -> confirmar -> notificar al correo antiguo.
El manejo de redirecciones es otro problema silencioso. Si tu endpoint de verificación acepta un parámetro de redirect, permite solo destinos internos conocidos. De lo contrario, los atacantes pueden usar tu dominio para rebotar usuarios a páginas de phishing.
Trata los tokens como contraseñas: se filtran con facilidad. Evita poner tokens completos en logs, eventos de analytics, reportes de errores o capturas de soporte. Si debes registrar, guarda solo un prefijo.
Un ejemplo realista: un prototipo captura la URL de verificación (con token) en una herramienta de analytics y termina en dashboards de terceros. Cualquiera con acceso puede verificar cuentas. Esto ocurre más de lo que los equipos esperan porque el código de plantillas suele registrar URLs completas por defecto.
Trampas comunes en flujos de incorporación generados por IA
Cuando la verificación de correo no funciona en una app generada por IA, la gente suele culpar primero al proveedor de correo. La entrega puede ser parte del problema, pero las fallas repetidas suelen venir del estado confuso de la app: la base de datos dice una cosa, el servidor verifica otra y la UI muestra una tercera.
Un patrón común es “demasiados tokens, sin reglas”. El flujo de registro genera un token nuevo en cada reenvío, pero los tokens antiguos nunca se revocan. Luego el usuario hace clic en el primer correo, el servidor comprueba el token más reciente y obtienes un desajuste aunque el enlace parezca válido. Dos pestañas o dos dispositivos también pueden crear peticiones en competencia que dejan el estado en un lugar raro.
El diseño de tokens es otra trampa. El código generado por IA a veces usa tokens predecibles, o pone un user ID (o correo) directamente en el enlace y lo llama “verificación”. Eso es fácil de adivinar o reutilizar, y más difícil de invalidar de forma segura.
La verdad del servidor vence a la verdad del cliente
Si la UI pone isVerified = true tras el clic sin una actualización servidor-side, se verá bien en ese navegador y fallará en todos los demás. Necesitas una fuente única de verdad en el servidor, almacenada en la base de datos, y cada acción protegida debe comprobarla. De lo contrario, la gente puede saltarse la verificación llamando a endpoints directamente.
El tiempo es otro fallo silencioso. Los usuarios reales hacen clic tarde, clican dos veces o reciben correos fuera de orden. Si no pruebas ventanas de expiración, desviación de reloj entre servicios y entregas retrasadas, lanzarás un flujo que solo funciona en condiciones de laboratorio.
Comprobaciones rápidas que atrapan la mayoría de errores de onboarding:
- Solo hay un token de verificación válido por usuario a la vez, con reglas de precedencia claras
- Se almacenan hashes de tokens (no tokens en bruto) y la validación siempre ocurre en el servidor
- El reenvío es un cambio de estado: revoca tokens antiguos, establece una nueva expiración y registra el evento
- La verificación es idempotente: un segundo clic responde “ya verificado”, no un error
- Se prueba el timing: enlaces expirados, correos fuera de orden, doble clic y cambios de dispositivo
Ejemplo realista: arreglando un registro roto en una semana
Un fundador lanza una app generada por IA y los primeros usuarios se topan con un muro: el registro funciona, pero nadie puede iniciar sesión porque la app insiste en verificar el correo. Los tickets de soporte dicen lo mismo: “Nunca recibí el correo”. Algunos usuarios sí lo reciben, pero al hacer clic el botón muestra un error como “token inválido”. El fundador busca “verificación de correo no funciona” y prueba ajustes rápidos, pero el problema sigue regresando.
Empieza por lo aburrido. El proveedor de correo está rechazando envíos porque la dirección from no coincide con el dominio verificado, así que muchos mensajes nunca salen. Además, la app genera tokens en memoria, los envía por correo, pero nunca los guarda en la base de datos. Así que cuando un usuario hace clic en el enlace, el servidor no tiene con qué comparar.
Una solución estable se vería así:
- Corregir la configuración del remitente (dominio verificado, from y reply-to)
- Guardar un token hasheado con tiempo de expiración y user id
- Validar al clic: el usuario existe, el token coincide, no está expirado, no está ya usado
- Añadir límite de reenvío (cooldown más tope diario) por usuario y por IP
- Invalidar tokens antiguos cuando se emite uno nuevo
Tras la corrección, la verificación funciona incluso si alguien abre el correo horas después. Si piden un correo nuevo, solo se acepta el enlace más reciente y los enlaces antiguos fallan de forma segura con un mensaje claro. Eso reduce la confusión y cierra una ruta común de abuso donde atacantes spamean reenvíos o reutilizan tokens antiguos.
Durante la transición, el soporte necesita un script simple que no culpe al usuario. Por ejemplo: “Hemos corregido nuestros correos de verificación. Por favor solicita un nuevo correo de verificación desde la pantalla de inicio de sesión. Si aún no lo recibes en 5 minutos, revisa spam y dinos qué dominio de correo usaste (Gmail, Outlook, etc.).”
Comprobaciones rápidas y siguientes pasos
Si la verificación de correo no funciona y bloquea registros, haz una pasada rápida que compruebe toda la cadena. La mayoría de los reportes de “no funciona” se reducen a un desajuste entre lo que se envió y lo que tu servidor espera cuando se abre el enlace.
Una lista rápida que puedes ejecutar en unos 15 minutos:
- Entrega: confirma que el correo se envía de verdad, no está atascado en la cola del proveedor y no cae en spam (revisa eventos y rebotes del proveedor)
- Corrección del enlace: abre el correo y compara dominio, ruta y query params del enlace con lo que maneja tu app (vigila la codificación de URL)
- Reglas del token: confirma formato del token, estrategia de hashing, vida útil y ajustes de tiempo coinciden en ambos lados
- Actualizaciones de estado: asegura que
verifiedse escribe una vez, en el registro de usuario correcto, y que el login lee ese mismo campo - Logging: añade un trace ID por petición, registra códigos de razón claros (expired, already_used, invalid) y nunca registres tokens completos (solo un prefijo)
Luego haz unas pruebas límite en una ventana de incógnito para ver lo que ven usuarios nuevos:
- Token expirado: espera más allá del TTL, haz clic en el enlace y confirma que obtienes un resultado claro de expirado y un camino seguro para reenviar
- Reenvía dos veces: solicita dos reenvíos, confirma que solo funciona el enlace más nuevo (o que los antiguos son invalidados)
- Clic en enlace antiguo tras un reenvío: confirma que no verificas el token equivocado ni vuelves a poner al usuario como no verificado
- Clic en el mismo enlace dos veces: confirma que el segundo clic se maneja de forma segura y no da error
- Crea dos cuentas, intercambia enlaces: confirma que los enlaces no pueden verificar a otro usuario
Si heredaste un código generado por IA, busca flujos auth spaghetti: múltiples endpoints de verificación, tokens guardados en sitios distintos, o lógica UI que marca verificado antes que el servidor. Son difíciles de ver sin leer el flujo de extremo a extremo.
Si quieres una segunda opinión, FixMyMess (fixmymess.ai) se especializa en diagnosticar y reparar código generado por IA, incluidos flujos de autenticación y verificación rotos. Una auditoría enfocada del código a menudo saca a la luz pequeños problemas acumulativos (configuración de entrega, persistencia de tokens, reglas de reenvío) que solo aparecen en producción.
Preguntas Frecuentes
How do I quickly tell if the problem is email delivery or my app logic?
Start by checking whether the verification click reaches your backend. If you see the request in server logs, delivery is probably fine and the bug is in token validation or state updates. If there’s no request, focus on email provider events, spam placement, and whether the link is built with the correct domain and path.
Users say the verification email never arrives—what should I check first?
First confirm your email provider accepted and sent the message, then check bounces and spam placement. Next verify your sender setup (from address and domain alignment) and make sure production isn’t still pointing at a test email service. If only certain domains fail, it’s often a deliverability or policy issue rather than your code.
Why do verification links show “invalid token” or “link expired” even right after signup?
It usually means the token in the URL can’t be matched to what the server stored, or the server thinks it’s expired. Common causes are URL encoding issues, comparing a hashed token to a plain token, rotating secrets between deploys, or clock/timezone mistakes. Log a clear failure reason like expired vs not_found so you can narrow it down fast.
The link says “verified” but the app still treats the user as unverified—why?
Make verification a real server-side state change, then make the UI read that server state. If the backend updates the user but the frontend keeps stale user data, the UI can still show “unverified.” Also check cookie/session settings (domain and secure flags) because users may be verified but not logged in automatically.
What’s the safest resend behavior: should old links still work or not?
Pick one simple rule and communicate it in the UI. A practical default is “newest token wins,” where every resend revokes older unused tokens so only the latest email works. Add a short cooldown and daily cap so resends can’t be spammed and so deliverability doesn’t get damaged.
How should I store verification tokens to avoid both bugs and security issues?
Store a hash of the token (not the raw token) along with the user ID, purpose, expiry time, and whether it was used. Treat it like a password: never log the full token or full URL, and reject used tokens forever. This makes validation reliable and reduces the blast radius if logs or a database leak.
Why does clicking the same verification link twice sometimes break things?
Make the verification endpoint idempotent: if the user is already verified, return a success message instead of an error. Some email clients or security tools also prefetch links, which can trigger a first “click” before the user taps it. Idempotency prevents that from breaking the user experience.
How do duplicate accounts or race conditions happen during signup and verification?
You’re likely creating multiple user records or multiple pending token records without a single source of truth. Enforce a unique constraint on email, and ensure verification consumes exactly one token and updates the user in one atomic operation. Also handle double-clicks and concurrent requests safely so state can’t flip back and forth.
How do I stop the resend button from being abused or hurting deliverability?
Rate-limit resends by account and IP, use a cooldown, and cap daily sends. Return the same message whether the email exists or not to prevent account discovery. Also avoid generating a brand-new token on every click with no limits, because it creates confusion and can flood your email provider.
Can FixMyMess help if this came from an AI-built prototype (Lovable, Bolt, v0, Cursor, Replit)?
Yes, and it’s common. AI-generated flows often store tokens in memory, build links with the wrong domain, or update “verified” only in the frontend. FixMyMess can run a free code audit to pinpoint the exact break (delivery config, token persistence, resend rules, state updates) and typically get verification working reliably within 48–72 hours.