Almost every codebase I've touched has some version of this line:
const ok = /^[^@]+@[^@]+\.[^@]+$/.test(email);
It feels like email validation. It isn't. Here's what that regex actually misses, and a more honest way to check an address before you trust it.
1. Regex only checks shape, not reality
a@a.a passes most regexes. So does test@mailinator.com (a disposable address) and ceo@yourcompany.com typed as ceo@yourcompnay.com. Your regex says "valid" to all three. Your signup funnel, your transactional email, and your deliverability rate disagree.
The full RFC 5322 grammar for a valid address is famously monstrous, and even the correct version tells you nothing about whether mail will actually arrive.
2. The four checks that actually matter
Instead of one regex, think in layers, cheapest first:
- Syntax — a sane subset, not the full RFC. Catch the obvious garbage.
-
Disposable / role detection — is the domain a throwaway (
mailinator.com,10minutemail) or a role inbox (admin@,support@)? These wreck your engagement metrics. -
Typo suggestions —
gmial.comtogmail.com,yaho.comtoyahoo.com. One of the highest-ROI fixes for signup conversion. - MX / deliverability — does the domain actually publish mail servers (MX records)? If there's no MX, nothing you send can ever land.
That last one is the step people skip, because it needs a DNS lookup rather than a string test.
3. Doing the MX check
In Node you can do it yourself:
import { promises as dns } from 'node:dns';
async function hasMx(domain) {
try {
const records = await dns.resolveMx(domain);
return records.length > 0;
} catch {
return false;
}
}
That's the core idea. In a browser or edge function you don't have raw DNS, so you'd hit a small endpoint that does the lookup for you.
4. A hosted version, if you don't want to build it
I got tired of re-implementing these four layers, so I put them behind one free GET call. Sharing in case it saves you the same afternoon:
GET https://verify-api.cchkjjdobby.workers.dev/email?email=test@mailinator.com
{
"domain": "mailinator.com",
"is_disposable": true,
"has_mx": true,
"status": "disposable",
"deliverable": false
}
CORS is enabled and it runs on Cloudflare Workers with no paid external calls, so the free tier is genuinely free. There's a no-signup UI too if you just want to paste an address and see the layers: https://tools-site.cchkjjdobby.workers.dev
Honest limitations
An MX check confirms the domain can receive mail — it can't guarantee a specific inbox exists without sending something. And you should never hard-block a signup on a typo guess; suggest, don't reject.
That's the trade-off: cheap layered checks catch most bad addresses before they cost you, and you accept that the last mile (does this exact mailbox exist) needs an actual send.
What does your stack do for this today — roll your own, or pay for a validation SaaS? Curious what the breaking point is where people decide to pay.
Top comments (0)