"What's the best regex for validating email?" is one of the most-asked programming questions of all time. The honest answer: a regex can only check the shape of an address, and the shape is the least important part of whether an email will be delivered.
This post covers what regex can and can't do, a regex that's good enough, and what to use for everything regex can't see.
Why "perfect" email regexes don't exist
The official grammar (RFC 5321/5322) allows things you'd never expect in a form:
"john..doe"@example.com quoted local part with consecutive dots
user+tag@example.com plus addressing
user@[192.168.2.1] IP-address literal
very.unusual."@".unusual.com@example.com
Regexes that try to cover the whole spec run to hundreds of characters, are impossible to review, and still disagree with real mail servers. Meanwhile, simple regexes reject valid addresses people actually use, such as o'brien@example.ie or long new TLDs like .photography.
A regex that's good enough
For a form, you want to reject obvious garbage and let the server-side check handle the rest:
const EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
It requires something before the @, a domain with a dot, and a TLD of at least 2 characters, with no spaces. It's intentionally permissive. Browsers do something similar for <input type="email">.
In Python, prefer a real parser over a regex. email-validator (used by Pydantic's EmailStr) handles internationalised addresses correctly:
from email_validator import validate_email, EmailNotValidError
try:
info = validate_email("Jane.Doe@Example.com", check_deliverability=False)
print(info.normalized) # Jane.Doe@example.com
except EmailNotValidError as e:
print(e)
Try your own inputs in the email syntax checker, which also catches domain typos like gmial.com.
What regex can't tell you
Every one of these addresses passes any reasonable regex:
| Address | Problem | Regex sees it? |
|---|---|---|
jane@gmial.com |
Typo in a popular domain | No |
jane@acme-corp-old.com |
Domain has no mail server (no MX) | No |
j.doe@acme.com |
Mailbox doesn't exist | No |
x7k2@mailinator.com |
Disposable address | No |
anything@catchall-corp.com |
Catch-all: can't be confirmed | No |
info@acme.com |
Role address, not a person | No |
Each of those is either a bounce, a fake signup or wasted outreach, and together they're most of the bad addresses in a real list.
The layers of real validation
- Syntax: regex or parser. Free, instant, local.
- Domain: does the domain exist and have MX records? (Try the MX lookup.)
- Known bad providers: disposable and spamtrap domains.
- Mailbox: ask the mail server whether the recipient exists, without sending an email.
- Catch-all detection: does the server accept every address, making step 4 meaningless?
Steps 1 to 3 can be done yourself with DNS and a maintained list. Steps 4 and 5 need SMTP conversations from well-reputed IPs, careful rate limiting and constant upkeep, because providers throttle and block verifiers. That's why most teams use an API for them.
Using an API for layers 2 to 5
curl -X POST https://www.mailrambo.com/v1/verify \
-H "Authorization: Bearer $MAILRAMBO_KEY" \
-H "Content-Type: application/json" \
-d '{"email": "j.doe@acme.com"}'
{ "email": "j.doe@acme.com", "deliverable": false, "reason": "mailbox_not_found", "credits_remaining": 48 }
You still run your regex first. It's free, it gives instant feedback in the browser, and MailRambo doesn't charge for addresses that fail syntax anyway.
Recommended setup
- Browser:
type="email"plus a light regex, for instant feedback only. - Server: a parser for syntax, then one API call for everything else.
- Decision: block
mailbox_not_found,disposableandinvalid_syntax. Decide explicitly aboutcatch_all: allow it at signup, exclude it from outreach.
Summary
Regex answers "does this look like an email?" It can't answer "will this email be delivered?" Use a permissive regex for instant feedback, and a verification API for the checks that actually prevent bounces and fake signups.
Test any address with the free email verifier.