How to Validate an Email Address the Right Way
Why a regex like ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-z]{2,}$ is not enough, what to validate server-side vs client-side, and how to catch the typos that lose you real signups.
Email validation has three layers: (1) shape — the address has an @ and a domain, (2) deliverability — the domain has MX records, and (3) confirmation — the user actually receives the welcome email. A regex like ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-z]{2,}$ handles layer 1 only and is sufficient for the input form; the second two layers happen server-side. Don’t reject valid edge-case addresses at the form; let them through and clean up the mess in the verification email.
Most email validation in client-side JavaScript is a single line of regex, often copied from the first Stack Overflow answer, and often wrong. The regex works for the obvious case ([email protected]) and silently fails on a real human's real email address in a way that costs you signups. Here is what each layer of validation actually does, what to use where, and which "valid" addresses the obvious regex rejects for no good reason.
Layer 1: shape — the regex check
The first thing any form does is check the address has the basic shape of an email: a local part, an @, a domain, and a dot. The minimum useful regex, in plain English:
- One or more characters that are not whitespace or @
- An @
- One or more characters that are not whitespace or @
- A dot
- Two or more letters
Translated to regex, the simplest version is:
^[^\s@]+@[^\s@]+\.[a-zA-Z]{2,}$
That regex matches the overwhelming majority of real email addresses and rejects the obvious garbage (not an email, @example.com, alice@, alice@example). Use this as the form-level check — the goal is to catch typos, not to certify the address is real.
The popular "RFC 5322 compliant" regex is 6,500 characters long and still doesn't cover every valid address. It will reject user@[123.456.789.0] (a valid IP-literal address) and accept [email protected] (which is invalid because of the double dot). It is not worth the readability cost. Stick to the simple regex and validate properly on the server.
What "valid" addresses the simple regex still rejects
There are address formats that are technically valid but the simple regex will turn away at the door. Test them in a regex tester with the simple pattern above:
"alice"@example.com— quoted local part. Valid. Rejecting this loses you a real customer.alice@[123.456.789.0]— IP-literal domain. Rare but valid. Most users won't have one.[email protected]— internationalized domain in Punycode form. The simple regex accepts this; the punycode-encoded form is the actual DNS label.alice@例え.jp— internationalized domain in Unicode form. Needs an additional normalization step before regex.[email protected]— single-letter local part with a 2-letter TLD. Valid. The simple regex accepts this.
The takeaway: the simple regex is generous enough for the 99% case. The forms that go further (Unicode normalization, IDN handling) are solving a problem most sites do not have. If you ship to a global audience, normalize Unicode before the regex check; if you don't, the simple regex is fine.
Layer 2: deliverability — the server-side check
The regex only confirms the address looks like an email. Whether the address actually exists, has a working mail server, and is willing to accept mail is a server-side check. Two parts:
DNS MX records
When the form is submitted, look up the domain part of the address in DNS. If the domain has MX records, mail for that domain is handled by a real server. If it has no MX records but has an A record, the domain accepts mail (fallback to A). If it has neither, the domain cannot receive mail — reject the signup.
This catches typos like [email protected] (a user meant gmail.com but mistyped) and obvious fakes like [email protected]. It is a 5-line DNS query on the server; every serious signup form does it.
Disposable / role / free-mail heuristics
Beyond MX, the next layer is pattern-matching on the local part and the domain to flag addresses you may want to handle differently:
- Disposable domains (mailinator.com, guerrillamail.com, 10minutemail.com) — temporary addresses used to bypass signups. The list is long but stable; subscribe to a maintained blocklist if you care.
- Role addresses (info@, admin@, support@) — not a single person. Useful for B2B contact forms; not useful for personalized emails.
- Free-mail providers (gmail.com, yahoo.com, outlook.com) — fine to allow, but if you need a business contact, ask for a work email separately.
None of these are validation failures. They are routing hints — you can still allow the signup, but you may want to skip the welcome email, add the address to a different list, or surface a different onboarding flow.
Layer 3: confirmation — the welcome email
The only way to know an email is real, deliverable, and monitored is to send mail to it and check that the recipient clicks the confirmation link. This is the third layer, and the one most signup flows get wrong. The pattern:
- User signs up. The form's regex check passes; the server's MX check passes.
- The server creates the account in a "pending" state and sends a confirmation email.
- The user clicks the link in the email. The server flips the account to "active" and starts sending real mail.
Until step 3, the user can sign in but most account features are locked. This protects you from typos like [email protected] (the typo gets the welcome email bounced, the user never confirms, you can clean up the row), from spammers (who rarely click confirmation links), and from typos in your own data (a misspelled email in the database is forever useless; a pending row that expires after 7 days is recoverable).
The confirmation email is also where you discover MX failures your server check missed, graylisting, full inboxes, and over-zealous spam filters. If a confirmation email bounces, you have evidence; if a user signs up with a real-looking address and never confirms, they probably mistyped.
Common mistakes to avoid
Rejecting plus-addressing
[email protected] is the same mailbox as [email protected]. It is how power users filter incoming mail. Rejecting it because your regex is too strict loses you the most engaged users. The simple regex above accepts it correctly.
Lowercasing the local part
Local parts of email addresses are technically case-sensitive per RFC 5321, but in practice all major mail providers treat them as case-insensitive. Storing the lowercased form is fine; rejecting [email protected] as invalid is not. The simple regex accepts both cases.
Trimming whitespace silently
Email addresses don't have spaces, but they often get wrapped in spaces when users copy-paste from a different source. Trim the input before validating. Trimming silently is the right call for the leading/trailing case; trimming in the middle should be a validation failure (because middle whitespace is never valid).
Re-validating on every keystroke
The validation should fire on form submit and on blur (when the user leaves the field), not on every keystroke. Real-time validation is annoying when the user has typed "alic" and the form is screaming red; a friendly check on blur and on submit is much better UX. The simple regex is fast enough to run on every keystroke if you must, but the UX is worse.
Putting it together
For a client-side form, the right validation is:
- Trim leading/trailing whitespace.
- Check the simple regex (
^[^\s@]+@[^\s@]+\.[a-zA-Z]{2,}$). - Show a clear error message on failure; show a green check on success.
- Send the address to the server, which does the MX check and sends the confirmation email.
That is the whole stack. Most signup forms stop at step 2 and call it done; the deliverability and confirmation layers are what separate forms that look polished from forms that actually work in production. Both are 5–10 lines of server-side code and pay for themselves in recovered signups the first time someone mistypes their address.
Email is the rare protocol where the format is forgiving and the deliverability is strict. Validate what the form can see; trust the server to validate what the form cannot.