Reference
Address validation
Is this address worth sending to? Syntax, the domain’s ability to receive mail, the things that make an address risky rather than dead — and the one check nobody else can perform: whether you have already bounced off it.
One call
/v1/address-checksverifications:readPOST /v1/address-checks
{ "address": "[email protected]" }
200 OK
{
"address": "[email protected]",
"localPart": "sam",
"domain": "example.com",
"verdict": "deliverable",
"reasons": [],
"checks": {
"syntax": "pass",
"domain": "pass",
"mx": "pass",
"disposable": "pass",
"role": "pass",
"suppressed": "pass"
},
"mxHosts": ["mx1.example.com"]
}POST rather than GET because an address in a query string is personal data in every access log between you and us — yours, ours, and anything in between.
Prefer the batch, and send up to a hundred addresses in one call. A hundred is the ceiling on addresses, the same one /v1/emails/batch carries; a hundred and one is an invalid_request, so a list of any size is chunked into hundreds. Lookups are deduplicated by domain, so sixty Gmail addresses and forty Outlook ones are two DNS queries rather than a hundred; it is also the shape the rate limits are designed around. Cleaning a list one serial request at a time is slower for you and worse for everybody.
Four verdicts
| Verdict | Means | Do |
|---|---|---|
deliverable | Syntax is sound and the domain accepts mail. | Send. |
risky | It will probably arrive, but something is worth knowing — a throwaway provider, a role mailbox, or an address on your own suppression list. | Usually send. Think twice before adding it to a marketing list. |
undeliverable | It cannot work: the syntax is invalid, the domain publishes no way to receive mail, or the address is suppressed by something you cannot overrule — a hard_bounce, which is the receiving server’s permanent word that the mailbox does not exist, or a platform-wide entry. In every one of those cases the send would be refused, so the verdict says so rather than inviting a retry. | Drop it, or offer the correction in didYouMean. |
unknown | We could not find out. DNS did not answer. | Nothing. Ask again later. |
What deliverable does not mean. It never means “this mailbox exists”. It means the domain will accept mail and we know nothing against this address — the syntax is within the RFC 5321 bounds, the domain publishes somewhere to deliver, the provider is not a throwaway one, the local part is not a role box, and the address is not on your own suppression list. That is the whole of it.
Whether sam@ is a real person at that domain is a question only the receiving server can answer, and the only way to ask it is to open a mail transaction against a stranger’s server and abandon it — which is the one thing we will not do. So a deliverable address can still bounce, and the bounce is the first true evidence anybody gets. What this endpoint is for is removing the addresses that were never going to work, not promising the ones that remain.
unknown is never a reason to drop an address. A transient DNS failure is the absence of evidence, not evidence — and treating the two as the same thing is how a five-minute resolver wobble turns into a permanently deleted contact list. It arrives as a 200 with retryable: true, it is never cached, and it is never billed.
Acting on a verdict
Four verdicts, four branches, and only three of them do anything. The one that catches people out is risky: it is not a softer undeliverable, it is a deliverable address with a fact attached, and the fact — not the verdict — is what should decide. So the branch reads checks, not just verdict.
// Cleaning a signup list before a campaign. One call per hundred addresses.
const { data, summary } = await posthaste.addresses.checkMany(batch);
const send = [];
const askAgain = [];
const offerCorrection = [];
for (const result of data) {
switch (result.verdict) {
case 'deliverable':
// The domain accepts mail and nothing is known against this address.
// NOT a promise that the mailbox exists.
send.push(result.address);
break;
case 'risky':
// It will arrive; the reason decides whether it should. Your own
// suppression entry is a bounce or a complaint you already received —
// sending anyway is what turns a soft problem into a blocked domain.
if (result.checks.suppressed === 'fail') break;
// A role box is fine for a receipt and wrong for a marketing list, so
// branch on what you are about to send rather than on the verdict.
if (campaign.kind === 'marketing' && result.checks.role === 'fail') break;
send.push(result.address);
break;
case 'undeliverable':
// Drop it — but a typo is the user's to fix, not yours. didYouMean is
// offered ONLY when the domain cannot receive mail at all.
if (result.didYouMean) offerCorrection.push([result.address, result.didYouMean]);
break;
case 'unknown':
// Do NOTHING. DNS did not answer, which is the absence of evidence.
// Deleting on it is how a five-minute resolver wobble eats a list.
askAgain.push(result.address);
break;
}
}
// Billed: data.length - summary.unknown. An unknown costs nothing.What happens to the three lists afterwards is the whole point. send goes out. offerCorrection goes back to the person who typed the address, because [email protected] is a mistake only they can confirm — writing the correction in for them is how you end up mailing a stranger. And askAgain is checked again on the next run and never deleted: it is the list of addresses about which nothing was learned, which is a different thing from the list of bad ones.
One shape to avoid: validating an address at signup and never again. A mailbox that worked in March is closed by December, and this endpoint reads DNS and your own suppression list — both of which move. Re-checking a list before a campaign is cheap; the bounce that follows not re-checking is not.
What is actually checked
| Check | What it catches |
|---|---|
syntax | Shape, plus the RFC 5321 bounds — 64 octets for the local part, 254 for the address. Octets, not characters: an address whose local part is 40 CJK characters passes a length check and is refused by the receiving server. |
domain | Whether the name resolves at all. |
mx | Mail exchangers, with the RFC 5321 §5.1 fallback: a domain with no MX but an A record still accepts mail, and calling that undeliverable is the most common bug in validators that only ask for MX. A null MX (RFC 7505) is its own answer — a published refusal, which also forbids the A-record fallback. |
disposable | Throwaway mailbox providers. Risky, never fatal — they usually accept. |
role | support@, info@, billing@ and the rest of RFC 2142. Never fatal: what a role address predicts is a complaint, not a bounce — several people read it and none of them signed up. |
suppressed | Whether this address is on a suppression list that applies to you. Everybody else infers deliverability from public records; we hold the bounce and the complaint for the addresses you have actually mailed. No other account’s list is visible here, and yours is not visible to them. |
When this check fails, the response carries suppressedBy, and the two values mean different things to you.
| suppressedBy | What it means |
|---|---|
account | Your own entry, from your own sending history. You can remove it — except a hard_bounce or a complaint, which are facts about the address rather than preferences about it. |
platform | An address suppressed across the whole platform, which you cannot remove and which will refuse the send whatever you decide. Rare, and it is why this check reads more than your own list: sending is refused for these at the API call, so a checker that looked only at your rows would answer deliverable for an address the very next request rejects. |
Did you mean
{
"address": "[email protected]",
"verdict": "undeliverable",
"reasons": ["no_mail_exchanger"],
"didYouMean": "[email protected]"
}Offered only when the domain cannot receive mail at all. A suggestion against a working domain would be worse than none: gmai1.com may be somebody’s real company, and “correcting” a deliverable address means sending mail to a stranger.
What we refuse to tell you
There is no catch-all detection and no catchAll field, permanently. Answering it means probing a stranger’s mail server with RCPT TO, which is indistinguishable from directory harvesting, does not work against Gmail, Outlook or Yahoo, would make this a public mailbox oracle, and would predict the wrong sending pool anyway. The long version is on deliverability.
What it costs
One address examined, against your plan’s monthlyValidations allowance — so a batch of a hundred costs a hundred. A cache hit costs one too: what you bought is the answer, and it is no less true for having been cheap to produce.
An unknown costs nothing. A batch that would cross the ceiling is refused whole rather than served partly, because a half-checked list is worse than an unchecked one — you cannot tell which half. The figure is on GET /v1/usage.
NextVerify →Email a one-time code and check it. The attempt cap, expiry, resend throttle and the rule that keeps the code out of the subject line are already built.