Outcomes
Every verification carries two independent things, on two fields, and it is worth being precise about which is which:
| Field | Answers | Values |
|---|---|---|
status |
Has it finished? | pending, complete |
result.outcome |
What did we conclude? | deliverable, invalid, catch-all, unknown |
result (and recovery) are null while status is pending — a check that has not finished has
reached no conclusion, and unknown is a conclusion rather than the absence of one.
This page is about the second column. It applies identically everywhere a verdict is written down:
single-verify responses, poll results, list rows, webhook payloads, the batch CSV's Outcome
column, and the dashboard.
On this page
result.outcome values
| Value | Meaning |
|---|---|
deliverable |
Mailbox confirmed deliverable via SMTP |
invalid |
Mailbox does not exist or hard-rejected |
catch-all |
Domain accepts all addresses; deliverability unconfirmable |
unknown |
Could not determine — DNS failure, transient error, or no SMTP probe data |
sub_status values
sub_status provides additional signal within a top-level status. It is an empty string
("") when none of the conditions below apply.
| Value | Meaning |
|---|---|
disposable |
Address belongs to a known disposable / temporary email provider |
role_based |
Address is a role alias (e.g. admin@, support@, noreply@) |
accept_all |
Domain is a catch-all — every address appears to succeed |
no_dns_entries |
No MX records found and DNS did not return a transient error |
mailbox_not_found |
SMTP server returned 550 or 551 (user unknown) |
greylisted |
SMTP server returned a 4xx temporary rejection — may be greylisting |
mail_server_did_not_respond |
DNS resolved but SMTP connection timed out or was refused |
Sub-status derivation is first-match:
disposable > role_based > accept_all > no_dns_entries > mailbox_not_found >
greylisted > mail_server_did_not_respond.
risk
risk is a single planning signal for when you want one field rather than a decision table. It is
assessed at probe time, from everything the check observed — including things this response does
not otherwise report to you.
| Value | What it means |
|---|---|
none |
A confirmed mailbox with no adverse signal |
low |
A free-mail domain, or an address we could not verify conclusively |
medium |
A catch-all domain, a role address, a non-ASCII local part, or a mailbox whose probe was deferred by the server |
high |
A definitive rejection, a disposable domain, a domain with no mail server, or a structurally suspect address (a quoted or commented local part) |
It is not a function of outcome. Several of its inputs — the SMTP conversation's exact status,
the domain's mail-server state, DNS transience, and the address's own syntax — appear nowhere else
on the response, so risk will sometimes say more than outcome and details together do. The
clearest case is a deferred mailbox: the server answered with a 4xx temporary failure, so
outcome is unknown and sub_status is greylisted, and risk is medium because the mailbox
is genuinely uncertain rather than merely unchecked. Read the four values as an ordered scale, not
as a formula over the other fields.
It is present on every completed result, live and test-mode alike. It is omitted whenever the
check carries no recorded assessment. That rule is wider than the one for free_email and
details, which are withheld only when the stored probe detail cannot be read: risk is also
absent on a healthy row that has no stored detail at all — a check completed before risk became
a recorded value, until it is backfilled — as well as while a check is still pending. An absent
risk means unknown, never none.
Deliverability guidance
result.outcome |
Use in mailing list? |
|---|---|
deliverable |
Yes |
invalid |
No — remove from list |
catch-all |
Use with caution; domain accepts all mail but individual mailboxes may not exist |
unknown |
Treat with caution; retry later if possible |
recovery
When the address you sent is dead, we sometimes find a live alternative for the same contact. When
we do, we probe it before telling you about it — so recovery is never a spelling guess, and
you never have to decide whether to trust it.
{
"object": "verification",
"id": "1c9d2f80-4f3a-4c19-9a51-52a1a4e0c001",
"created_at": "2025-06-10T14:07:44.010Z",
"livemode": true,
"status": "complete",
"email": "alice@gmial.com",
"result": {
"outcome": "invalid",
"sub_status": "no_dns_entries",
"risk": "high",
"free_email": false,
"account": "alice",
"domain": "gmial.com",
"details": { "...": "..." }
},
"recovery": {
"email": "alice@gmail.com",
"outcome": "deliverable"
},
"completed_at": "2025-06-10T14:07:48.220Z",
"credits_remaining": 4819
}
recovery.email is always a complete address — both the local part and the domain are
considered, and whichever did not change is carried through verbatim — and it is always an
addition. email still holds exactly what you submitted; nothing is ever silently substituted.
recovery.outcome uses the same vocabulary as result.outcome, so there is nothing new to learn:
| Value | Meaning |
|---|---|
deliverable |
A probe confirmed a mailbox at the recovered address |
catch-all |
The fix landed on a domain that accepts everything; the mailbox itself is unconfirmable |
There is deliberately no confidence field. The only decision you have about a recovered address
is whether to mail it, and outcome is the field that answers it — a separate certainty tier would
be our language about our own workings, mapping to nothing you can do.
recovery is null unless a probe resolved something unambiguous. In particular it is null when
several different candidates came back deliverable, or several different domains came back
catch-all: a guess between them is exactly the "wrong person" risk this feature exists to avoid.
A recovery is billable work. A definitive outcome or a recovery is what you pay for. So an
unknown result carrying a recovery is charged, and an unknown with no recovery is refunded —
if you see a charge against a non-definitive result, recovery in that same response is the reason.
The batch CSV carries the same two facts, in the same words, as the RecoveredEmail and
RecoveredOutcome columns.
catch-all vs catch_all — the naming convention
Both spellings appear in a result, and the difference is not an inconsistency. It is the API's naming rule:
| Spelling | What it is |
|---|---|
catch-all (hyphen) |
A value — the result.outcome enum member, sharing one vocabulary with the CSV export, the dashboard and this documentation |
catch_all (underscore) |
A field name — details.catch_all, and catch_all_count in a batch summary |
The rule: values are hyphenated, field names are snake_case. Every field on the wire is
snake_case (sub_status, free_email, created_at, mx_record), and the outcome vocabulary is a
single shared set of words used identically everywhere a verdict is written down.
Unifying the two would break one of those two guarantees, so both spellings are permanent. When you
write a comparison, match result.outcome == "catch-all" and read result.details.catch_all.
The full result object
{
"object": "verification",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"created_at": "2025-06-10T14:02:09.180Z",
"livemode": true,
"status": "complete",
"email": "alice@example.com",
"result": {
"outcome": "deliverable",
"sub_status": "",
"risk": "none",
"free_email": false,
"account": "alice",
"domain": "example.com",
"details": {
"role": false,
"disposable": false,
"catch_all": false,
"mx_found": true,
"mx_record": "mail.example.com",
"smtp_provider": "Google",
"smtp_code": 250
}
},
"recovery": null,
"completed_at": "2025-06-10T14:02:11.432Z",
"credits_remaining": 4820
}
credits_remaining is null for system and demo accounts, and also on list rows — a balance is
a per-request figure, so read it from GET /api/v1/credits.