Why Perfect Email Regex is Impossible
The official email spec (RFC 5321) allows quoted strings, IP address domains, and Unicode in local parts. A fully compliant regex is hundreds of characters long and still incomplete. The practical lesson: email regex should catch obvious mistakes, not be the final authority. The only true validation is sending an email and seeing if it arrives.
RFC 5322 technically permits addresses like "john..doe"@example.com (quoted local parts with consecutive dots) and user@[192.168.1.1] (IP address literals). No reasonable signup form should accept these, which is why spec-compliance and practical validation are two different goals. A good email regex protects the user experience by catching typos, not by implementing the RFC.
Practical Patterns
// Reasonably comprehensive
const emailRegex = /^[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])?)*.[a-zA-Z]{2,}$/;
// Simpler — catches the most common mistakes
const simpleEmail = /^[^s@]+@[^s@]+.[^s@]+$/;
The simple pattern catches: missing @, spaces, missing domain. Accepts: user+tag@example.com, user@sub.domain.co.uk. Good enough for most signup forms.
The comprehensive pattern adds length limits (63-character labels per RFC 1035), restricts the TLD to at least two letters (rejecting user@example.c), and permits the full set of RFC-allowed special characters in the local part. It still intentionally excludes quoted strings and IP literals because those are almost never legitimate user input.
Common Mistakes in Email Regex
Developers frequently make these errors when writing email validation patterns:
- Rejecting plus addressing: Patterns like
/^[a-z0-9]+@/rejectuser+newsletter@gmail.com, which is a valid and widely used feature for filtering. Always allow+in the local part. - Limiting TLD length: Old patterns restricted TLDs to 2-4 characters, breaking on
.museum,.photography, and hundreds of new gTLDs. Use[a-zA-Z]{2,}with no upper bound. - Forbidding dots in local part:
first.last@example.comis perfectly valid. The only restriction is that dots cannot be consecutive or appear at the start/end of the local part. - Case sensitivity assumptions: The local part is technically case-sensitive per RFC, but virtually every mail server treats it as case-insensitive. Normalize to lowercase for storage but accept any case in your regex.
Use the Browser First
<input type="email" required />
The browser's built-in email validation uses a well-tested pattern more comprehensive than most hand-rolled regex. Use type="email" first, and only add custom regex for specific requirements the browser does not handle.
The HTML5 type="email" validation follows the WHATWG spec, which is intentionally more permissive than most hand-written patterns. It accepts internationalized domain names and local parts with special characters. The browser also handles the UX — showing a native error tooltip in the user's language without any JavaScript.
The Three-Layer Approach
- Client-side UX:
type="email"or simple regex to catch obvious typos before submission - Server-side format check: Reject addresses missing @ and a dot — not to be strict, but to prevent garbage data
- Verification email: The only real validation — send a confirmation link
This layered approach means your regex does not need to be perfect. Its job is catching clear mistakes (missing @, spaces, no domain) before the form submits. The server catches anything the client missed, and the verification email is the final gatekeeper. Over-engineering the regex wastes time and rejects valid addresses.
Framework-Specific Validation
Most modern frameworks provide email validation out of the box, which is usually better than writing your own regex:
// Zod (TypeScript)
const schema = z.object({ email: z.string().email() });
// Express-validator (Node.js)
body('email').isEmail().normalizeEmail();
// Django (Python) — built-in EmailValidator
from django.core.validators import EmailValidator
These validators handle edge cases, internationalization, and normalization that a simple regex pattern cannot. Use them as your server-side format check (layer 2) and reserve custom regex only for client-side UX hints.
Test Cases to Always Cover
// Must accept:
"user+tag@example.com" // plus addressing
"user@sub.domain.co.uk" // multiple subdomains
"first.last@company.org" // dots in local part
"user@example.photography" // long TLD
// Must reject:
"user@.com" // domain starts with dot
"user@@example.com" // double @
"user example.com" // no @, has space
"@example.com" // empty local part
"user@" // empty domain
"user@example" // no TLD