← Blog

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.com passes every regex ever written. Only a DNS lookup for an MX record tells you it's a typo.
  • Is it a disposable address? mailinator.com is real, deliverable, and gone in ten minutes.
  • Is it a role account? info@, support@, and abuse@ 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_mailbox and catch_all as not_performed, so you are never guessing what it covered.

Frequently asked questions

Can a single regex fully validate an email address according to RFC 5322?

No. The full RFC 5322 grammar permits quoted strings, comments, and edge cases so intricate that a truly compliant regex runs to thousands of characters, and even then it only confirms shape, never that the domain exists or accepts mail. The practical move is a sane regex that rejects obvious syntax errors, then a separate check of the domain. Boundstone's email endpoint does exactly this, returning valid_syntax for the format alongside mx_found from a live DNS MX lookup, and it lists both under checks.performed.

If a regex isn't enough, what should I actually check to validate an email?

Validate in layers rather than with one clever pattern: syntax first, then whether the domain publishes MX records so it can receive mail at all, then policy signals like disposable domains, role addresses such as info@ or support@, and free-provider flags. Boundstone runs all of these, so checks.performed is syntax, mx, disposable_list, and role_list, returning valid_syntax, mx_found, disposable, role_account, and free_provider. It does not perform SMTP mailbox probing or catch-all detection and lists those under checks.not_performed, so you always know exactly what a result proves.

Does a 'valid' email result mean the mailbox exists and will receive mail?

No, and any tool that implies otherwise is overselling. A valid result built from syntax plus a live MX lookup means the address is well-formed and the domain is configured to accept mail, but it does not prove that the specific mailbox exists. Boundstone is explicit about this and lists smtp_mailbox and catch_all under checks.not_performed, so valid tells you an address is worth sending to, not that delivery is guaranteed.

Thomas Tsui

Founder of Boundstone — building phone, email and IP validation you can actually verify.

One honest API for email, phone and IP — every response lists what it checked and what it didn't claim to. Free tier: 250 credits/month, no card, credits never expire.

More from Boundstone — API documentation · Benchmark methodology · The benchmark series · Buyer's checklist