Four questions that share one word
« Is this a valid email address? » is not one question. It is four, they get harder in order, and the last one is the only one anybody actually wanted answered — which is why so much validation code is elaborate about the first and silent about the rest.
Here they are, in the order a sign-up form meets them.
- Is it shaped like an address?
- A syntax check. It runs in the browser, it costs nothing, and it catches the typed comma and the missing
@. It is the only layer that can honestly reject anything on its own — and even then it should reject far less than most patterns do. - Could that domain take mail at all?
- A DNS check. One query says whether anything, anywhere, is willing to accept mail for the part after the
@. It catches a domain name with a letter missing and a domain that expired last year, and it can tell you nothing whatever about the mailbox. - Does that mailbox exist?
- An SMTP check — and the one this guide will spend the longest telling you not to trust. You can ask. The answer is frequently a polite yes from a server that accepts every name, or a deliberate temporary failure, or an acceptance followed by a bounce minutes later.
- Is it their address?
- Nothing on the technical side can answer this. An address can be flawless, deliverable and somebody else's — mistyped by one digit, or typed on purpose to get past you. Only a message that arrives and gets used closes it.
The first three are cheap and prove little. The fourth is the only one that earns the name, and it is the one that costs a message. Everything below is about spending the cheap three well so the expensive one is not wasted.
Layer one: the pattern, and the four things it cannot know
There is no official regular expression for an email address, and there cannot be one. RFC 5322 defines a grammar rather than a pattern, and that grammar permits comments in parentheses, folded whitespace and quoted strings containing almost any character — none of which any real sign-up form should accept, and all of which a faithful pattern must.
What does exist is a deliberately narrower definition that browsers already implement: the one the HTML specification gives for an <input type="email"> field. It is a willed compromise rather than a transcription of the standard, it is what your form is already being held to before a line of your code runs, and it is short enough to read in one go:
/^[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])?)*$/Use that, or use the one your platform already ships. All four below are the same layer, and picking one is a smaller decision than what you do afterwards:
| Where you are | The check you already have | What it does that you might not expect |
|---|---|---|
| In the browser, with no code at all | <input type="email" required> | Applies the pattern above before your JavaScript ever sees the field, and shows the message in the visitor's own language. |
| In PHP, with nothing installed | filter_var($a, FILTER_VALIDATE_EMAIL) | Stricter than the HTML one: it insists on a dot in the domain, so it refuses an address that would only work inside a single machine. |
| In Python, with one small package | email_validator.validate_email(a) | Does the syntax and, if you let it, the domain lookup of layer two — the two cheap layers behind one call. |
| In Java or Kotlin, through bean validation | @Email | Very permissive on purpose. It is an annotation meant to catch the obviously wrong, and it will happily accept a domain that has never existed. |
Whichever you use, what matters is what it stays silent about. A pattern that returns true has told you one thing and four non-things.
- Not that the domain exists
- An address at a domain nobody has ever registered passes every pattern in this section. Nothing has been looked up and nothing has been contacted; no regular expression has ever made a network request.
- Not that the mailbox exists
- Even on a real domain, the pattern has no opinion about the part before the
@. That part belongs to the receiving server alone, and it is precisely the thing DNS will never tell you. - Not that it is deliverable
- A domain can have a flawless record and a mail server that has been switched off for a month. Syntax is a property of a string; deliverability is a property of the world at the moment you press send.
- Not that it belongs to the person typing it
- The most common bad address in any database is a real, deliverable one owned by somebody who never signed up — because a letter was mistyped, or because the form was filled in with a plausible-looking lie.
What is legal, and what gets wrongly thrown out
Most validation bugs are not bad addresses that got in. They are people who could not sign up, and they never appear in a log anybody reads — the form said no, and they went somewhere else. These six do it most often.
| What forms reject | Is it legal? | What it actually is |
|---|---|---|
A plus sign, as in name+shop@example.com | Legal, and widely used | Plus addressing: one mailbox, with a label its owner chose so they can see who leaked it. Refusing it tells a careful visitor exactly what kind of form this is. |
An apostrophe, as in o'brien@example.com | Legal, and somebody's surname | One of a dozen punctuation marks the local part allows. When a form refuses it, the reason is almost never the address — it is an escaping bug somewhere further down that nobody wanted to find. |
An ending nobody has heard of, as in .dev or .photography | Legal, and there are well over a thousand | A pattern with a list of endings written into it was already out of date on the day it was written, and it gets worse every year without anybody noticing. |
| A domain with no dot in it | Legal, and useless to you | An address like root@localhost is valid inside one machine and meaningless on a public form. This is the one case where the strict platform checkers are right to refuse. |
Non-Latin letters, as in 用户@例子.广告 | Legal, with a caveat | Internationalised addresses exist and are slowly spreading. Whether your own mail stack can send to one is a separate question — but a field that refuses to accept the characters has answered it for everybody, permanently. |
Capital letters before the @ | Legal, and not yours to change | The standard leaves the case of the local part to the receiving server. Almost every server ignores it; the ones that do not are the ones you will never hear from. |
And three numbers, which are worth writing into your code because they do not change:
- 64 characters
- The most the part before the
@may be. Anything longer is not a long address, it is not an address. - 255 characters
- The most the domain may be, dots included. Nothing legitimate has ever come close, and the limit is here mainly so that you have one.
- 254 characters
- The most the whole address may be when a server carries it — which is less than the two numbers above added together. This is the one to put in the database column and in the field's
maxlength.
Layer two: one lookup, and what it settles
The part after the @ is a domain, and a domain either has somewhere to put mail or it does not. One DNS query answers that in a few milliseconds, and it is the highest-value check in this entire guide — because the mistake it catches, a domain name typed slightly wrong, is by a distance the commonest real failure there is.
$ dig +short MX example.comAn answer means a host has been named. No answer does not mean no mail: a domain with no MX record but an A record still receives, because senders fall back to it. There are four outcomes, and only two of them are failures.
| What the lookup returns | Can it receive? | What the form should do |
|---|---|---|
| One or more MX records | Yes | Accept it. This is the overwhelming majority of addresses, and nothing further is worth doing before you send. |
No MX, but an A or AAAA record | Yes, by fallback | Accept it. This is unusual and entirely legal, and senders will deliver there. Refusing it turns a working address into a lost customer. |
A single record whose whole value is 0 . | No, and on purpose | Refuse it, and say why. That is a null MX: the owner of the domain has published, in the one way there is to publish it, that nothing here takes mail. |
| The domain does not resolve at all | No | Refuse it, and offer the nearest thing. NXDOMAIN on a name a human typed thirty seconds ago is almost always one wrong letter. |
Where this check goes wrong is never the lookup. It is where the lookup was put — on the critical path, per keystroke, with a failure that blocks the submit. Six rules keep it useful:
- Run it when the field is finished, not while it is being typed. One query when focus leaves, or one on submit. A query per keystroke is a query per keystroke.
- Lower-case the domain first. DNS does not care and your cache does: two spellings of the same domain are one query, not two.
- Ask for MX, and fall back to
A. Two queries, of which only the second is conditional. A library that checks MX alone will reject domains that work. - Cache the answer for a few minutes. A handful of domains account for most sign-ups anywhere, so most lookups become no lookup at all.
- Fail open. If the resolver times out, accept the address. A bad minute at your DNS provider must never become a form that turns everybody away.
- Suggest a correction; never apply one. When the domain is one letter from a common one, offer the correction as something to click. Silently rewriting what somebody typed is how a confirmation link goes to a stranger.
The lookup has one cost worth knowing about: it is the first moment your form speaks to the outside world about something a visitor typed. Where that matters, the alternative is to skip this layer entirely and let the message itself be the whole check.
Layer three: asking the server, and why the answer is not one
There is a way to ask a mail server whether it will take a particular address without sending anything to it. Open the conversation, name a sender, name the recipient, read the reply to that one command, and hang up before the message:
220 mx1.example.com ESMTP ready
EHLO checker.example.net
250 mx1.example.com
MAIL FROM:<probe@example.net>
250 2.1.0 Ok
RCPT TO:<someone@example.com>
250 2.1.5 Ok
QUIT
221 2.0.0 ByeThe 250 after RCPT TO is what every address-verification service in the world is ultimately selling. It is worth being precise about how little it is worth.
- A catch-all domain says yes to everything
- A domain configured to accept every name — which is what this service does, and what a great many company domains do — replies
250for an address nobody has ever used. The answer is true, and it is not information. - A careful server says
450on purpose - Greylisting refuses an unknown sender's first attempt and asks it to come back shortly. A real sender does; a prober never does. That temporary failure is a deliberate non-answer, and reading it as « no such mailbox » is exactly the mistake it was designed to produce.
- Some servers accept first and refuse afterwards
- Large providers routinely take the message during the conversation and decide about it later, which turns a refusal into a bounce arriving minutes after your probe came back clean.
- Some servers say no to everyone they do not recognise
- A server that has decided your address is a stranger can refuse the recipient for reasons that have nothing to do with the recipient. What you measured was your own reputation, not their mailbox.
- And it spends the reputation you were protecting
- A connection that names recipients and never sends anything is the exact shape of a directory harvest, because it is one. Doing it at volume from your own address is the quickest route onto a blocklist — and a blocklisted sender is one whose genuine messages stop arriving.
There is one narrow case where the probe is genuinely useful: a single address, checked by hand, on a domain you run or have permission to poke at. As a step inside a sign-up form it is slow, frequently wrong, and occasionally harmful to the very thing it was added to protect.
Role addresses, throwaway domains, and the list you are about to buy
Between the domain lookup and the real message sits a family of checks that are not about validity at all. They are about whether you want this address, which is a business decision wearing a technical costume — and worth separating from the three layers around it for that reason alone.
- Role addresses
- Names like
info@,support@andadmin@. They are real, and they are usually a shared mailbox rather than a person, which makes them a poor home for anything with a password behind it. Worth flagging. Rarely worth refusing. - Disposable domains
- Addresses like the ones this site hands out, on domains that exist in order to be thrown away. Checking against a public list of them is fair enough, as long as you are honest that every such list is incomplete and slightly out of date on the day you download it.
- Free-provider addresses
- Some business forms refuse anything that is not a company domain. That is a policy, it is sometimes the right one, and it deserves to be written as a policy — a sentence the visitor can read — rather than hidden inside a thing called validation.
- Near-misses of common domains
- A well-known provider's name with one letter wrong. This one is unlike the other three: it is not a policy, it catches a genuine mistake, and the person is always glad. It is the only entry here that is worth building.
The first three share a property that makes them hard to judge: you can measure what they let through and you cannot measure what they cost.
We have an obvious interest here, so here is the honest version. If what sits behind your form is a free trial with something expensive attached, refusing throwaway domains will save you money and you should do it. If it is a newsletter, a download, or an account somebody pays for, you are mostly taxing the cautious — and the cautious are the ones who read what you send.
Layer four: the message that is the check
Everything above narrows the field. None of it establishes the only fact that matters — that this address reaches the person in front of you — and exactly one thing does: send something there, and watch it get used.
This is the loop nearly every sign-up already has and half of them treat as a formality:
- Accept the address on one permissive check. The pattern in the browser, and nothing else standing between the visitor and the button.
- Create the account unverified. Not a hold on the form, not a « pending » screen: the person is in, and what they cannot do yet is the handful of things the address is actually needed for.
- Send one message carrying a single-use link or code. One, with an expiry, tied to that account and that address and no other.
- Let the link be the proof. A click, or a code typed back, is the whole verification. Nothing else in this guide produces an answer anywhere near as solid.
- Give them a way back. A visible « resend », and a way to change the address without losing the account — because the commonest reason a link is never clicked is a typo the person can now finally see.
- Expire the ones nobody confirmed. A quiet sweep after a fixed period stops the typos and the throwaways from piling up into a list nobody trusts.
Which creates a practical problem, and it is the reason this site exists. That loop is now the most important path in the product, and testing it means receiving real mail at an address you control — over and over, inside a pipeline, without a human opening an inbox.
An address on one of the public domains needs no sign-up and no key, and whatever arrives there is readable over HTTP a second later:
$ curl -sG https://grabmail.io/api/v1/mailbox \
--data-urlencode "address=signup-42@grabmail.io"From there a whole verification flow can be driven end to end by a test suite, or a domain you own can be pointed here so that every run gets an address which has never existed before. Messages are kept for 5 days and then deleted, which is the right lifetime for a test fixture and the wrong one for a mailbox.
What to actually put in the form
Condensed, in the order the code runs:
- Trim, and trim only. Whitespace at either end is a paste artefact and is never meant. Nothing else about the string is yours to change.
- Match one permissive pattern. The browser's, or your platform's. Refuse the missing
@and the typed comma, and refuse nothing else. - Cap the length at 254. One number, in the column and in the field, and an entire class of input you can stop thinking about.
- Look the domain up, off the critical path, failing open. MX and then
A, cached, and never a reason to block a submit that would otherwise have worked. - Offer a correction; do not make one. One letter away from a common domain is a question to ask, not a finding to act on.
- Send the message. That is the validation. Everything above it was triage.
- Say what is wrong, in words. Which is the part that decides whether anybody finishes:
| What happened | What forms usually say | What to say instead |
|---|---|---|
The string has no @ in it | « Please enter a valid email address » | « An email address needs an @ — did you mean name@example.com? » |
| The domain does not resolve | « Please enter a valid email address » | « We cannot find that domain. Is the spelling right? » |
| The domain is one letter from a common one | Nothing at all; the form submits | « Did you mean … ? », with the corrected address as a button to press |
| It is on a blocklist you chose to use | « Please enter a valid email address » | « For this we need an address you will still be able to read next month. » |
| The message went out and was never confirmed | Nothing at all; the account sits there | « We sent a link to that address. Not arrived? Resend it, or change the address. » |
Notice how many rows say the same wrong thing. « Please enter a valid email address » is the default everywhere because it is true of every case and useful in none of them: in three rows out of five the address was valid, and the person who typed it has no way to discover which row they are in.
The short version
- Trim the string. Change nothing else about it.
- Match one permissive pattern — the browser's or your platform's — and stop there. Do not write your own.
- Reject anything over 254 characters, and store a column that size.
- Look up MX for the domain, fall back to
A, cache it, do it off the critical path, and accept the address when the lookup fails. - Refuse a domain that does not resolve and a domain publishing a null MX. Accept everything else DNS gives you.
- Offer a spelling suggestion when the domain is a near-miss. Never apply it yourself.
- Decide separately, and in writing, whether you are blocking throwaway or role addresses — and why.
- Send a message with a single-use link, treat the click as the verification, and make resending and correcting the address easy.
- Test that loop against a real inbox, including the paths where it fails, and delete unconfirmed accounts on a schedule.
Nine lines, and the last two are the only ones that establish anything. The other seven exist so that the message at the end of them is worth sending.
Questions
Is there an official regular expression for email addresses?
No, and there cannot be. RFC 5322 gives a grammar, not a pattern, and a pattern faithful to it would accept quoted spaces and parenthesised comments that no provider issues. Use the one the HTML specification defines for an email field, or whichever checker your platform already ships, and spend the effort you saved on the confirmation message.
Can I check whether an email address exists without sending anything?
You can ask; you cannot know. A catch-all domain accepts every name, greylisting answers a deliberate temporary failure, large providers accept during the conversation and bounce afterwards, and a server that does not recognise you may refuse for reasons about you rather than the recipient. Bulk probing also puts your sending address on blocklists.
Is name+tag@example.com a valid address?
Yes. The plus sign is an ordinary permitted character in the local part, and at most large providers it also routes to the mailbox before it, which is what makes plus addressing useful. A form that refuses it is refusing legal addresses and advertising something about itself while doing so.
Are email addresses case-sensitive?
The domain never is. The part before the @ is, by the standard, left to the receiving server — and in practice every large provider ignores case. Keep a lowercased copy to detect duplicates, and send to the string exactly as it was typed.
How long can an email address be?
64 characters before the @, 255 for the domain, and 254 for the whole address as a server carries it. The last of those three is the number to use: put it in the database column and in the field, and a whole class of input stops being your problem.
Should I block disposable email addresses?
If there is something expensive behind a free trial, yes — and accept that any list you use is incomplete. For a newsletter, a download or a paid account, the people you turn away are mostly the careful ones, and you will never see them in a log. The full trade-off is worth reading before installing a package that makes the decision for you.
How do I test a sign-up verification flow without a real mailbox?
Send to an address on a public disposable domain and read it back over the API — no sign-up, no key, and a fresh address for every run. For a suite that needs hundreds, point a domain you own at a catch-all inbox and invent an address per test. The full walk-through is in testing an email verification flow end to end.


