Skip to content

Method

How we verify an email address

Eight stages, what each one can return, and why the page stops short of an accuracy percentage.

Verification asks the receiving mail server whether it would accept mail for an address, and stops before any message is sent. That is the whole of it at one sentence. The rest of this page is the sequence underneath, because the difference between two verifiers is almost never the SMTP conversation — it is what they do in the seven stages around it.

  1. 1

    Normalise

    The address is parsed and canonicalised. The domain is lower-cased; the local part is not, because the standard treats it as the receiving server’s business and a pipeline that lower-cases whole addresses is making an assumption that is usually safe and occasionally wrong. A malformed address stops here as undeliverable / syntax_error, and one over 254 characters as length_error. Neither costs a probe.

  2. 2

    Classify

    The address is labelled: is the domain a known disposable provider, a consumer mailbox provider, a role account, a local part that looks machine-generated. These labels never change the verdict. They are reported beside it, because "this mailbox exists" and "you probably should not write to it" are two different findings and collapsing them throws away the one you cannot reconstruct.

  3. 3

    Circuit gate

    Before any network call, a breaker is checked for the receiving provider. If checks against that provider are currently failing in a way that suggests the problem is ours rather than the address’s, the check is rescheduled rather than answered: unknown / circuit_open, retryable, not charged. A breaker that returns a wrong answer instead of no answer is worse than an outage.

  4. 4

    DNS and MX

    The domain’s MX records are resolved. No records, or an explicit null MX, is undeliverable / no_mx — the domain has said it does not receive mail. A domain that does not resolve at all is domain_not_found. A resolver failure is neither: it is unknown / dns_error, retryable, because a SERVFAIL is a statement about the resolver and marking live domains dead on one is the most expensive mistake available here.

  5. 5

    Catch-all knowledge

    What is already known about the domain is applied before spending a probe on it. A domain established as catch-all will accept every address, so the probe cannot settle this mailbox and the result is unknown / catch_all. That verdict is not retryable and is charged, because the probe established a real fact about the domain even though it could not establish one about the mailbox.

  6. 6

    Reserve a sending IP

    A probe is a real SMTP session from a real sending IP, so one has to be available and in the right state to use. Each IP carries a warmup ramp, a pacing curve, per-provider sub-caps, a reputation score and a cooldown. If nothing is available the check is not done: unknown / no_egress or egress_paced, retryable, not charged, and the API returns 503 ENGINE_CAPACITY with a Retry-After. Capacity is not a verdict.

  7. 7

    SMTP probe

    Connect to the MX host, EHLO, MAIL FROM, then RCPT TO naming the address — and stop. The conversation ends before DATA, which is the part where a message body would be transmitted. Nothing arrives in anybody’s inbox. The server’s reply code and its RFC 3463 enhanced code are recorded verbatim and travel back in the result.

  8. 8

    Settle

    The reply is turned into one of three verdicts and exactly one reason, and the credit is settled or released. A release writes no ledger row at all — a retryable result is not charged and then refunded, it is never charged. The result is cached for seven days per organisation and canonical address, so asking the same question twice in a week does not spend a second probe on somebody else’s server.

Why the sending infrastructure is the accuracy story

The protocol answer is small enough to implement in an afternoon, and people do. What happens next is the part that decides whether the answers are any good: a new IP address opening thousands of short SMTP sessions is, at the protocol level, indistinguishable from a dictionary attack. Receiving providers throttle it, then block it — and from that point every answer the code produces is unknown at best and a false negative at worst.

So every outbound probe is metered against a specific sending IP with a warmup ramp, a pacing curve, per-provider sub-caps, a reputation score and a cooldown, across a multi-IP pool with peer delegation and separate blocklist and reverse-DNS monitoring. Accuracy at volume is a function of that, not of the parser.

It is also why nobody can honestly offer unlimited verification, and why a capacity shortfall here produces a deferred check rather than a wrong answer. If no IP is available in the right state, the honest output is “not yet”.

Five cases a naive verifier gets wrong

Each of these is a place where the convenient answer and the true one differ. The verdict column is what the engine returns, and npm run engine:check fails the build if this page and the contract stop agreeing.

Situations where the convenient verdict and the true one differ
SituationWhat we return
The receiving MX said our IP is blockedunknown / ip_blocked, retryable — the mailbox was never tested. Not charged.
DNS returned SERVFAILunknown / dns_error, retryable — a resolver incident must not mark live domains dead. Not charged.
A disposable address whose mailbox does not existundeliverable / mailbox_not_found — a hard rejection outranks every classifier.
A catch-all domain answering 250unknown / catch_all, NOT retryable — the 250 describes the domain, not this mailbox. Charged.
A mailbox over quotaunknown / mailbox_full — a real mailbox, but mail queued to it bounces. Charged.

Which results are free, and which are not

“Unknown is never charged” is the line everyone in this category uses, ourselves included until we checked. It is not true here and we would rather say so: the charge follows retryable, not the verdict. 16 of the reason codes are transient and cost nothing; a catch-all or a full mailbox is an unknown that is charged, because the probe established a real fact. Four competitors have a more generous rule and the pricing comparison says which.

The vocabulary behind all of this

Questions about the method

How does email verification work?

The address is parsed, the domain’s MX records are resolved, and then an SMTP conversation is opened with the receiving mail server: EHLO, MAIL FROM, RCPT TO naming the address, and stop. The reply to RCPT TO is the answer. The conversation ends before DATA, so no message is ever transmitted.

Does verifying an email send them an email?

No. The SMTP conversation stops before the DATA command, which is the point at which a message body would be transmitted. Nothing reaches the recipient’s inbox and nothing is logged against them as a delivery.

How accurate is email verification?

We publish no accuracy figure, because we have not measured one in a way that would survive being quoted back at us — and neither has anyone else in this category in a form an outsider could reproduce. What we publish instead is the method above and the evidence behind every individual verdict: the reply code, the enhanced code, the MX host that answered, and the reason. A percentage with no method behind it is a number, not a claim.

Why does a verifier return unknown?

Because the mailbox was genuinely not tested, or was tested and the answer described the domain rather than the mailbox. A blocked probe, a DNS failure, greylisting, a server too busy to answer, a catch-all domain, a full mailbox. The reason field says which, and whether asking again would change it.

Can you verify a catch-all email address?

No, and nobody can. A catch-all domain accepts every address at the SMTP layer, so a positive reply describes the domain rather than the mailbox. It comes back as unknown with the reason catch_all and a stated ceiling on confidence, rather than as a valid result with a probability attached to it.

Run it on an address you know

Three checks a day with no account, or 100 credits a month free and renewing. The reply code and the reason come back either way.