Skip to main content
ContactLintAPIMCPPricingFree toolsCompareGet a key

Home / Email Validation Regex: What Works, What Breaks, and What It Costs in 2026

Email Validation Regex: What Works, What Breaks, and What It Costs in 2026

Published · Updated

Email this to yourself

By John Alewine, founder of ContactLint (LinkedIn). Tests run October 31, 2026. Markdown copy: /email-validation-regex.md.

What is the best regex for email validation?

An email validation regex checks that a string looks like an email address; for a form, the best one is a short, permissive pattern plus length limits. None of the 13 patterns we tested got all 24 addresses right. A regex cannot tell you if the domain exists, the address is a typo, or the mailbox is real.

Bottom line

If you want to see what a check finds after the regex passes, paste an address into our free tester, or get a free key with 100 checks a month and no card.

Key facts

What an email validation regex is, and what it is not

A regular expression is a pattern for matching text. Email validation by regex uses one to check format only: is there one @, a sensible local part before it, and a domain after it that looks like a domain. It is cheap and instant, and that is where its job ends.

A regex is not verification: it cannot tell you a mailbox exists. It is not a DNS check, so it cannot tell you the domain can receive mail. It is not typo detection: jane@gmial.com is a correctly shaped email address. And it is not consent. A well-formed string says nothing about who owns it or wants your mail. Treat it as the first, cheapest filter.

How an email address is built, and what forms should accept

An email address has three parts: a local part, the @ sign, and a domain. RFC 5321, the SMTP standard, defines the local part as a dot-string (atoms joined by single dots) or a quoted string, and domain labels as letters, digits and hyphens. RFC 5322 also allows comments and whitespace, as the WHATWG spec points out. RFC 5321 also allows IP address literals such as user@[192.0.2.1].

A signup form should not accept those rare forms. RFC 5321 itself says hosts should avoid defining mailboxes that need the quoted-string form. The WHATWG spec makes the same call for browsers.

The regex passed. Now what?

ContactLint checks what comes after the pattern: syntax, MX and A records, disposable domains, role addresses like admin@, and "did you mean" typo suggestions. You get 100 free checks a month, then $9 for 5,000.

Get a free API key

PartRule and limit
Local partLetters, digits and symbols such as + ' % _ - . with no leading, trailing or double dots. Max 64 octets.
@Exactly one.
Domain labelLetters, digits, hyphens. No underscore. WHATWG also limits each label to 63 characters with no leading or trailing hyphen.
DomainMax 255 octets. For a public form, require a dot and a top-level label of 2 or more letters.
Whole addressMax 254 octets, because the 256-octet path includes the angle brackets (RFC 3696 erratum 1690).
CaseDomain is not case-sensitive. The local part may be, so store it as typed.
Non-ASCIIUTF-8 local parts need the SMTPUTF8 extension (RFC 6531). Non-ASCII domains can always be sent as punycode (A-labels), like xn--bcher-kva.example.

Two test addresses sit in a gray zone. jane@example has no dot, and RFC 5321 allows only fully qualified domains in public SMTP. jane@example.c is well formed, but its domain has no MX or A record. Both are rejects for a public signup form, and we scored them that way.

Copy-paste email regex for JavaScript, Python and HTML

HTML: input type=email (WHATWG pattern)

The browser applies this rule for type=email. Below is the spec's pattern as a JavaScript string; \x60 stands in for the backtick in the spec's character class and matches the same character.

<input type="email" name="email" required
       maxlength="254" autocomplete="email">

const WHATWG = new RegExp(
  "^[a-zA-Z0-9.!#$%&'*+\\/=?^_\\x60{|}~-]+@" +
  "[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])?)*$"
);

Score: 18 of 24 alone, 19 of 24 with the length check. It never rejected a valid address in our set. It accepted double dots, leading and trailing dots, jane@example and jane@example.c. Repeat it on the server.

JavaScript: a permissive pattern plus length checks

We recommend Colin McDonnell's pattern plus explicit length checks: 23 of 24 in our test. Its one miss is user%tag@example.com, since % is not in its set. If you never want to block a real person, use the classic simple pattern: 16 of 24 with the length check, zero valid addresses rejected, the rest left to the API and confirmation email.

const EMAIL = new RegExp(
  "^(?!\\.)(?!.*\\.\\.)" +
  "([a-z0-9_'+\\-\\.]*)[a-z0-9_+\\-]" +
  "@([a-z0-9][a-z0-9\\-]*\\.)+[a-z]{2,}$",
  "i"
);
const SIMPLE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

function lengthOk(email) {
  const at = email.lastIndexOf("@");
  return at > 0 && at <= 64 && email.length <= 254;
}

function looksValid(raw) {
  const email = raw.trim();
  return lengthOk(email) && EMAIL.test(email);
}

Python: re.fullmatch with the same pattern

import re

EMAIL_RE = re.compile(
    r"^(?!\.)(?!.*\.\.)"
    r"([a-z0-9_'+\-\.]*)[a-z0-9_+\-]"
    r"@([a-z0-9][a-z0-9\-]*\.)+[a-z]{2,}$",
    re.IGNORECASE | re.ASCII,
)

def looks_valid(raw: str) -> bool:
    email = raw.strip()
    local, _, _ = email.rpartition("@")
    if not 1 <= len(local) <= 64 or len(email) > 254:
        return False
    return EMAIL_RE.fullmatch(email) is not None

Three details matter more than the pattern. Anchors: without ^ and $ (or fullmatch), a pattern can match an address buried in junk. In Python, $ also matches just before a trailing newline (see the re module docs), so re.match with $ lets "jane@example.com\n" through, while re.fullmatch rejects it. Case: domains are not case-sensitive, so use the i flag or re.IGNORECASE; the RFC 5322 pattern we tested has no flag and rejected USER@EXAMPLE.COM. Trim first: pasted addresses often carry a space or newline. Use re.ASCII in Python so \w matches only ASCII characters, as it does in JavaScript.

Method: we collected 12 published email regexes developers copy, each from a page we opened (six from emailregex.com, plus the WHATWG spec and five other sources), and added the classic simple pattern, for 13 patterns in all. We ran each as a full match against 24 fixed addresses (12 a public signup form should accept, 12 it should reject) in Python 3.11.17's re module, with re.ASCII so \w stays ASCII-only, as in JavaScript. We applied case-insensitivity only where the source said so, and tested Microsoft's pattern without its punycode mapping step. One minimal translation: regexr's [\w-\.] is invalid in Python, so we wrote the same set as [\w.-]. Node.js 22 gave the same scores for the three patterns we recommend. Tested October 31, 2026. We also timed each pattern on "a"*50000+"@", "a."*25000 and "a"*50000+"@a" with a 5-second hard timeout. Rows marked "plus our length check" add a 64/254 limit to a pattern. The ContactLint row is our own product's syntax step, measured by us in our repo. Numbers in parentheses are pattern length in characters. Limits: syntax only, not deliverability, and 24 addresses is a small deliberate set, not real traffic.

Pattern sourceCorrect of 24, and what it got wrong
ContactLint syntax step (our own product, measured by us in our repo)23. Passed jane@example.c; our DNS step then marks it invalid (no_mx_or_a).
Colin McDonnell plus our length check23. Rejected user%tag@example.com.
emailregex.com "Javascript" plus our length check23. Accepted jane@-example.com.
emailregex.com "Javascript" (154 characters)22. Accepted jane@-example.com and the 65-octet local part.
Colin McDonnell, "An email regex for reasonable people" (82)22. Rejected user%tag@example.com. Accepted the 65-octet local part.
emailregex.com "General Email Regex (RFC 5322)", on a page headlined "99.99% Works" (428)21. Rejected USER@EXAMPLE.COM. Accepted jane@example.c and the 65-octet local part.
OWASP Validation Regex Repository (76)20. Rejected o'brien@, user%tag@. Accepted jane@-example.com, 65-octet local part.
WHATWG plus our length check19. Accepted the three dot errors, jane@example and jane@example.c.
emailregex.com ".NET" (46)19. Rejected punycode and user%tag@. Accepted jane@example.c, the underscore domain, 65-octet local part.
WHATWG HTML Standard, input type=email (133)18. No valid address rejected. Accepted three dot errors, jane@example, jane@example.c, 65-octet local part.
emailregex.com "Ruby (simple version)" (46)18. Accepted six bad addresses, including "jane doe@". Backtracking: did not finish within 5 seconds.
GeeksforGeeks tutorial pattern (48)17. Rejected o'brien@. Accepted six bad addresses, including all three dot errors.
emailregex.com "HTML5" copy, printed with a curly apostrophe (73)16. Rejected o'brien@. Accepted seven bad addresses.
Classic simple pattern plus our length check16. Rejected nothing valid. Accepted eight bad addresses.
emailregex.com "Python" (50)15. Rejected o'brien@ and user%tag@. Accepted seven bad addresses.
Microsoft .NET docs IsValidEmail regex (26)15. Rejected nothing valid. Accepted nine bad ones, including "jane@example.com>".
Classic simple ^[^\s@]+@[^\s@]+\.[^\s@]+$ (26)15. Same as Microsoft's: nine bad addresses accepted.
regexr.com/3e48o (32)12. Rejected six valid addresses, including plus, apostrophe, .museum and .photography. Accepted six bad ones.
AddressPatterns that got it wrong
65-octet local part @example.com13 of 13
jane@-example.com9 of 13
jane.@example.com8 of 13
jane@example.c8 of 13
jane..doe@example.com7 of 13
.jane@example.com7 of 13
o'brien@example.com5 of 13
user%tag@example.com5 of 13
jane@example..com4 of 13
jane@exam_ple.com4 of 13
jane+news@example.com1 of 13

What it means: none of the 13 patterns enforced the 64-octet local-part limit; only our length-check rows and ContactLint's syntax step did. So length checks belong in code next to any regex. Longer was not better: the 428-character RFC 5322 pattern scored below an 82-character one. Short is not automatically safe either: our one ReDoS risk was 46 characters.

If an AI assistant wrote your email regex

If a chat assistant or coding tool gave you an email regex, treat it like any pattern copied from the web. We did not test what any AI tool produces, so we make no claim about its output. Test the pattern you were given, not the tool. Paste it into this Node.js harness. It uses 11 of our 24 addresses, mostly the ones that broke the most patterns, plus a backtracking check.

// Paste the regex you were given here:
// Drop any g flag: it makes .test() stateful.
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

const shouldPass = [
  "jane+news@example.com",
  "o'brien@example.com",
  "user@xn--bcher-kva.example",
  "USER@EXAMPLE.COM",
  "user%tag@example.com",
];
const shouldFail = [
  "jane..doe@example.com",
  ".jane@example.com",
  "jane@-example.com",
  "jane@example.c",
  "a".repeat(65) + "@example.com",
  "jane@example.com>",
];

let misses = 0;
for (const s of shouldPass) {
  if (!re.test(s)) {
    misses++;
    console.log("REJECTED", s);
  }
}
for (const s of shouldFail) {
  if (re.test(s)) {
    misses++;
    console.log("ACCEPTED", s);
  }
}
console.log("misses:", misses, "of 11");

// Backtracking check: time should stay near 0 ms
for (const n of [24, 28, 32]) {
  const t = Date.now();
  re.test("a".repeat(n) + "@");
  console.log(n, "chars:", Date.now() - t, "ms");
}

Any REJECTED line is a real customer turned away; fix those first. ACCEPTED lines are cheaper, since an API check and a confirmation email catch them later. If the timing climbs at each step, replace the pattern: steady growth from one size to the next is the warning sign. An AI agent that saves contacts can skip the regex and call a validation tool, such as validate_email on our hosted MCP server, before it writes to a CRM.

How to validate emails on a form, step by step

1. Trim the input and lowercase the domain

Strip spaces and newlines. Lowercase the domain. Keep the local part as typed, since RFC 5321 lets hosts treat it as case-sensitive.

Time: 5 minutes.

Done when: " Jane@Example.COM " is stored as "Jane@example.com".

2. Check the length limits

Reject a local part over 64 octets or an address over 254. Count bytes if you accept non-ASCII.

Time: 10 minutes.

Done when: the 65-octet local part from our test set fails on the client and the server.

3. Run the permissive regex on client and server

Use type=email in the browser and a tested pattern on the server. Anyone can skip the client check.

Time: 30 minutes.

Done when: the harness above shows no REJECTED lines.

4. Call a validation API for MX/A, disposable, role and typo signals

On submit, send the address to an API that checks DNS, flags disposable and role addresses, and suggests typo fixes. Our guide to validating email on signup has server code, and our pricing page shows what a check costs.

Time: 1 to 2 hours.

Done when: a domain with no MX or A record is blocked and disposable addresses follow your policy.

5. Show "did you mean" instead of blocking

Show the suggested fix under the field with a one-click accept. Do not block the form. See the common domain typos it catches.

Time: 1 hour.

Done when: jane@gmial.com shows a suggestion and the form still submits.

6. Send a confirmation email for anything that matters

Double opt-in is the only step that proves a person reads that inbox. Use it for accounts, trials and marketing lists.

Time: half a day if your app already sends email.

Done when: unconfirmed addresses never get marketing mail.

7. Watch your bounces

Track hard and soft bounces separately; suppress hard bounces at once. See our guide to hard bounce vs soft bounce.

Time: 1 hour to set up, then a weekly look.

Done when: hard-bounced addresses are suppressed automatically.

Mistakes that cost the most

Rejecting real people: 5 of 13 patterns blocked o'brien@example.com

In our test, 5 of 13 patterns rejected an apostrophe, 5 rejected a % sign, and one rejected plus addresses and .photography. Each false reject is a signup you never see.

Fix: run the harness above and allow every character in the WHATWG local-part set.

Shipping a backtracking pattern: 1 of 13 did not finish within 5 seconds

The emailregex.com Ruby pattern, \A([\w+\-].?)+@..., nests quantifiers. In our Python run its time grew exponentially on inputs from 18 to 26 characters, and the 50,001-character input hit our 5-second timeout.

Fix: replace any pattern with nested quantifiers. A 254-character cap is still good practice, but it cannot save this pattern, which slows down well below that length.

Sending to dead or mistyped domains: a cost you can measure

8 of 13 patterns passed jane@example.c, and no regex can catch jane@gmial.com. Both become bounces. Put your own volume and bounce rate into our bounce cost calculator and compare the result to a ContactLint check at $0.0018 each on the 5,000 pack, or less on bigger packs.

Fix: add an MX/A check and typo suggestions at signup.

Regex vs HTML5 input vs validation API vs mailbox verifier vs confirmation email

OptionWhat it catches, what it costs, the catch
Regex in your codeMalformed strings. Free and instant. Blind to DNS, typos and mailboxes; a bad pattern blocks real people or backtracks.
HTML5 input type=emailMalformed strings in the browser. Free. Browser only, accepts jane@example, no local-part or total length limit.
Validation API (like ContactLint)Adds MX/A records, disposable, role and typo checks. ContactLint: 100 free a month, then $9 for 5,000. Cannot confirm a mailbox exists.
Mailbox (SMTP) verifierAsks the mail server about the mailbox. Priced per check by each vendor, see our comparison. Catch-all domains still return unknown.
Confirmation emailProves a person reads the inbox. Costs one send. Adds delay; some real people never click.

How we compared the options

Criterion (weight)What it measures
Catches bad input (35%)Bad addresses stopped: format, dead domains, typos, throwaways, missing mailboxes.
Never blocks real people (25%)How rarely it rejects a valid address.
Cost (15%)Money and build time for a small team.
User experience (15%)Speed and friction at the moment of signup.
Safety and upkeep (10%)Backtracking risk, bypass risk, maintenance.

Disclosure: ContactLint sells a validation API (100 free checks a month, then credit packs from $9), so judge the scores with that in mind. There are no affiliate links on this page. Prices are as listed on October 31, 2026.

Regex in your code: Best for a fast first filter. Score: 7.4/10 (our judgment: we rated it 5, 8, 10, 9 and 8 on the five criteria above, in order, then applied the weights). Trade-off: dead domains and typos pass.

HTML5 input type=email: Best for instant feedback at zero cost. Score: 6.9/10 (our judgment: rated 3, 10, 10, 9 and 5, weighted). Trade-off: browser only, easy to bypass, no local-part or total length limit.

Validation API (like ContactLint): Best for signups, trials and agents that need DNS, disposable and typo signals. Score: 7.5/10 (our judgment: rated 7, 8, 7, 8 and 8, weighted). Trade-off: per-check cost, and no mailbox or catch-all answer.

Mailbox (SMTP) verifier: Best for cleaning a list before a big send. Score: 6.7/10 (our judgment: rated 8, 7, 4, 5 and 8, weighted). Trade-off: slower, pricier per check, and catch-all domains stay unknown.

Confirmation email: Best for accounts and marketing lists. Score: 7.6/10 (our judgment: rated 9, 7, 7, 5 and 9, weighted). Trade-off: it runs after signup and loses people who never click.

The scores are close because each option covers a different gap, so the steps above stack them.

Which check fits your form

If you are ...Use this
A newsletter signuptype=email, length checks, double opt-in. Add typo suggestions if bounces rise.
A SaaS free trialRegex plus a validation API, and block disposable signups if trial abuse costs you.
A B2B lead formRegex plus an API; flag free mail and role addresses for sales review instead of blocking.
A checkoutRegex plus a "did you mean" suggestion. Never block a paying customer on a soft signal.
An AI agent writing to a CRMA validation tool call before every write. See email validation for AI agents.

Who this is not for

Teams that must confirm a mailbox exists before a cold send. You need an SMTP mailbox verifier, and even then catch-all domains cannot be confirmed. Start with our verifier comparison.

Mail software that parses message headers. It needs the full RFC 5322 grammar, comments and all, and a real parser library, not a form regex.

Apps whose users have non-ASCII local parts. Most patterns we tested allow only ASCII letters, and our test set had no non-ASCII local parts. Accept UTF-8 local parts, check length in bytes, and test with mail systems that support RFC 6531.

Frequently asked questions

What is the regex for email validation in JavaScript?

Use type=email in the browser plus a short pattern and length checks in code, as shown above. Colin McDonnell's pattern plus our length check scored 23 of 24 in our test. Trim the input first.

Is there a perfect email regex?

No. In our test of 13 patterns, the best scored 22 of 24 on its own, and all 13 accepted a local part longer than RFC 5321 allows. Length and domain checks belong outside the regex.

Should I use the RFC 5322 regex?

Not for a signup form. The 428-character version we tested scored 21 of 24, rejected an all-caps address, and allows quoted strings and IP literals. Even the WHATWG spec calls its own simpler rule a willful violation of RFC 5322.

Does input type=email validate emails?

It checks format in the browser with the WHATWG pattern, which scored 18 of 24 in our test. It accepts jane@example and double dots, and it is easy to bypass, so repeat the check on the server.

Can regex check if an email is real?

No. A regex sees only the string. Mail records need a DNS lookup, mailbox existence needs an SMTP check, and ownership needs a confirmation email.

Why does my regex reject plus addresses?

Its local-part character class leaves out +. Add it, plus ' and %, to the class. Of the 13 patterns we tested, only regexr.com/3e48o rejected jane+news@example.com.

Sources

What changed in this guide

Check what the regex cannot see

A regex tells you an address is well formed. ContactLint checks the rest: MX and A records, disposable and role addresses, and typos of big providers. It does not connect to mail servers, so it cannot confirm a mailbox exists or detect catch-all domains. Get a free key with 100 checks a month, or connect an agent to our MCP server.

Get a free API key. 100 checks per month, no card required.