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) and that reply settled the address. Never reported beside outcome: "unknown": a 5xx we could not interpret, or one that refused us on policy grounds rather than naming the mailbox, leaves the address unresolved and is reported as mail_server_did_not_respond
greylisted A 4xx temporary rejection from the mail server. Any of three: the server answered this address's own probe with a 4xx — may be greylisting; or it kept deferring and our retry budget ran out before it accepted or rejected the address; or a 4xx ended the connection before this address was asked about — the server stopped the conversation rather than deferring this mailbox, so the address is unchecked rather than uncertain and risk is low instead of medium. The last code the server sent is in details.smtp_code in every case. It is an uncertainty signal and is therefore never reported beside deliverable or catch-all — see the note under the precedence line
mail_server_did_not_respond No usable answer from a mail server. Any of three: a mail server was identified and the SMTP conversation produced NO REPLY AT ALL — the connection timed out, was refused, or the probe budget ran out without the server ever answering about this address; or the MX lookup itself failed transiently, so no host could be dialled; or a server did reply and what it said settled nothing about this address — a policy refusal, a rejection relayed from another hop, or wording we do not recognise — and our retry budget then ran out. details.smtp_code carries the reply's code whenever there was one

Sub-status derivation is first-match: disposable > role_based > accept_all > no_dns_entries > mailbox_not_found > greylisted > mail_server_did_not_respond.

A sub-status never contradicts the outcome beside it. The three address- and domain-level reasons (disposable, role, catch-all) apply to any outcome, but the SMTP reasons are constrained in both directions: the ones that mean "we could not settle this" are not reported on a deliverable or catch-all result, and the one that means "the server said this mailbox does not exist" is not reported on an unknown one. A settled result with no further signal carries an empty sub_status. That is why a deliverable result can carry a 4xx in details.smtp_code with no sub-status at all: some temporary rejections prove the mailbox exists — a mailbox full deferral is the clearest case, since a mailbox must exist to be full — so the address is resolved, and describing it as deferred would contradict the verdict you were given.

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 this address's own probe 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.

risk is not a formula over sub_status either, and the same pair of fields shows why. A greylisted result whose 4xx ended the connection before this address was ever asked about — the server stopped the conversation rather than deferring this mailbox — is genuinely unchecked, so it carries risk low while still reporting sub_status greylisted. The sub-status describes the reply we got; risk describes how much that reply tells you about this mailbox. 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.