Reset link says expired or already used on the first click
A user requests a reset, opens the email, clicks once, and is told the link has expired or has already been used. Requesting another produces the same result, and it happens consistently for some users and never for others.
- Any stack
- Bubble
- Lovable
- Bolt
- Replit
- Auth & login
- Email delivery
Root cause, in plain English
Corporate mail security fetches every link in an inbound message to check where it goes. If your reset token is consumed by the request that loads it, the scanner spends it seconds after delivery and the human arrives second. It reproduces perfectly for users behind scanning mail providers and never for anyone on a consumer mailbox, which is why it looks random from the outside.
How to fix it
Confirm the pattern by checking which mail domains the affected users share. A clean split along employer lines rather than random users points squarely at link scanning.
Stop consuming the token on a plain page load. Loading the link should show a form; the token is only spent when the user submits it.
Never put a state-changing action behind a bare link that a scanner can follow, and keep the token out of anything a prefetcher treats as a resource to fetch.
Give tokens a sensible lifetime and allow reissue, so a user who genuinely is late can recover without contacting support.
Test through a real mailbox on a scanning provider, not only through a local inbox that never prefetches anything.
How Nightlamp detects this automatically
- Email flow
- Browser journey
- Account lifecycle
An email_flow check runs the real loop — request the reset, receive the message, extract the link, and follow it — so it proves the token is still valid at the moment a human would use it. Pairing it with a browser_journey that completes the reset form catches the case where the link opens but the submission fails, which a delivery-only check would call healthy.
Catch this before your customers do
Nightlamp runs these checks continuously against your live app and sends a plain-English diagnosis — not a wall of logs — the moment this pattern shows up.
Related patterns
Frequently asked questions
- How do I tell this apart from a genuinely expired link?
- Look at the gap between sending and the first click. If the token was consumed within seconds of delivery — long before the user could have opened the message — something automated spent it. A real expiry shows a plausible human delay.
- Is shortening the token lifetime a fix?
- It makes things worse. A scanner fetches the link almost immediately, so a shorter window only reduces the time a real user has to arrive after the token is already gone.
- Does this affect magic-link sign-in too?
- Yes, and usually more visibly, because magic links are the whole authentication path rather than an occasional recovery flow. The same fix applies: the link opens a page, and a deliberate action spends the token.
Newsletter
Get new incident patterns as we publish them
One email when new failure patterns, fixes, and monitoring recipes for no-code and AI-built apps land. No fluff, unsubscribe any time.
Double opt-in. One-click unsubscribe. No spam, ever.