Security

How to verify an email address at signup

September 26, 2026 · Updated September 30, 2026

Accept the address after a light format check, then prove the user can receive mail at it by sending a single-use link. Until that link comes back the address is unverified, and nothing that depends on it should work.

Two states side by side. Unverified: logging in and requesting a password reset are allowed, while any mail to the address, billing, publishing, inviting people, issuing API keys and showing the address to other users are blocked. Between them, a single use link of 32 or more characters, valid about eight hours, confirmed on the POST and never on the GET. Verified: everything above plus notifications, billing, publishing and invitations, with a later address change starting the same cycle and mailing a cancel link to the old address.

Checking the format

The OWASP Input Validation Cheat Sheet calls email syntax under RFC 5321 "far more complicated than most people realise". A regular expression strict enough to be correct rejects addresses that work, and one loose enough to accept them all validates nothing.

OWASP's recommendation is basic validation followed by the mail server's own verdict: do a light check, then hand the address to the mail server and catch the rejection. A light check confirms four things before the address gets that far:

  • One @, with something on each side

  • A domain of letters, numbers, hyphens and periods

  • No backticks, quotes or null bytes

  • Local part at most 63 characters, whole address at most 254

Accept what looks odd but is legal. A plus sign before the @ is how people tag their own mail, and an apostrophe belongs in plenty of surnames. An unfamiliar top-level domain is usually a new registry your regular expression has never heard of.

The token

The verification link carries a token with the same properties as a reset token, and for the same reasons, covered in How to build a password reset flow:

PropertyValue
LengthAt least 32 characters, OWASP's floor for this token
SourceThe operating system's cryptographic random source
UseOnce, then deleted or marked consumed
LifetimeOWASP suggests around eight hours, which suits mail people read in the evening
At restStored as a hash, so a copy of the database holds no live links
ScopeOne account, and issuing a new one invalidates the previous

A longer window than a reset token is the right call here. A reset token is a way into an account and expires in 30 minutes. A verification token confirms a mailbox, and an expiry that beats the user to their inbox produces a support message and no security benefit.

Serve the link over HTTPS to a page with a confirm button, and do the verification on the POST. A link that acts on GET is consumed by whatever opens mail before the user does: scanners, link previews, antivirus fetchers. Each of those marks the address verified with no human involved, which defeats the one thing the step was for.

Set Referrer-Policy: no-referrer on that page and load nothing from a third party on it, so the token is not handed to anyone's analytics.

Let the user ask for another one. The resend button is rate limited by account and by address, per How to rate limit a login page, because a resend costs you an email and costs an attacker using your form as a mailer nothing.

What to gate on verification

ActionBefore the address is verified
Logging inAllowed. A user who mistyped their address needs to get in to fix it
Marketing or notification email to that addressBlocked, until the address is proven
Billing, publishing, inviting other people, issuing API keysBlocked. These are the actions where a wrong address becomes someone else's problem
Showing the address to other usersBlocked
Requesting a password resetAllowed. It goes to the address on file either way

Show the state as a banner with the resend button in it, on every page, and not as a modal the user dismisses once and never sees again.

Saying nothing about who is registered

The verification response is one sentence whatever happened:

If that address can receive mail, a link is on its way.

The signup form is the exception, and a deliberate one. We tell the user when an address is already registered, because a signup form that accepts a taken address and continues produces a confused person with no account. That leaves registration as an enumeration point by design, which is the decision recorded in How to write a login error message, alongside the alternatives if you would rather not take it.

Changing the address later

The new address gets verified before it replaces the old one, or an attacker who has taken a session changes the address, verifies nothing, and owns the recovery path.

  1. Store the new address as pending, leaving the current one in place.

  2. Send the verification link to the new address.

  3. Send a notice to the old address saying a change was requested, with a link that cancels it.

  4. Swap them when the new address confirms, and email both.

  5. Require the password, and a second factor if one is enrolled, before step 1.

A sequence between your app, the new address and the old one. The password and second factor come first. A verification link goes to the new address while the current one stays in place, a notice with a cancel link goes to the old address, and the two swap only once the new address confirms. Skipping the pending state hands every future reset to whoever has taken one session.

What to leave out

  • Do not sign the user in from the verification link. A link that signs them in is a magic link, which is an authentication mechanism with its own token rules and its own expiry.

  • Do not expire it in 15 minutes. People read mail late, and the resend loop you are forcing on them is worse than the longer window.

  • Do not delete unverified signups without saying so. If you clear them after 30 days, the page says 30 days.

  • Do not block the login until verification, unless a regulator requires it. The cost lands on the user who typed one letter wrong, and they cannot correct it from outside.

Sources