Passwordless

Why Your Login Code Didn't Arrive: Deliverability for Auth Email

A deliverability guide written for engineers who ship magic links and OTPs — SPF, DKIM, DMARC, why transactional mail needs its own subdomain, greylisting, content that trips filters, SMS toll fraud, and the metrics that show the truth.

Emilian GheoneaJuly 9, 20268 min read

If your product uses magic links or one-time codes, then email is not a notification channel — it is your authentication infrastructure. When a message does not arrive, a user cannot log in. Not "has a degraded experience." Cannot log in.

Teams discover this the hard way, usually as a support queue full of "I never got the code" tickets, a provider dashboard showing 99.4% delivered, and no idea how to reconcile the two. The reconciliation is that "delivered" means the receiving server accepted the message. It says nothing about which folder it landed in, or how long it took.

This article covers what actually determines whether an authentication email reaches a human.

The authentication trio, and why DMARC is the one that matters

Three DNS records establish that you are allowed to send as your domain. All three are mandatory now — the major providers began requiring them for bulk senders in 2024, and the direction of travel is toward requiring them for everyone.

SPF lists which servers may send for your domain:

example.com.  TXT  "v=spf1 include:_spf.provider.com -all"

Use -all (hard fail), not ~all (soft fail). And watch the ten-lookup limit — SPF permits at most ten DNS lookups when evaluating, each include: counts, and exceeding it makes the whole record evaluate to permerror, which most receivers treat as no SPF at all. Three or four providers is enough to blow past it. This silently breaks and nothing tells you.

DKIM cryptographically signs each message, so the receiver can verify it was not modified in transit and genuinely came from your domain. Your provider gives you a public key to publish as a TXT record at a selector. Use 2048-bit keys, and rotate the selector periodically.

DMARC ties them together and is the record that actually changes outcomes:

_dmarc.example.com.  TXT  "v=DMARC1; p=reject; rua=mailto:[email protected]; pct=100"

DMARC does two things. It tells receivers what to do when SPF and DKIM both fail — and, critically, it requires alignment: the domain in the visible From: header must match the domain that SPF or DKIM authenticated. This is the check that catches the extremely common misconfiguration where a provider's SPF passes for their bounce domain while your From: says example.com, so nothing aligns and DMARC fails despite both underlying checks passing.

Roll it out in stages: p=none while you read the aggregate reports and find the senders you forgot about (your CRM, your ticketing system, that one cron job), then p=quarantine, then p=reject. Going straight to p=reject on a domain with unknown senders will break legitimate mail you did not know existed.

The rua reports are not optional in practice. They are the only visibility you get into who is sending as your domain and whether it is passing.

Separate your sending domains

The single highest-leverage structural decision: transactional mail and marketing mail must not share a domain reputation.

Reputation is computed per sending domain and per IP. A marketing campaign that generates complaints — and every campaign generates some — drags down the reputation of everything sent from that domain. If your login codes share it, your authentication starts landing in spam because someone sent a newsletter.

The standard split:

mail.example.com     -> transactional (auth, receipts, alerts)
news.example.com     -> marketing
example.com          -> corporate mail, untouched

Each subdomain builds its own reputation. Auth mail, which is opened at extraordinary rates and almost never marked as spam, develops an excellent one — as long as nothing else shares it.

Two follow-ons. Do not send anything promotional from the transactional subdomain. The "while you're here, check out our new feature" appended to a login code is a real thing teams do, and it converts a protected channel into an unprotected one. And use a dedicated IP only if you have the volume — below roughly 100,000 messages a month, a well-managed shared pool from a reputable provider will outperform a dedicated IP that never accumulates enough reputation to be trusted.

Warm-up, and the launch-day cliff

A brand-new sending domain has no reputation, which receivers treat as suspicious rather than neutral. A domain that has never sent, suddenly emitting fifty thousand messages, looks exactly like a compromised one.

Ramp gradually — a few hundred on day one, roughly doubling every day or two, watching bounce and complaint rates as you go. Most providers automate this. The failure case that surprises people is a product launch on a fresh domain: everything tested fine at ten messages a day, and at ten thousand a day the deferrals begin. Warm up before you need the volume.

Greylisting, and the gap your dashboard hides

Your provider reports delivery in 200 milliseconds. Your user says the code took six minutes. Both are telling the truth.

Many receiving servers — corporate gateways especially — practise greylisting: the first delivery attempt from an unknown sender is temporarily rejected with a 4xx, on the theory that spam software will not retry. Legitimate senders retry, and the second attempt is accepted. The retry interval is controlled by the sending server and is commonly one to fifteen minutes.

For a newsletter this is invisible. For a login code with a five-minute expiry, it is a broken product.

What helps:

  • Set OTP expiry to at least ten minutes. Five is too aggressive for real-world mail. If the code is short and the verify endpoint is properly rate limited, ten minutes does not meaningfully weaken it.
  • Choose a provider with a fast, aggressive retry schedule for transactional mail. Ask them; the number varies a lot.
  • Show the user what is happening. "Codes usually arrive within a minute. Some corporate mail systems delay them by a few minutes — the code stays valid for ten." This one sentence removes a large share of support tickets.
  • Offer a resend after 60 seconds, with real feedback, and — importantly — keep the previous code valid or say clearly that it is not. A user with two codes in their inbox who tries the first one and is told it is invalid will conclude your product is broken.

Content that trips filters

Authentication email should be the most boring message your system sends.

  • Plain text alongside HTML, and the plain-text part must contain the actual code and link. A message with an empty or placeholder text part is a spam signal, and some clients render only that part.
  • Minimal images. A logo at most. An image-heavy transactional message with almost no text is a classic filter trigger.
  • One link. A message with a login link plus footer links to your blog, social accounts, and pricing page looks like marketing. Ship the login link and the legally required footer, nothing else.
  • Do not use link tracking or click wrapping. Redirecting the login link through a tracking domain means the receiver sees a link to a domain that is not yours, which hurts reputation, and it means link-scanning gateways consume the link before the user clicks — the prefetch problem. Turn tracking off for auth mail specifically.
  • Do not use URL shorteners. Heavily associated with abuse.
  • Put the code in the subject line. 123456 is your Example login code. Users can read it from the notification without opening anything, and it measurably reduces time-to-login.
  • Avoid urgency language. "URGENT — verify immediately or your account will be deleted" is written the way phishing is written, and filters have noticed.
  • Set List-Unsubscribe only where required. Transactional mail is generally exempt, and an unsubscribe on a login code creates a user who cannot log in.

One more, easy to miss: a Reply-To that goes nowhere. noreply@ is common and mildly hostile — some receivers score it down, and users do reply. Pointing it at your support queue costs nothing.

The SMS-specific problems

If you send codes by SMS, the failure modes are different and mostly worse.

Toll fraud is a live financial risk. Attackers trigger OTP sends to premium-rate numbers in specific countries and collect a share of the termination fee. An unrestricted OTP-send endpoint can generate thousands in charges overnight. The defenses are hard destination-country allowlists, per-number and per-account send caps, and a daily spend ceiling that stops sending and pages a human — see rate limiting.

Delivery is genuinely unreliable and opaque. Carrier filtering, number portability, and roaming all cause silent drops. Delivery receipts vary in accuracy by carrier and country. You will not always know why a message failed.

Registration is now mandatory in many markets. Sending application-to-person SMS to the US requires brand and campaign registration; other countries require sender ID registration. Unregistered traffic gets filtered.

Include a code-autofill hint. iOS and Android will offer to fill the code automatically if the message follows the expected format — @example.com #123456 on the last line. Small change, meaningfully better experience.

And the strategic point: SMS is expensive, unreliable, vulnerable to SIM swap, and increasingly discouraged as a second factor. Where you can, prefer email or passkeys, and keep SMS as a fallback for users who have nothing else.

Measure the right things

Your provider's dashboard reports what it can see, which is not what you need to know.

Time to inbox, not time to send. Instrument the whole path: the timestamp when you enqueued, and the timestamp when the user successfully entered the code. The distribution of that delta — especially the 95th percentile — is the real number. It will be much worse than your provider's.

Completion rate by email domain. Segment magic-link and OTP completion by the recipient's domain. If Gmail completes at 94% and one corporate domain completes at 31%, you have found a deliverability problem that no aggregate metric would ever show.

Bounce rate, split by hard and soft. Hard bounces must be suppressed immediately and permanently. Continuing to send to addresses that returned a permanent failure is the clearest spam signal you can emit.

Complaint rate. Above 0.1% is trouble. For authentication mail it should be near zero — if it is not, you are probably sending to people who did not request it, which usually means someone is using your signup form to harass an address.

Resend rate. How often users click "send it again." A high rate means slow delivery, and it is a leading indicator that shows up before the support tickets do.

Monitor your own DNS. Alert if SPF, DKIM, or DMARC records change or fail to resolve. A DNS migration that drops a TXT record is a silent, total deliverability failure, and the first signal is usually a customer.

Key takeaways

  • SPF, DKIM, and DMARC are mandatory. DMARC alignment is the check that fails most often; watch the SPF ten-lookup limit.
  • Separate transactional from marketing at the subdomain level, and never send anything promotional from the auth subdomain.
  • Greylisting is why codes are slow. Set expiry to ten minutes and tell the user delays are normal.
  • Auth email should be boring: plain text alternative, one link, no tracking redirects, code in the subject line.
  • SMS carries toll fraud and carrier filtering risk — allowlist destinations, cap spend, register your traffic.
  • Measure time to inbox and completion rate by recipient domain. Aggregate delivery rates hide exactly the failures that matter.

Written by

Emilian Gheonea

Senior Blockchain & Full-Stack Software Engineer. I build EmbedAuth — an embeddable authentication platform for SaaS — and write about the auth problems most teams hit too late.