Cold Leads

How accept-all detection works, and how to read Cold Leads results

Who it is for
Developers and ops teams who decide what to do with risky results
The problem
Some mail servers accept mail for any address at their domain, so a mailbox check cannot tell a real person from a made-up name. Other servers answer only temporarily, or cannot be reached at all, and a bare valid label can hide the fact that the mailbox was never checked.
The solution
Understand the SMTP dialogue a verifier runs and what each reply means, then read the status, score and reason codes Cold Leads returns, including the cases in which the mailbox probe did not run.
What you get
A decision table that maps every Cold Leads reason code to an action, and a script that applies it to one address.

Addresses, IDs and results in the examples are illustrative. example.com is reserved for documentation, so a real check of these addresses returns invalid.

The SMTP dialogue behind a mailbox check

A verifier looks up the domain's MX hosts and opens an SMTP connection to one of them on port 25. It greets the server, names a sender and a recipient, reads the reply to the recipient, and says goodbye before any message would be sent. The second session asks the same server about an address that cannot exist.

Two SMTP sessions (illustration)
# Session 1: is the address accepted?
S: 220 mx1.example.com ESMTP
C: EHLO verifier.example.net
S: 250-mx1.example.com
S: 250 SIZE 52428800
C: MAIL FROM:<check@verifier.example.net>
S: 250 2.1.0 Sender OK
C: RCPT TO:<anna@example.com>
S: 250 2.1.5 Recipient OK          <- accepted (a 550 here would mean: rejected)
C: QUIT                            <- no DATA command: nothing is delivered
S: 221 2.0.0 Bye

# Session 2: would the same server also accept an address that cannot exist?
S: 220 mx1.example.com ESMTP
C: EHLO verifier.example.net
S: 250 mx1.example.com
C: MAIL FROM:<check@verifier.example.net>
S: 250 2.1.0 Sender OK
C: RCPT TO:<zq7c41e09b2a6f5d@example.com>
S: 250 2.1.5 Recipient OK          <- also accepted: the server accepts all addresses
C: QUIT
S: 221 2.0.0 Bye

Reply codes and what they allow you to conclude

Reply codes are defined in RFC 5321. The first digit carries the meaning: 2 is success, 4 a temporary failure, 5 a permanent failure.

ReplyMeaning in RFC 5321What a verifier can conclude
220Service ready (the greeting banner)The server talks SMTP; the check can go on.
250, 251, 252Action completed, will forward, or cannot verify but will acceptThe server will take mail for this recipient now. On an accept-all server that says nothing about the mailbox.
421Service not available, closing the channelNo conclusion; the server is busy or refuses the checker.
450, 451, 452Mailbox unavailable, local error, insufficient storage (temporary)No conclusion. Greylisting servers answer this way to unknown senders and expect a retry minutes later.
550Mailbox unavailable, for example not found, or rejected by policyUsually the mailbox does not exist, but a policy block of the checking server looks the same.
551, 552, 553, 554User not local, storage exceeded, mailbox name not allowed, transaction failedPermanent refusal of this recipient in this session.

The accept-all probe, greylisting and other limits

  • Accept-all: after the real address is accepted, the verifier asks the same server about a random local part on the same domain. If that is accepted too, the first acceptance carries no information, and the result is risky with the reason catch_all.
  • Greylisting (RFC 6647): a server temporarily rejects unknown sender and recipient combinations with a 4xx reply and accepts a retry later. A single pass sees only the 4xx, and Cold Leads does not wait and retry within one check, so the result is risky with smtp_unknown.
  • Late bounces: some servers accept every recipient during the dialogue and reject unknown ones only after they have accepted a message, with a bounce. A check cannot see this.
  • Policy blocks: a server may refuse the checking host itself. Cold Leads treats refusals at the greeting, EHLO or MAIL FROM step as smtp_unknown, and any 500–559 reply at RCPT TO as mailbox_missing.
  • Port 25: the probe needs an outbound SMTP connection. Many cloud and serverless platforms block outbound port 25, and then no dialogue happens at all.

What Cold Leads returns, and when the probe did not run

A check runs, in this order: syntax (a failure ends the check), a built-in list of 40 disposable-mail domains, a list of 25 role names (info, sales, support, admin and others) and an MX lookup that falls back to the domain's A record. The SMTP mailbox and accept-all probe runs only when Cold Leads can open an SMTP connection to one of the first two MX hosts. The reasons array always tells you which case you have.

POST /api/v1/verify, a domain whose mail server could not be reached over SMTP
{
  "email": "anna@example.com",
  "status": "valid",
  "score": 75,
  "reasons": ["smtp_unreachable"],
  "mx": "mx1.example.com",
  "catch_all": false,
  "disposable": false,
  "role": false,
  "checked_at": "2026-09-30T08:15:02.114Z",
  "cached": false
}
Last reasonstatusscoreMeaning
okvalid97 (80 for role addresses)The server accepted the address and did not accept the random address.
catch_allrisky60The server accepted both the address and the random address.
mailbox_missinginvalid2The server rejected the recipient with a reply from 500 to 559.
smtp_unknownrisky50No definite answer: a temporary 4xx reply, a refusal of the checker, an MX host on a private network, or a mix of unreachable and unclear hosts.
smtp_unreachablevalid75 (65 for role addresses)No MX host answered on port 25 from the checking host. The domain accepts mail, but the mailbox and accept-all were not tested.
timeoutrisky50, or 60 if the address was accepted but the accept-all probe did not finishThe optional timeout_ms budget ran out. The result is not cached.
no_mxinvalid0The domain has neither an MX nor an A record.
syntaxinvalid0The address fails the syntax rules; nothing else is checked.
  • disposable and role appear before the last reason when they apply. A disposable domain makes the status risky with a score of 40 (ok), 30 (smtp_unreachable) or 20 (smtp_unknown).
  • catch_all is true only when the accept-all probe ran and the random address was accepted. Its false value is informative only when the last reason is ok or mailbox_missing; after smtp_unreachable, smtp_unknown or timeout, accept-all was not tested. The MCP tool verify_email, hosted or local, returns catch_all as null in those cases; the REST API returns false.
  • cached is true when the result comes from the 30-day cache; checked_at then shows the time of the original check.
  • Bulk job results carry only the last reason code, as reason.

A decision table, and a script that applies it

Last reasonSuggested action
okSend.
smtp_unreachableSend with care: small batches, and remove hard bounces straight away. The mailbox was not confirmed.
catch_allReview. Keep only if you have another signal that the person exists, such as a reply or a known address pattern at that company.
smtp_unknownReview. A new check within 30 days returns the cached result and still costs a credit.
timeoutCheck again with a larger timeout_ms, up to 30,000; timeouts are not cached.
mailbox_missing, no_mx, syntaxDrop.
disposable presentDrop for B2B outreach.
decide.mjs
// node decide.mjs anna@example.com   (Node 18+, COLDLEADS_API_KEY=sk_... in the environment)
const email = process.argv[2];
const res = await fetch("https://coldleads.app/api/v1/verify", {
  method: "POST",
  headers: { "x-api-key": process.env.COLDLEADS_API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({ email, timeout_ms: 10000 }),
});
const r = await res.json();
if (!res.ok) throw new Error(`HTTP ${res.status}: ${r.error}`);

const final = r.reasons[r.reasons.length - 1];
// catch_all only carries information when a mail server answered the probe
const smtpAnswered = ["ok", "catch_all", "mailbox_missing"].includes(final);
const ACTION = {
  ok: "send",
  smtp_unreachable: "send carefully: domain accepts mail, mailbox not checked",
  catch_all: "review: the server accepts every address",
  smtp_unknown: "review: no definite answer from the server",
  timeout: "check again with a larger timeout_ms",
  mailbox_missing: "drop",
  no_mx: "drop",
  syntax: "drop",
};
console.log({
  email: r.email,
  status: r.status,
  score: r.score,
  reasons: r.reasons,
  mailbox_checked: smtpAnswered,
  catch_all: smtpAnswered ? r.catch_all : "not tested",
  cached: r.cached,
  action: r.disposable ? "drop: disposable domain" : ACTION[final] ?? "review",
});

Limits and costs

  • 1 credit per verification, including syntax failures and results served from the 30-day cache.
  • timeout_ms is optional and accepts 1,000 to 30,000 milliseconds; any other value answers 400 bad_timeout.
  • The API is part of the Business plan: $99 a month with 10,000 credits a month; extra packs of 1,000 credits cost $5. 120 requests per minute per key.

FAQ

Why is an address valid when its mailbox was not checked?

Because failing to reach a mail server from the checking host is not evidence against the address. The status reflects the checks that ran: the syntax is right and the domain accepts mail. The lower score (75 instead of 97) and the reason smtp_unreachable tell you the mailbox itself was not tested.

Does catch_all false mean the domain is not accept-all?

Only when the probe ran, which you can see from a last reason of ok or mailbox_missing. After smtp_unreachable, smtp_unknown or timeout, accept-all was never tested.

Will checking a risky address again change the result?

Within 30 days a new check returns the cached result (cached: true) and still costs 1 credit. Timeout results are not cached, so those are worth checking again with a larger timeout_ms.

Does a verification send an e-mail to the person?

No. The dialogue ends with QUIT right after the recipient step, before the DATA command, so no message is delivered.