Developers

Email regex: validating email addresses with regular expressions

Copy a practical email regex that accepts real addresses and rejects broken ones, in six languages, each tested against the same 25 tricky addresses. Then see what no regex can tell you.

Updated October 9, 2026 · 7 min read

Email regex test: valid addresses that match and invalid ones that do not

The short answer: a practical email regex

This pattern accepts the addresses people actually use, including plus tags, apostrophes and long top-level domains, and rejects the mistakes that make an address undeliverable:

/^(?=.{1,254}$)(?=.{1,64}@)[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+(?:[A-Za-z]{2,63}|xn--[A-Za-z0-9-]{2,59})$/

It isn’t the full RFC 5322 grammar on purpose: it leaves out quoted local parts ("jane doe"@example.com) and IP addresses as domains, which are valid on paper but rejected by almost every mail service and form. Trim the input before you test it, since people paste addresses with spaces.

What the pattern checks

PartWhat it does
(?=.{1,254}$)The whole address has at most 254 characters (RFC 5321).
(?=.{1,64}@)The part before the @ has at most 64 characters.
[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+The local part: letters, digits and the special characters RFC 5322 allows.
(?:\.[…]+)*Dots only between characters: no dot at the start or end, never two in a row.
(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+One or more domain labels of up to 63 letters, digits and hyphens, not starting or ending with a hyphen.
(?:[A-Za-z]{2,63}|xn--[A-Za-z0-9-]{2,59})A top-level domain of letters, or an internationalized one in punycode (xn--).

The rules behind each part are explained in the guide to the email address format.

Email regex in JavaScript

const EMAIL_REGEX =
  /^(?=.{1,254}$)(?=.{1,64}@)[A-Za-z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[A-Za-z0-9!#$%&'*+\/=?^_`{|}~-]+)*@(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+(?:[A-Za-z]{2,63}|xn--[A-Za-z0-9-]{2,59})$/;

function isValidEmail(value) {
  return EMAIL_REGEX.test(value);
}

isValidEmail("jane+news@example.com"); // true
isValidEmail("jane..doe@example.com"); // false

In JavaScript, ^ and $ mark the start and end of the whole string as long as you don’t add the m flag. Don’t add g either: a global regex remembers its position between test() calls and returns alternating results. Step-by-step form validation is in the guide to validating email in JavaScript.

Email regex in Python

import re

EMAIL_REGEX = re.compile(
    r"(?=.{1,254}\Z)(?=.{1,64}@)"
    r"[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*"
    r"@(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+"
    r"(?:[A-Za-z]{2,63}|xn--[A-Za-z0-9-]{2,59})"
)

def is_valid_email(value: str) -> bool:
    return EMAIL_REGEX.fullmatch(value) is not None

Use fullmatch(). With re.match() and a pattern ending in $, Python also accepts "jane@example.com\n", because $ matches before a trailing line break. More in validating email in Python.

Email regex in PHP

const EMAIL_REGEX = '/\A(?=.{1,254}\z)(?=.{1,64}@)[A-Za-z0-9!#$%&\'*+\/=?^_`{|}~-]+(?:\.[A-Za-z0-9!#$%&\'*+\/=?^_`{|}~-]+)*@(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+(?:[A-Za-z]{2,63}|xn--[A-Za-z0-9-]{2,59})\z/';

function isValidEmail(string $value): bool
{
    return preg_match(EMAIL_REGEX, $value) === 1;
}

Use \A and \z (or the D modifier): with ^ and $ alone, preg_match accepts an address followed by a line break. PHP also has a built-in check, filter_var($email, FILTER_VALIDATE_EMAIL); the test table below shows where it differs. More in validating email in PHP.

Email regex in Java

import java.util.regex.Pattern;

public final class EmailValidator {
    private static final Pattern EMAIL_REGEX = Pattern.compile(
        "(?=.{1,254}\\z)(?=.{1,64}@)"
        + "[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*"
        + "@(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\\.)+"
        + "(?:[A-Za-z]{2,63}|xn--[A-Za-z0-9-]{2,59})");

    public static boolean isValidEmail(String value) {
        return EMAIL_REGEX.matcher(value).matches();
    }
}

matches() requires the whole string to match, so the pattern needs no ^ and $. Backslashes are doubled inside Java strings. Library checks, an MX lookup and an API call in Java are in the guide to validating email in Java; the same steps for .NET are in the C# email validation guide.

Email regex in Go

import (
	"regexp"
	"strings"
)

// Go's regexp package (RE2) has no lookaheads, so the length limits are checked in code.
var emailRegex = regexp.MustCompile(
	"^[A-Za-z0-9!#$%&'*+/=?^_\x60{|}~-]+(?:\\.[A-Za-z0-9!#$%&'*+/=?^_\x60{|}~-]+)*" +
		"@(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\\.)+" +
		"(?:[A-Za-z]{2,63}|xn--[A-Za-z0-9-]{2,59})$")

func isValidEmail(value string) bool {
	at := strings.LastIndex(value, "@")
	return len(value) <= 254 && at >= 1 && at <= 64 && emailRegex.MatchString(value)
}

The pattern contains a backtick, which can’t appear in a Go raw string, so it’s written as \x60 in a normal string. net/mail, MX lookups and an API check are in the guide to validating email in Go.

Email regex in Ruby

EMAIL_REGEX = /\A(?=.{1,254}\z)(?=.{1,64}@)[A-Za-z0-9!\#$%&'*+\/=?^_`{|}~-]+(?:\.[A-Za-z0-9!\#$%&'*+\/=?^_`{|}~-]+)*@(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+(?:[A-Za-z]{2,63}|xn--[A-Za-z0-9-]{2,59})\z/

def valid_email?(value)
  EMAIL_REGEX.match?(value)
end

In Ruby, ^ and $ match at every line break, so "<script>\njane@example.com" passes a pattern written with them. Always use \A and \z. The # is escaped as a precaution: in Ruby regex literals, #$name interpolates a global variable. Model validations, an MX lookup and a mailbox check for Rails are in the guide for Ruby.

The HTML5 pattern: type="email"

Browsers validate <input type="email"> with a pattern from the HTML standard:

/^[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])?)*$/

The standard calls it a deliberate violation of RFC 5322: it is simple on purpose. As the test table shows, it accepts dots at the start or end of the local part and domains without a top-level domain (jane@example), so use it for instant feedback and check again on the server. Tested results for type="email", plus pattern and custom error messages, are in the guide to the HTML email validator. The same idea works in schemas and spreadsheets: see JSON Schema email validation and Google Forms and Excel email validation.

Why not a full RFC 5322 regex?

A regex that implements the complete RFC 5322 address grammar, with quoted strings, comments and IP addresses, runs to hundreds of characters. It is hard to read, impossible to review, and accepts addresses like "jane doe"@[192.0.2.10] that no email service will deliver to. It also still can’t tell you whether the address exists. The practical pattern above trades those rare edge cases for clarity.

Test table: 25 tricky addresses

Each address was run through every pattern on this page. The six language versions of our pattern return identical results, shown in the “Our pattern” column. ✓ means the check accepts the address, ✗ that it rejects it; a check is right when it agrees with “Should pass?”.

AddressShould pass?Our patternSimpleHTML5PHP filter_varOld pattern
jane@example.comYes✓✓✓✓✓
jane.doe@example.comYes✓✓✓✓✓
jane+news@example.comYes✓✓✓✓✗
o'brien@example.ieYes✓✓✓✓✗
jane_doe@mail.example.co.ukYes✓✓✓✓✓
x@example.coYes✓✓✓✓✓
jane@example.photographyYes✓✓✓✓✗
JANE@EXAMPLE.COMYes✓✓✓✓✓
jane@xn--bcher-kva.exampleYes✓✓✓✓✗
jane@example.xn--p1aiYes✓✓✓✓✗
jane.@example.comNo✗✓✓✗✓
.jane@example.comNo✗✓✓✗✓
jane..doe@example.comNo✗✓✓✗✓
jane@exampleNo✗✗✓✗✗
jane@-example.comNo✗✓✗✗✓
jane@example..comNo✗✓✗✗✓
jane@exa_mple.comNo✗✓✗✗✗
jane doe@example.comNo✗✗✗✗✗
jane@doe@example.comNo✗✗✗✗✗
jane@example.cNo✗✗✓✓✗
"jane doe"@example.comNo (RFC-valid)✗✗✗✗✗
jane@[192.0.2.10]No (RFC-valid)✗✓✗✓✗
josé@example.comNo (needs SMTPUTF8)✗✓✗✗✗
aaa…a@example.com (65 a’s)No✗✓✓✗✓
jane@example.com + line breakNo✗✗✗✗✗

What the columns show:

Find email addresses in a text

To extract addresses from a block of text, drop the ^ and $ anchors and search for every match. A simpler pattern works better here, since you’ll validate what you find anyway:

const EMAIL_IN_TEXT = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g;
const text = "Write to jane.doe@example.com or sales@example.co.uk. Old address: (bob@old-domain.org)";
text.match(EMAIL_IN_TEXT); // ["jane.doe@example.com", "sales@example.co.uk", "bob@old-domain.org"]
import re
re.findall(r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}", text)

Both stop at punctuation after an address (the period after co.uk) and skip strings without a top-level domain such as name@localhost. Run the results through the validation pattern, or through the bulk email verifier to see which addresses actually work. To skip the code, paste the text into the free email extractor: it removes duplicates, sorts the addresses and filters them by domain.

Why regex is not email verification

A regex answers one question: is this string shaped like an email address? It can’t tell you whether:

jane.doe@example.com passes every pattern on this page and may still bounce. Use regex for instant feedback in the form, then check the address itself: with the free email checker by hand, or from your code with the email validation API, which runs the syntax, MX, mailbox and disposable checks in one request.

Frequently asked questions

What is the best regex for email validation?

One that follows the format rules without being stricter than they are: letters, digits and the special characters ! # $ % & ' * + - / = ? ^ _ ` { | } ~ in the local part, dots only between characters, a domain of valid labels, and the length limits of 64 and 254 characters. The pattern on this page does exactly that and is tested in six languages.

Is there an RFC 5322 compliant regex?

Patterns that implement the full RFC 5322 grammar exist, but they are hundreds of characters long and accept addresses such as quoted strings with spaces and IP addresses in brackets, which almost no mail service accepts. For forms and apps, a practical pattern is the better choice.

Should I validate email addresses with regex?

Yes, as a first check: it catches typos and junk instantly, before any request. But a regex only checks the format. Whether the domain receives mail and the mailbox exists needs a DNS lookup and an SMTP check, for example with an email validation API.

How do I find all email addresses in a text with regex?

Use a pattern without the ^ and $ anchors and match it globally, for example [A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,} with the g flag in JavaScript or re.findall in Python. Then validate each address you found.

Email validation API

Validate emails in your app

emailvalidation.io checks syntax, MX records and the mailbox over SMTP, flags disposable, role and free addresses and returns a quality score, in one request.

/v1/info Email validation API Read the documentation

100 free validations every month. No credit card required.

GET https://api.emailvalidation.io/v1/info?email=support@emailvalidation.io

{
  "email": "support@emailvalidation.io",
  "user": "support",
  "tag": "",
  "domain": "emailvalidation.io",
  "format_valid": true,
  "mx_found": true,
  "smtp_check": true,
  "catch_all": null,
  "role": true,
  "disposable": false,
  "free": false,
  "score": 0.64,
  "state": "deliverable",
  "reason": "valid_mailbox",
  "did_you_mean": ""
}

Keep reading

Start using our email validation software today!

Get 100 validations per month for free