Great work!

XP to next level

BugEater

Why Regex Fails Email Validation

The canonical email regex appears in Stack Overflow answers, open-source libraries, and production systems around the world:

^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$

It looks complete. It is not. This lesson catalogs exactly what it gets wrong and why the gap matters.

What This Regex Rejects That RFC 5322 Allows

Missing atext characters

The character class [a-zA-Z0-9._%+-] includes only a subset of valid atext characters. Missing:

  • !user!name@example.com rejected
  • #user#name@example.com rejected
  • $user$name@example.com rejected
  • &user&name@example.com rejected
  • 'user'name@example.com rejected
  • *user*name@example.com rejected
  • /user/name@example.com rejected
  • =user=name@example.com rejected
  • ?user?name@example.com rejected
  • ^user^name@example.com rejected
  • `user`name@example.com rejected
  • { | } ~ → rejected

Quoted strings

The regex has no mechanism to recognize quoted strings. "user name"@example.com is rejected because spaces are not in the character class.

IP address literals

user@[192.168.1.1] is valid per RFC. The domain character class [a-zA-Z0-9.-]+ does not include [ or ], so IP literals are rejected.

What This Regex Accepts That RFC 5322 Forbids

Consecutive dots in local part

The + quantifier means "one or more characters from the class." The class includes .. So user..name is accepted — the two dots are just two characters in the sequence. The RFC rule "no consecutive dots" is not enforced.

Leading dot in local part

^[a-zA-Z0-9._%+-]+ — the ^ is followed by [...] which includes .. Nothing prevents a leading dot. .user@example.com is accepted.

Trailing dot in local part

Similarly, [...]+@ — nothing prevents a trailing dot before @. user.@example.com is accepted.

Technically invalid TLD lengths

The {2,} means "two or more letters." This is not a constraint from the RFC. The RFC imposes no minimum TLD length (single-letter TLDs exist) and the practical maximum of about 24 characters is not enforced either.

The TLD Requirement Is Wrong

\.[a-zA-Z]{2,}$

This requires at least one dot in the domain and at least two letters after the last dot. Both of these are practical constraints but neither is from the RFC:

  1. The RFC allows user@localhost (no TLD required)
  2. The RFC allows numeric-only labels: user@1.2.3.4 would be rejected even though [IPv6::1] should be an IP literal

The regex is encoding the popular mental model of email, not the actual specification.

Why "Good Enough" Is a Real Problem

You might argue: this regex handles 99% of real email addresses correctly. Why does it matter?

Real user impact: user+tag@example.com (Gmail tagging) and user'name@example.com (Irish names) are real addresses in active use. Rejecting them turns away real users.

Moving goalposts: TLD restrictions become wrong as new TLDs are added. A regex that required {2,4} was wrong when .museum launched. A regex that requires {2,6} is wrong now for some long TLDs.

Security implications: Over-restrictive validation can be used to block specific accounts. An attacker who knows the service rejects + in emails can register with user+block@example.com knowing that the legitimate owner user@example.com who tries the plus variant will fail.

What Proper Validation Looks Like

Option 1: RFC-compliant parser

Use a library that implements the full RFC 5322 grammar. In Java: the Apache Commons Email Validator (though it has known gaps). In Python: the email-validator library. These parse rather than regex-match.

Option 2: Practical compromise

Use a permissive check (does the string contain exactly one @ with non-empty parts on each side?) and verify deliverability by sending a confirmation email. This is the approach used by Google, Microsoft, and most major services.

Option 3: Informed regex

If regex is required, derive it from the actual atext definition. This still can't handle quoted strings correctly, but at least the character set is right:

^[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{1,}$

This still has the consecutive-dot problem and still can't handle quoted strings, but at least it doesn't reject + or '.

Testing Recommendation

When testing an email validator, always include:

  1. user+tag@example.com — if rejected, classic regex was used
  2. user'name@example.com — if rejected, regex missing apostrophe
  3. user!name@example.com — if rejected, regex missing exclamation
  4. user..name@example.com — if accepted, regex doesn't check consecutive dots
  5. .user@example.com — if accepted, regex doesn't check leading dot
  6. user.@example.com — if accepted, regex doesn't check trailing dot

Quiz

A developer uses the regex ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$. Which valid RFC 5322 address does this reject?

A regex validator accepts user@@example.com as valid email. What does this reveal about the regex?

Why do most real-world email validators deliberately not implement the full RFC 5322 grammar?

A tester finds that the system accepts user@exam_ple.com (underscore in a domain label). RFC 5321 disallows underscores in email domain labels. What is the correct verdict?