The Email Validation Regex to Use and What It Can't Catch

Avatar for Jarrett Lusso Jarrett Lusso

Updated November 11, 2026

6 min read

The Email Validation Regex to Use and What It Can't Catch

Search for "email regex" and you'll find patterns that run to several hundred characters, each claiming to follow the email standard. You don't need any of them. The pattern below is the one every modern browser uses for <input type="email">, and it is the right default for almost every app.

This guide gives you that pattern in Ruby, JavaScript, and Python, explains what it can and can't tell you, and shows how to check that an address can actually receive mail.

The email regex to use

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

This comes from the HTML Living Standard. It's deliberately stricter than the official email RFCs. It rejects things the RFCs technically allow, like quoted local parts ("john smith"@example.com) and comments, because real people don't use them and most mail systems don't accept them.

Here's what each part does:

Part

Pattern

What it matches

Username

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

Letters, numbers and the symbols allowed in addresses, including + for tags like jane+news@example.com

At sign

@

The single required @

Domain

[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?

One domain label of up to 63 characters that doesn't start or end with a hyphen

Extra domain parts

(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*

Any number of further labels, like .co.uk

One thing to know: the browser pattern accepts addresses with no dot in the domain, like admin@localhost. If you only want public addresses, change the final * to + so at least one dot is required.

Copy-paste versions

Ruby

Ruby ships this pattern in the standard library:

Ruby
require 'uri'

URI::MailTo::EMAIL_REGEXP.match?('jane@example.com') # => true

Rails

Use the same pattern in a model validation:

Ruby
validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }

JavaScript

Node JS
const EMAIL_REGEX = /^[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])?)*$/;

EMAIL_REGEX.test('jane@example.com'); // true
EMAIL_REGEX.test('jane@example'); // true (see the note above)
EMAIL_REGEX.test('jane@@example.com'); // false

In a form, you often don't need the regex at all. <input type="email"> applies this exact pattern, and you can read the result with input.checkValidity().

Python

Python
import re

EMAIL_REGEX = re.compile(r"[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])?)*")

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

fullmatch checks the whole string, so the ^ and $ anchors aren't needed.

What regex can't catch

A regex only checks that an address is shaped correctly. Every one of these passes:

  • Typos in the domain. jane@gmial.com and jane@yaho.com are perfectly valid syntax.
  • Domains that don't receive mail. jane@example-company.com passes even if that domain has no mail server, or doesn't exist.
  • Mailboxes that don't exist. notarealperson123@gmail.com is valid syntax at a real domain. Only Gmail's mail server knows the mailbox isn't there.
  • Disposable addresses. Throwaway inboxes from temporary email services look like any other address and stop working within hours.
  • Role addresses. info@, sales@ and noreply@ are valid but often aren't a person, and they tend to complain or bounce.
  • Accept-all domains. Some servers accept mail for any address at the domain, so even a mail server check can't tell whether a specific person is there.
  • Full mailboxes. The address exists, but mail sent to it will bounce.

These are what actually hurt you. Sending to addresses that don't exist produces hard bounces, and a high bounce rate damages your sender reputation, which means more of your mail goes to spam, including mail to your good addresses.

Why checking that an address exists is hard

The only system that knows whether a mailbox exists is the mail server that hosts it. In theory you can ask it directly: look up the domain's mail servers in DNS, open an SMTP connection and ask whether it will accept mail for the address, without sending anything.

In practice, the answer you get back is often missing, misleading or wrong:

  • You may not be able to ask at all. Most cloud providers block or restrict outbound port 25 by default, so the check can't run from your app servers.
  • Servers say yes to everything. Accept-all servers accept every address at the domain, and some servers accept an address during the check and bounce the message later.
  • Servers refuse to answer. Greylisting, rate limits and temporary failures are designed to slow down exactly this kind of request, so a single attempt often tells you nothing.
  • Asking too often gets you blocked. Mail servers blocklist IPs that make many checks, and that can affect your own mail if it shares the IP.
  • Every major provider behaves differently. Gmail, Outlook, Yahoo and corporate mail gateways each respond in their own ways, and those behaviors change over time.
  • The mail server isn't the whole picture. Disposable domains appear daily, typo domains like gmial.com can accept mail, and role addresses work fine but rarely reach a person. Spotting these takes data that has to be kept current.

Getting a reliable answer means combining many signals, retrying intelligently, handling each provider's quirks and keeping that data up to date. That's what an email verification service is built to do, and why most teams use one instead of building it.

Verifying emails with the Emailable API

The Emailable API runs the syntax, domain, mail server, disposable, role and accept-all checks in one request and returns a single answer. Our official client libraries for Ruby, Node.js and Python make it a few lines of code.

In Ruby, for example:

Ruby
Emailable.api_key = 'YOUR_API_KEY'

result = Emailable.verify('jane@example.com')
result.state # => "deliverable"

Each result comes back with a state (deliverable, undeliverable, risky or unknown) and a reason that explains it. Our guide to all possible states and reasons covers what each one means. The response also includes did_you_mean, which suggests a fix for common typos like gmial.com, and the API docs list every field.

Putting it together

For a signup or lead form, use both:

  1. Regex in the browser for instant feedback while someone types. It's free and catches obvious mistakes like a missing @.
  2. The API on submit, on your server, to reject undeliverable addresses and offer the did_you_mean correction before you save the record.

For an existing list, upload it to the bulk email verifier instead of calling the API once per address. To check a single address by hand, use the free email verifier.

New accounts get 250 free credits, so you can try the API on your own form without paying anything. Create an API key and you can be verifying emails in a few minutes.

FAQ

What is the best regex for email validation?

The pattern from the HTML Living Standard shown above. It's what browsers use, it accepts the addresses real people have and it rejects malformed input. Fully RFC-compliant patterns are much longer and accept addresses that almost no mail system supports.

Can regex check if an email address exists?

No. Regex only checks the format. Confirming that a mailbox exists depends on the mail server that hosts it, and getting a reliable answer from mail servers takes the retries, provider-specific handling and up-to-date data that an email verification API provides.

Should I validate emails on the client or the server?

Both. Client-side checks give users quick feedback, but they can be bypassed, so always validate again on the server before saving or sending.

Why does my regex reject valid addresses?

Common causes are not allowing + in the local part, limiting top-level domains to 2 to 4 letters (many are longer now, like .photography) and not allowing subdomains like mail.example.co.uk. The pattern above handles all three.