Authentication

Email Verification That Actually Works (and What It Doesn't Prove)

Why email verification exists, what it genuinely proves, the four flow designs and their trade-offs, token security, deliverability, and the blocking-vs-nagging decision that quietly shapes your activation rate.

Emilian GheoneaJuly 8, 20269 min read

Almost every product has an email verification step, and almost every product built it in an afternoon and never revisited it. That is unfortunate, because verification sits directly on the critical path between "someone wants to use your product" and "someone is using your product." Get it slightly wrong and you lose signups to a spam folder. Get it very wrong and you have an account takeover vector.

This article is about what verification actually proves, the four ways to structure the flow, and the specific decisions that separate a verification step users barely notice from one that quietly costs you a third of your conversions.

What verification proves — and what it doesn't

Be precise about the claim, because the design follows from it.

Email verification proves that at the moment the link was clicked or the code was entered, someone had read access to that mailbox. That is the entire claim. It is a narrow claim, and it is worth enumerating what it does not establish:

  • It does not prove the person who signed up is the mailbox owner. It proves someone with mailbox access completed the step. Usually the same person; not always.
  • It does not prove the address will still be deliverable next month. Corporate addresses are deactivated, personal domains lapse, and free providers recycle abandoned addresses after a period of inactivity — which means a verified address can later belong to a different human entirely.
  • It does not prove the address is not disposable. Ten-minute-mail services deliver perfectly well and verify perfectly successfully.
  • It does not prove intent. A user who verifies has demonstrated mailbox access, not that they wanted an account.

So what is verification for? Three concrete things, and knowing which ones you care about determines how strict you should be.

Recoverability. If the address is wrong, the user's password reset goes nowhere, and their account is permanently unrecoverable through self-service. Verification is the cheapest possible check that your only recovery channel works. For most products this is the real reason.

Deliverability protection. Sending to unverified addresses means sending to typos, and typos bounce. Hard bounces are the fastest way to damage your sending domain's reputation, which means your legitimate mail starts landing in spam. Verification protects the whole channel, not just one message.

Abuse resistance. Requiring a working mailbox raises the cost of creating accounts in bulk — modestly. It is a speed bump, not a wall, and treating it as your primary anti-abuse control is a mistake covered in bot detection at signup.

Four flow designs

There is more variation here than people expect, and the choice has a measurable effect on completion.

Click-a-link

The classic. Send a URL containing a token; clicking it marks the address verified. Zero typing, one click, familiar to everyone.

The weakness is context switching. The user leaves your product, goes to their mail client, clicks, and lands in a new browser — because mobile mail apps open links in an in-app webview or the system default browser, which is frequently not the browser where they signed up. Their signup session does not exist there. Now you have a verified address and a user staring at a login screen in an unfamiliar browser, wondering if it worked. This handoff is where most link-based flows lose people.

Enter-a-code

Send a short numeric code; the user types it into the page they are already on. The session never breaks, the flow completes in the original tab, and the user's mental model — "I'm still signing up" — stays intact.

The cost is typing, and the security requirement is real: a short code is brute-forceable, so it needs strict rate limiting on the verify endpoint, a small attempt budget, and a short expiry. The trade-offs mirror those in magic links vs OTP codes.

Both, in the same message

Send a link and a code, and let the user pick. Costs one extra line in the email template and handles both the "I'm on my phone reading mail" case and the "I'm at my desk" case. This is the pragmatic default and it is under-used.

Deferred verification

Let the user in immediately, unverified, and require verification only when they attempt something that needs it. This maximizes activation and is the right call for products where the first session has obvious value. It requires you to actually build the gating, which is the part teams skip — and an unverified state that is never enforced is just a database column.

The blocking decision

Here is the question that matters most and gets the least thought: can an unverified user use the product?

Hard blocking — nothing works until verified — is simple to reason about and expensive. Every deliverability problem becomes a total signup failure. If your verification email takes four minutes to arrive because a corporate mail gateway is greylisting you, a hard block means the user sat on a "check your email" screen for four minutes with nothing to do. A meaningful share of them left.

Soft gating is almost always better: the user is in, they can look around, and the specific actions that depend on a working mailbox are the ones that require verification. Choose those actions deliberately:

  • Anything that sends mail on the user's behalf, or that would let them use your sending reputation.
  • Anything involving payment or billing.
  • Inviting other users, or being granted elevated permissions in an organization.
  • Anything irreversible.

Meanwhile, a persistent but non-modal banner with a working "resend" button does the nagging. The important detail is that the resend button must give real feedback — "sent to [email protected] just now" — because a button that appears to do nothing gets clicked eleven times, which is both a support ticket and a mailbomb.

Token design

The verification token is a credential. Treat it like one.

Make it high-entropy and random. Not a signed user ID, not an incrementing integer, not a hash of the email. 32 bytes from a CSPRNG, URL-safe encoded.

Store only a hash. If your database leaks, raw verification tokens in it are immediately usable to verify — and, in flows where verification also grants a session, to take over accounts. Hash on write, hash the incoming value, compare.

Expire it, and pick the window honestly. Fifteen minutes is a security posture that generates support tickets, because people read email hours later. Twenty-four hours is the common balance for a link that only marks an address verified. If clicking the link also signs the user in — see below — treat it as a login credential and shorten it accordingly.

Single use. Consume it on success. Also decide what a second click does: the user forwarded the mail to themselves, or their mail client prefetched the link. Showing "invalid or expired token" to a user who successfully verified thirty seconds ago is a bad experience for a non-event. If the address is already verified, say so and move on.

Bind it to the address, not just the account. If the token only identifies a user record, and the user changes their pending email between send and click, the old token can verify the new address. Encode which address is being verified and check it matches the pending value.

Never put it in a redirect that echoes back. A verification URL that includes a next parameter you redirect to without validation is an open redirect, and it is being sent by you, from your domain, to a user primed to click. Allowlist the destinations.

The prefetch problem

This one catches people by surprise. Corporate mail security gateways, link scanners, and some mail clients fetch every URL in an incoming message to check for malware. If your verification link is a GET that performs the verification as a side effect, the scanner verifies the address before the human ever sees the email. Worse, if the token is single-use, the human's click then fails.

The general fix is to stop performing state changes on an unauthenticated GET. The verification link lands on a page that presents a confirm button, and the actual verification happens on the POST. Scanners follow the GET harmlessly; the human clicks through. It costs one extra click and eliminates a category of mysterious "the link didn't work" reports.

If the extra click is unacceptable for your funnel, the mitigation is to make repeat consumption idempotent — the first consumption verifies, subsequent ones return success — and to accept that a scanned link means the address was verified without human action. Which, given that a scanner having access to the mailbox proves mailbox access just as much, is defensible.

Deliverability is half the feature

You can design a perfect flow and still fail, because the message did not arrive. Verification mail is transactional and should be treated as infrastructure:

  • Authenticate your domain. SPF, DKIM, and a DMARC policy are not optional. Without them, a growing share of providers will not deliver you at all.
  • Send transactional mail from a different subdomain than marketing mail. Reputation is per-domain. A promotional campaign that generates complaints should not be able to sink your signup emails.
  • Warm up gradually. A brand-new sending domain that suddenly emits ten thousand messages looks exactly like a compromised one.
  • Suppress hard bounces immediately. Continuing to send to an address that returned a permanent failure is the single clearest spam signal you can emit.
  • Keep the message boring. One clear call to action, minimal images, a plain-text alternative that actually contains the code and the link. Filters treat image-heavy, link-heavy transactional mail with suspicion.
  • Measure time-to-inbox, not just time-to-send. Your provider reports the message as delivered in 200ms; the user reports it arriving in six minutes. The gap is greylisting and it is invisible unless you instrument it.

The wider treatment of this — including the SMS-specific pathologies — is in why your OTP emails don't arrive.

Changing a verified email

The flow everyone builds and nobody threat-models. If a user can change their email address, the change flow is an account takeover path: an attacker with a hijacked session points the account at their own mailbox and now controls recovery.

The correct shape has three properties. First, the new address is pending until verified — the old one remains the account's address, and remains the recovery channel, until the new one is confirmed. Second, the old address is notified, with a link to revoke the change, because that notification is how a legitimate user learns about a hijacked session. Third, the change requires re-authentication, ideally a fresh password or passkey check rather than merely a valid session, for the same reason step-up authentication exists.

Key takeaways

  • Verification proves mailbox access at one moment in time — nothing more. Design to that claim.
  • Offer a link and a code in the same message; they fail in different situations.
  • Prefer soft gating over hard blocking, and choose the gated actions deliberately.
  • Treat the token as a credential: high entropy, hashed at rest, single use, bound to the address, and safe against link prefetchers.
  • Half of verification is deliverability. Authenticate your domain, separate transactional from marketing, and measure time-to-inbox.
  • The email change flow is an account takeover vector. Pending state, notify the old address, require re-authentication.

EmbedAuth ships verification as a hosted flow with hashed single-use tokens, per-address and per-IP send limits, and a resend path that will not let a user mailbomb themselves — plus customizable templates so the message reads as yours rather than as someone else's infrastructure.

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.