Email regex generator
Runs in your browser
The patterns are below, in eight languages, and you can copy any of them. First, the part that matters more: you should not use one to decide whether an address is real.
Why a regex is the wrong tool for this
The grammar is not a regular language
RFC 5322 permits comments in an address, and comments nest: user(a(b)c)@example.comis legal. Nesting to arbitrary depth is exactly the thing a regular expression cannot express, which is not a matter of writing a better one. The well-known “RFC-compliant” pattern is about 6 KB long, was generated rather than written, and still does not cover the grammar.
The addresses you would reject are real
A quoted local part is legal: "very.unusual.@.unusual.com"@example.com. So is an address literal for the domain: user@[203.0.113.42]. So is a hostname with no dot in it — user@localhost — and so is a bare public suffix, which is why postmaster@ai is a working address today.
Internationalised addresses go further. 用户@例子.广告 is an address a person can hold, and every pattern on this page rejects it. So does most software, which is a problem the industry has rather than a reason to feel comfortable.
A pattern cannot answer the question you are actually asking
You do not want to know whether an address is well-formed. You want to know whether mail sent to it will arrive. Those are different questions and only one of them is answerable: definitely.not.a.real.person@example.com is perfectly well-formed and will never receive anything.
The only proof is a send. Mail something to it with a link in it and see whether the link is followed. Everything else — syntax checks, MX lookups, mailbox probing — narrows the field without answering.
So what is the pattern for?
Catching a typo before you spend a send on it. Somebody wrote name@gmial and has not noticed; a cheap check in the form tells them now rather than after they have waited for a confirmation that will never come. That is a real and useful job, and it is a smaller one than validation.
Which is why the right shape is: a loose check in the browser, a look, is this what you meant? rather than a refusal, and a confirmation mail that does the actual proving. If your check and your mail disagree, trust the mail.
And if you are writing the check for a signup form, be careful what you reject on purpose. Plus-addressing (name+shop@example.com) is legal, widely used, and blocked by a surprising amount of software specifically to stop people tracking who sold their address — which is the behaviour a per-sender alias exists to make unnecessary.
The patterns
Both are anchored at both ends, and neither has a nested quantifier — so there is no input that makes one of them backtrack pathologically, which matters when the input is something a stranger typed.
Pick a pattern
JavaScript
const EMAIL = /^[A-Za-z0-9_%+-]+(?:\.[A-Za-z0-9_%+-]+)*@(?:[A-Za-z0-9-]+\.)+[A-Za-z]{2,}$/;
EMAIL.test(value);Use test() rather than match(), and never with the /g flag on a shared regex — a global regex keeps lastIndex between calls and will return false every other time.
What it does with real addresses
Run in your browser, against the pattern above, just now. The last group is the honest part: these addresses are legal and this pattern turns them away.
Accepted
- a@example.commatches this pattern
- first.last@example.commatches this pattern
- user+tag@example.commatches this pattern
- user_name@example.commatches this pattern
- 123@example.commatches this pattern
- user@mail.example.co.ukmatches this pattern
- user@example.technologymatches this pattern
Rejected, correctly
- plainaddressdoes not match this pattern
- @example.comdoes not match this pattern
- user@does not match this pattern
- user name@example.comdoes not match this pattern
- user@exam ple.comdoes not match this pattern
- user@@example.comdoes not match this pattern
- user@example..comdoes not match this pattern
Rejected by this pattern, accepted by the browser one
- user@exampledoes not match this pattern
- user@localhostdoes not match this pattern
- postmaster@aidoes not match this pattern
- .user@example.comdoes not match this pattern
- user.@example.comdoes not match this pattern
- user..name@example.comdoes not match this pattern
Legal addresses this turns away
- "very.unusual.@.unusual.com"@example.comdoes not match this pattern
- user@[203.0.113.42]does not match this pattern
Three ways to get this wrong in code
^ and $ do not mean end-of-string everywhere
In Ruby they match at a line break, always — there is no flag to turn that off. So /^…$/ happily accepts "you@example.com\nBcc: everyone", and if that value then reaches a mail header you have header injection. Ruby needs \A and \z. In .NET, $ matches before a trailing newline, so C# needs \z too. The snippets above use the right anchors per language.
A global regex remembers where it stopped
In JavaScript a regex with the g flag keeps lastIndex between calls. Share one across a form and EMAIL.test(value) returns true, then false for the same input, then true again. It is a genuinely confusing bug and the fix is to not use g for a test.
Normalise before you compare
The domain half is case-insensitive; the local part, formally, is not — though in practice every provider treats it as though it were. If you are storing addresses, lower-case the domain and decide explicitly what you do with the local part, rather than discovering later that two accounts exist for one person.
One of the free email tools.