DEV Community

Nguyen Tu
Nguyen Tu

Posted on Originally published at botslave.com

How to Validate Email Addresses Without Sending Mail: MX Records, Catch-Alls, SMTP Probing

Every lead pipeline eventually asks the same question: "is this email address real?" The honest answer is that "real" is four different questions wearing one coat — and each layer of validation only answers one of them. Confusing the layers is how teams end up trusting a green checkmark that proves almost nothing, or throwing away good leads because one weak signal said no.

This is the validation model we use in our own lead operations work: what each check actually proves, where SMTP probing helps, where it quietly lies to you, and how to design a pipeline that records evidence instead of pretending to know the truth. The thinking behind this staged approach — automate the routine, keep judgment where it matters — is the same one behind how we work.

1. Four questions, not one

When someone says an address is "valid", they might mean:

  1. Is it well-formed? Does it parse as an email address at all?
  2. Can the domain receive mail? Is there a mail server willing to accept messages for this domain?
  3. Does this mailbox exist? Is there an actual account behind the local part?
  4. Is it safe and worthwhile to send to? Will sending hurt sender reputation, or reach a human who matters?

Syntax checks answer question 1. MX lookups answer question 2. Question 3 can only be approximated without sending mail — and question 4 is a policy decision, not a technical one. Most "email verifier" scores collapse all four into a single number. That is convenient and wrong: a catch-all corporate domain and a personal Gmail account can both score "valid" while meaning completely different things for your pipeline.

2. Layer one: syntax, and why it is not trivial

The grammar of an email address is defined by RFC 5322, and it is more permissive than almost every hand-rolled regex assumes. Quoted local parts ("john..doe"@example.com) are legal. So are comments in obsolete syntax that no sane system should accept but some parsers do. In practice:

  • Use a real parser from your platform's ecosystem, not a regex copied from a forum. Then apply a stricter business rule on top: most pipelines reject quoted strings, consecutive dots and addresses longer than 254 characters, not because the RFC forbids all of them, but because real-world mail systems handle them inconsistently.
  • Normalize before you validate. Trim whitespace, lowercase the domain (domains are case-insensitive; local parts technically are not, though nearly every provider treats them case-insensitively), and convert internationalized domains to their ASCII form with IDNA/punycode — you must validate and resolve the form DNS actually uses.
  • Do not "fix" addresses silently. Plus-addressing (name+tag@gmail.com) and Gmail's dot-insensitivity are provider semantics: the same mailbox, but stripping tags or dots to deduplicate is a dedupe decision, not a syntax correction. Record the original; derive a canonical form separately if your pipeline needs one.

Syntax validation is cheap and deterministic. It is also the layer that proves the least. Everything interesting happens at the domain.

3. Layer two: MX records — the domain's front door

To find out where mail for a domain goes, you ask DNS for its MX (mail exchanger) records. Each record has a preference value (lower wins) and a host name. Two lookups cover most of the world:

// Node.js — no dependencies, built-in dns module
const dns = require('node:dns/promises');

async function mxFor(domain) {
  try {
    const records = await dns.resolveMx(domain);
    return records.sort((a, b) => a.priority - b.priority);
  } catch (e) {
    if (e.code === 'ENODATA' || e.code === 'ENOTFOUND') return [];
    throw e;
  }
}
Enter fullscreen mode Exit fullscreen mode
# Python — with dnspython (pip install dnspython)
import dns.resolver

def mx_for(domain: str):
    try:
        answers = dns.resolver.resolve(domain, "MX")
        return sorted((r.preference, str(r.exchange)) for r in answers)
    except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN):
        return []
Enter fullscreen mode Exit fullscreen mode

Three outcomes matter:

  • MX records exist → the domain publishes where its mail goes. You have also learned something else for free: the MX hosts usually reveal the provider (Google Workspace, Microsoft 365, a regional hoster), which tells you which behavioral rules to expect later.
  • A single MX record pointing at "." (null MX, RFC 7505) → the domain explicitly declares it accepts no mail. This is a hard, standards-based "invalid" — one of the few you will ever get. Treat any address at such a domain as undeliverable, permanently.
  • No MX records at all → strictly, RFC 5321 §5.1 defines an "implicit MX": fall back to the domain's A/AAAA records. In practice, domains with A records but no MX are a grey zone — personal sites, parked domains, misconfigurations. Most lead pipelines mark them low-confidence rather than valid, and they are right to: a domain that wanted mail would publish MX.

What MX tells you is strictly question 2: a front door exists. It says nothing about whether your specific mailbox is behind it.

4. Layer three: SMTP probing — useful, and a compulsive liar

The classic trick for question 3 is to start an SMTP conversation with the receiving server and stop just before sending anything:

S: 220 mx.example.net ESMTP ready
C: EHLO validator.example.com
S: 250 Hello
C: MAIL FROM:<check@example.com>
S: 250 OK
C: RCPT TO:<person@example.com>
S: 250 OK            <- mailbox accepted?
C: RSET / QUIT
Enter fullscreen mode Exit fullscreen mode

(Illustrative session, not a captured transcript.)

A 550 here often means "no such user" and a 250 is taken as "mailbox exists". Both readings are unreliable, for structural reasons:

  • Accept-all edges. Many servers — especially corporate gateways and Microsoft/Google tenants fronted by security appliances — accept RCPT TO for any local part and decide what to do with the message later. Your probe gets a 250 for mailboxes that do not exist, and the bounce only happens after a real send, if it happens at all (some systems silently discard).
  • Anti-enumeration defenses. Large providers deliberately stopped answering honestly years ago, precisely because probing was used to harvest valid addresses. A 250 from them is close to meaningless; a 550 may be policy, not absence.
  • Greylisting and tempfails. A 450/451 can mean "come back later" (greylisting), rate limiting of your probing IP, or a tarpit deliberately slowing you down. It is not a verdict in either direction.
  • Your reputation, not theirs. Probing is done from your IP. Datacenter IPs are frequently refused outright, and sustained probing patterns get IPs listed. The probe measures your standing with their filters as much as the mailbox.

The professional conclusion: an SMTP probe is a weak signal, strongest when it returns an explicit 550 user unknown from a server you have already established is not catch-all, and weakest everywhere else. Which raises the real question — how do you know a domain is catch-all?

5. Catch-all domains: where mailbox validation ends

A catch-all (accept-all) domain accepts mail for every local part, real or invented. The standard detection is simple: probe the server with a local part that cannot exist — a long random string:

RCPT TO:<xq7zv2k9plm4rf8d@example.com>
250 OK   -> this domain accepts anything: it is catch-all (at this edge, today)
Enter fullscreen mode Exit fullscreen mode

If the random address is accepted, mailbox-level validation for that domain is over. Every address there is "unverifiable" by construction — not valid, not invalid. In B2B lead data this is not an edge case: small and mid-size companies land on catch-all configurations all the time, through default hoster settings, migration leftovers, or a deliberate "never miss a message" choice by their admin.

How a pipeline should treat catch-all addresses:

  • Never merge them into "valid". Keep a separate bucket with its own sending policy (for outreach: send later, send fewer, watch the first bounces closely).
  • Never auto-delete them either. A catch-all domain contains real people; the CEO's address is in there next to a thousand invented strings. Deleting the bucket deletes your best leads along with the noise.
  • Cache the verdict per domain, not per address, and re-check periodically — catch-all status is a server configuration, and configurations change.

This is the same discipline we apply to any layered signal system: in our research into Shopee's security headers, the recurring lesson was that one signal rarely means what it appears to mean until you know which layer produced it. Email is no different.

6. Supporting signals that are actually worth collecting

Around the three main layers, a few cheap checks add real value:

  • Disposable/temporary domains. Maintain or license a list of throwaway-mail domains. The address may be perfectly valid and worthless in ten minutes — that is a question-4 decision, and for lead pipelines the answer is usually reject.
  • Role accounts. postmaster@, admin@, sales@, contato@ are deliverable but shared inboxes. Tag them; do not drop them — in small companies the role inbox is the decision-maker's inbox.
  • Provider fingerprint from MX. Knowing a domain sits on Google Workspace vs. a small regional hoster changes how you interpret probes, bounces and rate limits downstream.
  • Domain typos. gmial.com, hotmial.com and friends are data-entry errors, not mailboxes. Suggest the correction to a human or a form; never silently rewrite an address and send to a stranger's mailbox.
  • Free vs. corporate domain. Not a validity signal at all — but valuable routing metadata for enrichment and segmentation.

7. Designing the pipeline: verdicts with evidence, not scores

A robust validation stage stores, per address: the syntax result, the MX set and provider, the null-MX flag, the catch-all verdict (and when it was measured), the probe outcome if you probe at all, and timestamps for everything. The output is a label with a reason, for example:

| Evidence | Label | Pipeline action |
|---|---|
| Bad syntax / NXDOMAIN | invalid | Drop, log reason |
| Null MX (RFC 7505) | invalid | Drop, do not retry |
| No MX, A record only | low-confidence | Hold for review or enrich later |
| Catch-all domain | unverifiable | Separate bucket, cautious policy |
| MX ok, probe 550 user-unknown, not catch-all | likely-invalid | Drop, keep evidence |
| MX ok, everything else neutral | deliverable | Proceed |

Two design rules make this work over time. First, never overwrite the raw input — store the pipeline's conclusions next to the original address, so a better future check can re-derive them. Second, revalidate on a schedule: MX records move, domains lapse, catch-all gets switched off. A validation result is a measurement with a date, not a property of the address. (It is the same reason our automations are built to be safely re-runnable — idempotent by design, so re-checking never corrupts state.)

8. Frequently asked questions

Does a 250 response to RCPT TO guarantee the mailbox exists?
No. On catch-all and accept-all systems it only proves the server accepts the address at the edge. Delivery — or a later bounce — is decided afterwards.

Is SMTP probing illegal or abusive?
Probing is a normal protocol conversation, but at scale it looks exactly like address harvesting, which is what the defenses are built against. Expect blocks, respect them, and prefer MX-level checks plus evidence-based scoring for bulk hygiene. Sending one real, wanted email remains the only ground truth.

Can I skip probing entirely?
For list hygiene, mostly yes: syntax + MX + null MX + catch-all detection removes the addresses that damage sender reputation (hard-invalid domains), which is where most of the value is. Probing adds a marginal signal at a real operational cost.

How often should a lead list be revalidated?
Before any major send, and periodically for standing lists — domains and mail configurations change constantly. A verdict older than a few months should be treated as stale until re-measured.

References


Originally published on botslave.com. Botslave is an automation studio — see what we build and how we work.

Top comments (1)

Collapse
 
damiana_teliti_5b0178f9f4 profile image
Damiana Teliti •

"Four questions, not one" is the best framing of this I've seen. Collapsing them into one score is exactly how catch-all addresses end up counted as "valid".

One practical addition for the catch-all layer: the usual detection trick is to probe a random, clearly non-existent local part (something like a long random string) on the same domain before probing the real address. If the server accepts the random one too, the domain is catch-all and the answer for the real address carries no information. Two things to watch: do it on the same SMTP connection so you don't double the rate-limit pressure, and cache the result per domain, because big lists usually have many addresses at the same company.

Also agree on recording evidence rather than a verdict. Keeping the raw SMTP reply code next to the label makes it much easier to re-score later when your rules change.