The perfect email validation regex is a myth (RFC 5322 and what to do instead)
A short pattern plus a 254-character cap catches typos; everything that decides whether an address can actually receive mail happens after the regex.
Contents
You came here for one thing: an email validation regex you can paste in once and never think about again. Here is the version nobody puts in the gist title — there is no perfect one, there never was, and the most famous candidate is both enormous and still wrong. What you actually want is a short pattern that catches typos, a length cap, and then two or three checks a regex physically cannot perform.
The RFC 5322 regex, and why it disappoints
RFC 5322 is the grammar behind an email address — comments, quoted strings, folding whitespace, the works. The regex that faithfully encodes it runs to roughly 6,400 characters across dozens of lines. It gets passed around as "the one true email regex," and it fails in both directions at once.
It accepts addresses that will never deliver: nobody@nonexistent-domain-xyz.example is perfectly valid syntax and bounces on the first send. It also accepts exotic-but-legal forms most mail servers reject outright — quoted local parts like "a b"@example.com, IP-address literals in the domain. Meanwhile stricter hand-rolled patterns swing the other way and reject addresses that deliver fine, like a plus tag (you+news@example.com) or a long new TLD.
The lesson isn't "write a better regex." It's that a regex only ever answers one question — is this shaped like an email — and shape was never what you were worried about.
A short pattern that is strict enough
Skip the 6,400-character monument. The pattern browsers use for <input type="email"> (from the WHATWG HTML standard) is a sane default, and pairing it with a 254-character cap kills the two things you actually see in the wild: fat-finger typos and pathological input.
import re
# The WHATWG HTML5 pattern — what browsers use for <input type="email">
EMAIL_RE = re.compile(
r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+"
r"@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?"
r"(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$"
)
def looks_like_email(value: str) -> bool:
# 254 is the practical max length of an addr-spec (RFC 5321).
return len(value) <= 254 and EMAIL_RE.match(value) is not None
The same regex in JavaScript, since you'll want it on the client too:
// The WHATWG HTML5 pattern
const EMAIL_RE = /^[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])?)*$/;
const looksLikeEmail = (value) =>
value.length <= 254 && EMAIL_RE.test(value);
For most forms, that is genuinely the end of the story — say so, and don't over-engineer. If a user typos their own address, this catches the missing @ and the trailing space, and <input type="email"> will have caught most of it before your code runs. That's the whole job of client-side email validation: fast feedback, not truth.
The checks a regex can't do
Where the pattern stops is exactly where the interesting questions begin, and none of them are syntax:
- Does the domain even accept mail?
gmial.compasses every regex ever written. Only a DNS lookup for an MX record tells you it's a typo. - Is it a disposable address?
mailinator.comis real, deliverable, and gone in ten minutes. - Is it a role account?
info@,support@, andabuse@are valid but rarely a person.
You can do the first one yourself with a DNS library:
import dns.resolver # dnspython
def has_mx(domain: str) -> bool:
try:
return len(dns.resolver.resolve(domain, "MX")) > 0
except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN):
return False
Disposable and role lists you'd have to source and keep current yourself. For a full Python walkthrough of regex plus MX in one script, see validating email in Python. And for the deeper distinction underneath all of this — checking an address is shaped right versus checking mail actually arrives — see email validation vs verification.
One call, one honest contract
This is the layer Boundstone sits at: the same regex-plus-254 you'd write, plus live MX, plus maintained disposable and role lists, behind one response shape that's identical across every language.
curl -s https://api.boundstone.io/v1/verify/email \
-H "Authorization: Bearer bs_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com"}'
You get back valid_syntax, domain, mx_found, disposable, role_account, and free_provider — and then the part that matters most:
"checks": {
"performed": ["syntax", "mx", "disposable_list", "role_list"],
"not_performed": ["smtp_mailbox", "catch_all"]
}
Read not_performed first. Boundstone does not knock on the mailbox over SMTP, and it does not try to detect catch-all domains — both are listed, on every response, in plain sight. That's deliberate. A "valid" you can trust is one that tells you exactly what it did and didn't verify, so you never mistake "the syntax and MX are fine" for "this inbox exists." Want to try it before writing a line of code? The keyless email validator returns the same contract.
The short version
- There is no perfect email validation regex. Stop hunting for it.
- Use a short pattern plus a 254-character cap. The WHATWG one is fine. It catches typos, which is the whole job of a regex.
- The RFC 5322 monster is both over-permissive and over-strict — pasting it in buys you complexity, not correctness.
- Everything that decides whether mail arrives happens after the regex. MX, disposable, role — do those checks, or call something that does.
- A "valid" is only as good as its checks list. Boundstone lists
smtp_mailboxandcatch_allasnot_performed, so you are never guessing what it covered.