DevLab
Regex

Regex for Email Validation: The Right Approach

Learn why perfect email regex is impossible, what a practical validation pattern looks like, and when to use client-side vs server-side validation.

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]+@/ reject user+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.com is 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

  1. Client-side UX: type="email" or simple regex to catch obvious typos before submission
  2. Server-side format check: Reject addresses missing @ and a dot — not to be strict, but to prevent garbage data
  3. 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

Frequently Asked Questions

What is the best regex for email validation?

There is no single "best" regex. For client-side forms, the simple pattern /^[^\s@]+@[^\s@]+\.[^\s@]+$/ catches the most common errors (missing @, spaces, no domain) without rejecting valid addresses. For server-side validation, use your framework's built-in email validator (Zod, express-validator, Django EmailValidator) instead of a custom regex. The only true validation is sending a confirmation email.

Why does my email regex reject valid addresses like user+tag@gmail.com?

Your regex likely restricts the local part to only alphanumeric characters. The + sign is a valid character in email local parts and is widely used for filtering (e.g., user+newsletters@gmail.com routes to user@gmail.com). Make sure your pattern allows +, dots, hyphens, and other RFC-permitted special characters in the local part.

Should I use the HTML5 type="email" or a regex pattern?

Use both. The HTML5 type="email" attribute provides built-in browser validation with native error messages in the user's language — no JavaScript required. Add a regex pattern only if you need stricter validation than the browser provides (e.g., restricting to a specific domain). Never rely solely on client-side validation; always validate on the server too.

Does email regex need to handle international characters?

Internationalized email addresses (like 用户@例子.中国) are valid per RFC 6531 but rarely supported by mail servers in practice. Most applications can safely restrict to ASCII characters. If you serve a global audience and need to accept IDN domains, use a library that handles punycode conversion rather than trying to write a regex for Unicode email addresses.

How long can an email address be?

Per RFC 5321, the total email address length is limited to 254 characters. The local part (before @) can be up to 64 characters, and each domain label (between dots) can be up to 63 characters. Most email regex patterns do not enforce these limits, which is fine for client-side validation — the mail server will reject oversized addresses anyway.

Practice with these tools

More Learning Topics

RegexRegex Basics: A Complete Beginner's GuideRegexRegex Special Characters: Complete ReferenceRegexRegex Groups and Captures ExplainedRegexRegex Quantifiers: Complete GuideCSSCSS Selectors: The Complete GuideCSSCSS Specificity: Why Your Styles Aren't ApplyingJSONJSONPath Syntax: Query JSON Like XPathTimeUnix Timestamps ExplainedEncodingBase64 Encoding ExplainedEncodingJWT Structure and How It WorksEncodingJWT vs Session Tokens: Which Should You Use?EncodingJWT Refresh Tokens ExplainedCryptoHash Functions Explained: MD5, SHA-256, and When to Use EachEncodingURL Encoding Explained: What %20 Actually MeansJSONJSON Schema Explained: Validate Your JSON DataJSONJSON vs YAML: Which Should You Use?JSONJSON.stringify and JSON.parse: Edge Cases You Should KnowRegexRegex Lookahead and Lookbehind: Match Without ConsumingCSSThe CSS Box Model: margin, padding, border, and contentCSSFlexbox vs CSS Grid: When to Use EachCSSCSS Custom Properties (Variables) ExplainedTimeISO 8601 Explained: The Right Way to Format DatesTimeUnix Timestamps vs ISO 8601: Which to Use in Your API?EncodingUTF-8 Explained: How Computers Store TextTextCORS Explained: Why Your API Call is BlockedTextHTTP Status Codes: A Practical Developer GuideRegexNamed Capture Groups in Regex: Clean ExtractionsColorsColor Spaces Explained: RGB, HSL, HEX, and BeyondColorsColor Contrast for Developers: WCAG Rules and How to CheckNumbersNumber Bases Explained: Binary, Octal, Decimal, and HexNumbersBitwise Operations for Web DevelopersHTMLHTML Semantic Elements: A Complete ReferenceTextText Encoding for Developers: ASCII, UTF-8, and UnicodeRegexWhat is Regex? Complete Guide for DevelopersJSONJSON Format Explained: Structure, Syntax, and Common ErrorsEncodingHow JWT Works: Header, Payload, Signature DecodedHTMLHow to Convert Any Website to Markdown (for LLMs, RAG & Docs)TextPreparing Website Content for RAG: Clean Markdown Pipelines