A regex can only validate the shape of an email address — not whether it exists or can receive mail. For almost every real-world form, the right approach is a deliberately simple pattern such as ^[^\s@]+@[^\s@]+\.[^\s@]+$ (or the slightly stricter ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$) to catch obvious mistakes, plus a confirmation email as the only definitive proof the address is real. Chasing full RFC 5322 compliance with a regex is impractical: the pattern balloons to hundreds of characters, still gets edge cases wrong, and rejects valid addresses while accepting ones your application can never actually deliver to.
That's the summary an AI overview gives you. The part it can't give you is why the simple pattern is the correct engineering choice, which real addresses the "clever" regexes silently reject, and the exact rule browsers already use for you. Get this wrong in one direction and spam floods your database; get it wrong in the other and you bounce paying customers at the signup box.
Anatomy of an email address
Every validation decision comes back to two parts split by a single @: the local part (the mailbox) and the domain (where mail is delivered). The rules for each are looser than most people assume.
The local part is remarkably permissive: it can contain letters, digits, dots, and a set of special characters including +, _, %, and -. The +news fragment above is a sub-address tag — a real, deliverable address that many people use to filter mail. The domain is one or more DNS labels ending in a top-level domain that today can be anything from .com to .museum to .engineering. Any assumption stricter than that is where validators start rejecting real users.
Which regex should I actually use?
There is no single correct email regex — only trade-offs between how many typos you catch and how many real addresses you wrongly reject. This table maps the common choices to what each one catches and misses so you can pick deliberately.
| Regex approach | Pattern (JavaScript literal) | What it catches | What it misses / wrongly rejects | Use when |
|---|---|---|---|---|
| Minimal / permissive | /^[^\s@]+@[^\s@]+\.[^\s@]+$/ | Missing @, missing domain, missing dot, spaces | Accepts some malformed strings (double dots, weird chars) — but you'll verify by email anyway | Newsletter signups, low-risk forms, anywhere you send a confirmation |
| Practical / balanced (recommended) | /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/ | The above plus enforces a plausible local part and a 2+ letter TLD | Some valid quoted/exotic local parts; internationalized (Unicode) addresses | Most registration and contact forms |
| HTML5 / WHATWG (browser built-in) | <input type="email"> (see pattern below) | Enforces the same grammar every major browser uses; free, no JS | ASCII only — rejects internationalized email; no deliverability check | Client-side instant feedback in any HTML form |
| RFC-ish / stricter | Long domain-label pattern (see below) | Constrains each DNS label to valid length/characters | Overkill for validation; harder to read; still no deliverability | You genuinely need tighter domain-shape rules server-side |
| "Full RFC 5322" | 400+ character monster | Almost every legal address | Unreadable, unmaintainable, accepts addresses you can't route, still proves nothing about existence | Essentially never — use a library instead |
| Confirmation email | (not a regex) | Proves the address exists and the person owns it | Nothing about format — run a regex first | Always, for any address that matters |
The row that actually matters is the last one. Every regex above validates shape; only the confirmation email validates reality.
The practical pattern, explained
^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
^— start of string[a-zA-Z0-9._%+-]+— local part: letters, digits, and._%+-(note+is allowed, so plus-tags pass)@— the literal separator[a-zA-Z0-9.-]+— domain: letters, digits, dots, hyphens\.— the final literal dot before the TLD[a-zA-Z]{2,}— a top-level domain of 2 or more letters (not capped at 3 — that cap is a classic bug)$— end of string
function isEmailShaped(email) {
return /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(email);
}
import re
EMAIL_RE = re.compile(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$')
def is_email_shaped(email: str) -> bool:
return bool(EMAIL_RE.match(email))
Want to experiment with these patterns against your own test addresses before shipping them? Try them live:
Let the browser do it: HTML5 type="email"
Before you write any regex, remember the browser already ships one. <input type="email"> validates against the WHATWG "valid email address" grammar in Chrome, Firefox, Safari, and Edge:
<form>
<input type="email" name="email" required />
<button>Sign up</button>
</form>
The specification is refreshingly honest about this: it calls its own grammar a deliberate "willful violation" of RFC 5322, chosen because it is simpler and more useful than the real standard. The regex the browser effectively applies is:
^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$
If you want a pattern attribute for older setups or to tighten things, you can paste that in — but for most cases the bare type="email" is enough. Its one notable limitation: it accepts ASCII only, so it rejects internationalized (Unicode / EAI) addresses like 用户@例え.jp. That is a reasonable default, but worth knowing if you serve a global audience.
The over-strict trap: regexes that reject real people
The most damaging validation bug isn't accepting junk — it's silently rejecting valid addresses at your signup form. These are the patterns that cost you real users:
- Rejecting the plus sign.
john+newsletter@gmail.comis completely valid and extremely common. Any regex without+in the local part is broken. - Hard-coding old TLDs. Patterns like
(com|net|org|edu)$or a{2,3}length cap on the TLD reject.io,.dev,.museum,.engineering, and hundreds of newer top-level domains. TLDs can be up to 63 characters. - Disallowing uppercase.
^[a-z]+@...bouncesSarah.Connor@Example.com. Email addresses are case-insensitive in practice. - Forbidding subdomains. Users at
name@mail.company.co.ukare real; a pattern that only allows one dot in the domain rejects them. - Blocking dots or hyphens in the domain. Breaks the majority of corporate and country-code addresses.
The safe rule of thumb: when unsure, be more permissive, and let the confirmation email be your strict gate. A false rejection is a lost signup you never even hear about.
What a regex fundamentally cannot do
No regex — not the simple one, not the 400-character RFC beast — can answer the only question that ultimately matters: will mail reach this person? Regex cannot verify:
- That the domain exists —
user@notarealdomain12345.compasses every pattern. - That a mail server is running and reachable for that domain.
- That the mailbox exists —
nonexistent@gmail.comis perfectly well-shaped. - That the person owns the address they typed (or didn't fat-finger someone else's).
- Internationalized addresses — Unicode local parts and IDN domains need dedicated handling, not a character class.
typo@exmaple.com ← passes every email regex, reaches no one
This is why format validation is step one of three, never the whole job.
The validation that actually works: three layers
Layer 1 — Format (client + server). Use HTML5 type="email" for instant feedback and a small regex as backup. This catches sarah@gmial and empty fields before they waste a round trip.
Layer 2 — Server-side validation. Never trust the client; it can be bypassed. Re-run the format check on the server. For registration, avoid revealing whether an address is already in use (user-enumeration risk) — behave the same whether it exists or not.
Layer 3 — Confirmation email. Send a one-time verification link and only mark the address verified when the user clicks it. This is the single step that proves the address exists and that the person entering it can read it. For anything that matters — accounts, billing, password resets — it is non-negotiable.
Prefer a library over hand-rolled regex
For real applications, a maintained library handles far more edge cases than a regex you wrote in five minutes — and several can also check that the domain has MX records:
- JavaScript:
email-validator, or the email rules injoi/yup/zod - Python: the
email-validatorpackage (also checks deliverability signals) - PHP: built-in
filter_var($email, FILTER_VALIDATE_EMAIL) - Java: Apache Commons Validator's
EmailValidator
Use the library for format, then still send the confirmation email for the part no library can verify.
The bottom line
Use a simple, permissive regex (or plain HTML5 type="email") to catch obvious typos, resist the urge to make it "stricter," and rely on a confirmation email as the only real proof of a working address. A regex validates that a string looks like an email; it never validates that the email is real. Chase the shape with a readable pattern, prove reality with a verification link, and reach for a battle-tested library before you reach for a 400-character regex you'll never be able to debug.