Most "email verification" tutorials end with the same trick: open an SMTP connection to the recipient's mail server and ask whether the mailbox exists. It works less and less. Gmail, Outlook and most corporate servers accept everything (catch-all) or answer vaguely, and probing from a cloud IP is a quick way to get that IP blocklisted.
The good news: you can catch most of the junk in a list without contacting any mail server at all. Here's the checklist I ended up with.
1. Syntax, but realistic
The full RFC 5322 grammar allows things no real provider accepts (quoted local parts, comments, IP literals). For list cleaning, a practical rule is better: one @, a local part of normal characters, and a domain with a real TLD. Lowercase the domain, trim whitespace, and strip mailto: that people paste in.
2. Does the domain accept mail?
import { Resolver } from 'node:dns/promises';
const dns = new Resolver();
dns.setServers(['1.1.1.1', '8.8.8.8']);
async function acceptsMail(domain) {
try {
const mx = await dns.resolveMx(domain);
// A "null MX" (RFC 7505) means: this domain never receives email.
if (mx.length === 1 && mx[0].exchange === '') return false;
return mx.length > 0;
} catch (e) {
if (e.code === 'ENODATA') {
// No MX: mail falls back to the A record.
try { return (await dns.resolve4(domain)).length > 0; } catch { return false; }
}
if (e.code === 'ENOTFOUND') return false; // domain doesn't exist
throw e; // timeout etc.: report "unknown", don't guess
}
}
Two lessons:
- Treat timeouts as unknown, not invalid. Marking a real customer invalid because a DNS server hiccuped is worse than leaving one bad address in.
- Cache per domain. A 50k list usually has only a few thousand distinct domains.
3. Typos of big providers
gmial.com, hotmal.com, gmail.con. These often do resolve (someone registered them), so DNS alone won't catch them. Compare the domain with a short list of popular providers using edit distance:
function editDistance(a, b) {
const d = Array.from({ length: a.length + 1 }, (_, i) => [i]);
for (let j = 1; j <= b.length; j++) d[0][j] = j;
for (let i = 1; i <= a.length; i++)
for (let j = 1; j <= b.length; j++)
d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1,
d[i - 1][j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
return d[a.length][b.length];
}
// distance 1–2 from gmail.com, outlook.com, yahoo.com… → suggest the fix
Only suggest when the domain is not itself a known provider, or you'll "correct" gmx.com to gmail.com.
4. Disposable and role addresses
- Disposable: the community-maintained disposable-email-domains list (public domain, about 9,000 entries) catches most throwaway services. Refresh it regularly.
-
Role accounts:
info@,sales@,support@,noreply@. They're real, but they're shared inboxes, and many email tools treat them as a risk. Flag them instead of deleting them.
5. Duplicates that don't look like duplicates
For Gmail, dots and +tags don't matter, so these are all the same inbox:
john.smith+news@gmail.com
johnsmith@googlemail.com
JohnSmith@gmail.com
Build a canonical form (lowercase, remove dots and the +tag for Gmail, map googlemail.com to gmail.com) and dedupe on that. Only remove +tags for providers where you know they're aliases.
6. Cheap extra signals
- Domain age from the registry's RDAP service: a domain registered last week on a B2B sign-up form deserves a second look.
-
Parked domains: name servers like
sedoparking.commean "this domain is for sale", not a real company. - SPF / DMARC presence: not proof of anything alone, but real sending domains usually have them.
What this can't tell you
Honestly: none of this proves that a specific mailbox exists. "Valid" here means "well formed, the domain is real and accepts mail, and nothing looks off." For list hygiene before an import, that's usually the part that matters, and it's fast, cheap and doesn't touch your sender reputation.
If you'd rather not build it
I packaged this checklist as an Apify Actor: Bulk Email Validator. Paste emails or a CSV link, and you get valid / risky / invalid with the reason, a typo suggestion, disposable/role/free flags, mail provider, domain age, parked and SPF/DMARC flags, and duplicate detection. It never sends a test email. Pay-per-email, about $0.50 per 1,000.
What's the weirdest thing you've found in an email list?
Written with AI assistance; the snippets and numbers were tested before publishing.
Top comments (0)