Passwordless sign-in has one hard dependency: the email must arrive, quickly, in the inbox. If it does not, the user cannot log in at all: there is no fallback, no "reset password" path, nothing. It is the one email where a deliverability problem is a total outage.
It is also the one most likely to be filtered, because "click this link to access your account" is what phishing looks like.
Speed matters as much as placement
A password reset that arrives in five minutes is annoying. A sign-in link that arrives in five minutes has already expired in the user's patience, and they have requested three more.
- Send it from the request path, not a queue, unless your queue is measured in single-digit seconds. This is the send that should be the fastest thing your app does.
- Do not batch it. Ever. Batching adds latency to the one message that cannot afford any.
- Do not put it behind a job runner with a cold start.
Then design the UI for the case where it is slow anyway: after the request, show "check your email", show the address you sent to, and offer a resend button that is disabled for 30 seconds. Most support tickets here are people who typed the wrong address and had no way to tell.
Corporate scanners click your links
This is the failure that looks impossible from your side: the user says the link does not work, your logs say the token was consumed, and the timestamps are seconds apart.
Microsoft Defender for Office 365, Proofpoint, Mimecast and similar products follow every URL in an incoming message to check for malware. They do it before the mail is delivered, from a data centre, with a browser-like user agent. If your link is single-use, the scanner burns it and the human gets "this link has already been used".
Three mitigations, in order of how much they help:
1. Do not consume the token on GET. Land on a page that says "Confirm sign-in for sam@example.com" with a button that POSTs. Scanners follow links; they do not submit forms. This is the fix that works, and it costs one extra click.
2. Bind the session to the click, not the email. Set the session cookie on the POST, not on the GET, so a scanner cannot create a session at all.
3. Show the URL as text too. Some scanners rewrite links in a way that breaks them entirely. A visible URL the user can copy is a way through.
A short window helps a little (15 minutes rather than 24 hours) but it does not help against a scanner, which clicks within seconds.
Getting it into the inbox
Authenticate properly. SPF, DKIM and DMARC on a verified subdomain. See the domain-verification doc; there is no copy trick that compensates for failing authentication.
Use a dedicated subdomain for transactional mail. mail.example.com for
sign-in and receipts, something else for marketing. If your newsletter earns a
complaint, sign-in must not inherit it.
Write like a receipt, not like a campaign. No images, no tracking pixel, no link shortener, one link, a plain subject ("Your sign-in link"). Every marketing signal you add moves it toward the Promotions tab, and the Promotions tab is where sign-in links go to die.
Never deduplicate it. sendEmail takes an optional idempotencyKey and the
magic-link path deliberately omits it. A user who requests a second link is
telling you the first did not arrive; collapsing the second into the first locks
them out permanently.
Say what to do if they did not ask for it. "If you did not request this, ignore it: nothing happens until the link is used." It is honest, it reduces alarm, and it reads less like phishing.
Token rules
The email is a delivery mechanism for a credential, so the credential has to be strong on its own:
- 32 bytes from a CSPRNG, URL-safe encoded. Not a UUID v4 with a timestamp, not a signed user id.
- Single use. Delete or mark consumed inside the same transaction that creates the session, so two rapid clicks cannot both succeed.
- Short expiry. Fifteen minutes is a good default. Say it in the body.
- Store a hash, not the token. Your database is not the right place to keep a credential in plaintext.
- Rate limit requests per address and per IP. Without it, the endpoint is a free way to mail anyone repeatedly from your verified domain, which is how a sending reputation gets destroyed by someone else.
- Never log the URL. Not in application logs, not in an error report, not in a Sentry breadcrumb. The template takes the URL as a prop precisely so it has no other reason to exist anywhere.
When it still does not arrive
In order:
- Your logs: is there a message id? If not, the send failed.
EmailSuppressedErrormeans a previous hard bounce, which is a real answer. - The Resend dashboard: delivered, bounced or queued?
- Show original in the recipient's client: did SPF, DKIM and DMARC pass?
- The spam folder and the Promotions tab.
- The recipient's domain. Corporate mail is filtered far more aggressively than consumer mail, and some domains block unfamiliar senders outright. That is the case where offering a second sign-in method is the only real fix.
That last point is worth stating plainly: passwordless is a great default and a poor monopoly. Offer at least one OAuth provider alongside it, so a user whose IT department eats your mail still has a way in.