Catch-all domains are the quiet reason your "validated" email list still has garbage in it. A catch-all domain accepts mail sent to any address at that domain — literally-anything@company.com will get a "delivered" response even if no such mailbox exists. Your MX check passes. Your regex passes. The email still bounces or silently vanishes later.
Here's how to actually detect and handle these programmatically, instead of finding out the hard way — this is the exact gap tools like Gamalogic exist to close.
Step 1: Why Standard Checks Miss This
Recall the basic validation chain most of us start with:
async function basicValidate(email) {
const domain = email.split('@')[1];
const mxRecords = await dns.resolveMx(domain);
return mxRecords.length > 0; // true = "valid"
}
This only confirms the domain can receive mail — not that the specific mailbox exists. Catch-all domains are configured to accept everything, so this check will happily return true for addresses that don't exist at all.
await basicValidate('definitely-not-real-12345@some-catchall-domain.com');
// returns true — but that mailbox doesn't exist
That's the trap.
Step 2: Detecting a Catch-All Domain
The standard technique is to probe the domain with a clearly-fake, randomly generated address and see if the mail server accepts it too. If it accepts a made-up address, the domain is catch-all, and you can't trust individual-address checks on it.
const crypto = require('crypto');
function generateFakeAddress(domain) {
const randomLocal = crypto.randomBytes(12).toString('hex');
return `${randomLocal}@${domain}`;
}
async function isCatchAll(domain, smtpCheckFn) {
const fakeAddress = generateFakeAddress(domain);
const result = await smtpCheckFn(fakeAddress);
return result.accepted === true; // if a fake address is "accepted", the domain is catch-all
}
smtpCheckFn here would be an SMTP handshake — connecting to the mail server and issuing a RCPT TO command without actually sending mail, then reading the server's response code.
Step 3: Why Rolling Your Own SMTP Probe Is Painful
In theory this is straightforward. In practice, doing SMTP-level probing yourself runs into a handful of real problems:
- Greylisting. Many mail servers deliberately delay or soft-reject the first attempt from an unknown sender, so a single probe often gives a false negative.
- Rate limiting and IP reputation. Mail servers actively watch for exactly this kind of probing behavior and will throttle or blacklist your sending IP.
- Inconsistent server behavior. Some servers return ambiguous codes, some accept everything then bounce later (a "soft" catch-all), and some outright block SMTP verification attempts entirely.
- Connection overhead. SMTP handshakes are slow compared to a DNS lookup, and doing this synchronously in a request path will tank your response times.
I hit all of this pretty quickly — a probe that worked fine against Gmail's test domain behaved completely differently against a mid-size company's mail server with greylisting turned on.
Step 4: Structuring the Check with a Fallback
Given those failure modes, it's worth wrapping the check with clear states rather than a binary true/false:
async function checkDomainType(domain) {
try {
const mxRecords = await dns.resolveMx(domain);
if (mxRecords.length === 0) return { type: 'no_mail_server' };
const catchAll = await isCatchAll(domain, smtpProbe);
return { type: catchAll ? 'catch_all' : 'standard' };
} catch (err) {
return { type: 'unknown', error: err.message };
}
}
Then decide what to do with each outcome in your application logic — a catch_all result usually means "accept the signup, but flag this list for a stricter re-verification step later" rather than an outright reject, since you genuinely can't tell valid from invalid at that point.
Step 5: Using a Dedicated Service Instead
Because reliable catch-all detection needs infrastructure most apps don't want to maintain (rotating IPs, greylisting retry logic, a database of known catch-all domains), this is one of the areas where handing it to an API makes sense rather than a maintenance burden. I ended up using Gamalogic's catch-all detection for this instead of maintaining my own SMTP probing setup:
async function checkDomainType(email) {
const response = await fetch(
`https://api.gamalogic.com/v1/verify?email=${encodeURIComponent(email)}&api_key=${process.env.GAMALOGIC_API_KEY}`
);
const data = await response.json();
return data.status; // 'valid' | 'invalid' | 'catch-all'
}
Same three-way result, without needing to run your own SMTP infrastructure or manage IP reputation.
Takeaway
Catch-all domains break the assumption that "the mailbox exists" follows from "the domain accepts mail." Detecting them yourself is possible but comes with real infrastructure costs — greylisting, IP reputation, rate limits — that are easy to underestimate until you've hit them. Treat a catch-all result as "uncertain" rather than "valid," and build your signup or list-cleaning logic around that third state instead of forcing a binary decision.
Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.