Skip to content

Blog

Email validation regex: the pattern and its limits

The pattern worth using, the valid addresses the popular ones reject, and the one question no regular expression can answer.

VerifyInbox · published 11 September 2026 · 4 min read

If you only want the pattern: use the one the HTML standard defines for input[type=email], reproduced below. It is the most widely implemented email pattern in existence, it is what every browser already applies to an email input, and agreeing with the browser is worth more than being cleverer than it.

The rest of this page is about what that pattern does and does not do, which matters because a validation rule that rejects a real customer’s address is a bug that never gets reported — the customer just leaves.

The pattern

The HTML Standard’s definition of a valid e-mail address
const EMAIL =
  /^[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])?)*$/;

The specification is explicit that this is a willful violation of RFC 5322: it is deliberately both stricter and more permissive than the standard grammar, because the full grammar is impractical in a form field and because browsers needed one rule they could all implement identically. Knowing that it is a deliberate compromise is most of what you need to use it well.

What the common patterns get wrong

Here is that pattern beside the kind of expression that circulates on forums, run over eight addresses. Every result below is from an actual run under Node 22.

rx.mjs
const NAIVE = /^[A-Za-z0-9._%-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/;
const HTML5 =
  /^[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 cases = [
  "[email protected]",
  "[email protected]",
  "[email protected]",
  "o'[email protected]",
  "jane@münchen.de",
  "[email protected]",
  "[email protected]",
  "jane@example",
];

console.log("address".padEnd(30), "naive", " html5");
for (const c of cases)
  console.log(c.padEnd(30), String(NAIVE.test(c)).padEnd(5), String(HTML5.test(c)));

Output

address                        naive  html5
[email protected]           true  true
[email protected]    false true
[email protected]        false true
o'[email protected]            false true
jane@münchen.de                false false
[email protected]         true  true
[email protected]          true  true
jane@example                   false true

Read that table row by row, because almost every row is a decision someone made.

  • jane+newsletter@ — valid, widely used, rejected by the naive pattern because + is missing from its local-part class. This is the single most common false rejection in the wild.
  • [email protected] — valid, rejected by the {2,4} TLD assumption. That assumption was reasonable before the new gTLD programme and has been wrong ever since.
  • o'[email protected] — valid; apostrophes are legal in a local part and real people have them in their names. Rejected by the naive pattern, accepted by the standard one.
  • jane@münchen.de — rejected by both, correctly for the HTML pattern: an internationalised address needs RFC 6530 handling, not a wider character class. Convert the domain to punycode first.
  • [email protected] — the same address after punycode conversion, and now both accept it. This is the practical route: normalise, then validate.
  • [email protected]accepted by both, and invalid. Consecutive dots are not permitted in an unquoted local part. The HTML pattern lets it through by design; if this matters to you, a parser is the answer, not a longer expression.
  • jane@example — a bare hostname with no dot. Accepted by the HTML pattern deliberately, because intranet addresses are real. Almost certainly wrong on a public signup form, so add a dot requirement there if you want one — as a separate rule, not by editing the pattern.

The failure mode nobody mentions

Several widely copied "RFC-compliant" email patterns contain nested quantifiers over overlapping character classes. On a crafted input those backtrack exponentially, and a validation routine becomes a way to pin a CPU core — from a text field, unauthenticated, which is about the most convenient denial-of-service surface a web application can offer.

The pattern above does not have that shape. If you write your own, check it against a long non-matching input before it goes anywhere near a request handler, and prefer a parser for anything complicated.

Where a regex is the right tool

Narrowly, and usefully: catching a typo in a form field before the user submits it, in the browser, with an error message they can act on. That is a genuine improvement to a signup flow and the HTML pattern does it for free — a plain <input type="email" required> applies it with no JavaScript at all.

Everywhere else, use a parser. In Python that is email-validator; in JavaScript it is validator.js or the email check in your schema library; in most other ecosystems there is one obvious choice. All of them handle the grammar, and all of them give you a normalised address, which a regex never does.

What no pattern can do

A regular expression operates on a string. The question you actually have — will mail to this address arrive — is a question about the state of somebody else’s mail server, and no amount of pattern matching reaches it.

What each layer can establish
LayerQuestionTool
SyntaxIs this well-formed?A regex, or better, a parser
Domain / MXCan this domain receive mail?A DNS lookup
MailboxDoes this mailbox exist?An SMTP conversation with the receiving server

[email protected] passes every pattern on this page and passes a DNS check too. It is almost certainly not a mailbox. Only the third layer distinguishes them, and it produces three answers rather than two — deliverable, undeliverable, and unknown for when the server declined to say.

If you have an address that passed your pattern and you want to know whether it is real, run it through the verifier. The Python route, with runnable code, is in validating an email address in Python.

Terms used on this page

Sources

Primary sources checked 11 September 2026. If something here is out of date, tell us and we will correct it.

More: How to find someone's email address · How to validate an email address in Python · Gmail, Yahoo and Microsoft sender requirements · How to block disposable email addresses

Questions

Is regex enough for email validation?

For catching a typo in a form field, yes. For deciding whether to send, no. A pattern tells you a string is well-formed; it cannot tell you the domain accepts mail or that the mailbox exists, and those are the two things that decide whether a message arrives.

How do I validate an email address using RegExp in JavaScript?

Use the pattern the HTML standard defines for input[type=email] — the one every browser already applies. In a form you get it for free with <input type="email" required>. In code, use validator.js or your schema library rather than maintaining a pattern yourself.

How do I verify that strings are in a valid email format?

Parse rather than match. A parser handles quoted local parts, internationalised addresses and the normalisation a regex cannot do, and it gives you back a canonical form you can store. A pattern is for a quick client-side check only.

What is the difference between syntax validation and deliverability validation?

Syntax validation asks whether the string is a well-formed address, offline. Deliverability validation usually means a DNS check on the domain. Neither establishes that the mailbox exists — that needs an SMTP conversation with the receiving server.

Can an email sent to a "valid" address bounce?

Constantly. Valid means well-formed. The mailbox can have been deleted, be over quota, or never have existed — and on a catch-all domain the server accepts every address regardless, so even a positive SMTP result describes the domain rather than the mailbox.

Ask the mail server, not a pattern

100 credits every month, free and renewing. Three verdicts, never two, with the SMTP evidence attached to each one.