Email verification
Check whether addresses you already have are safe to send to, including catch-all domains.
Verification answers one question about an address you already have: is it safe to send to? It is separate from finding an address — finding produces a candidate, verifying grades one.
Oxygen runs one verifier chain for this, and every surface resolves to it: the verify_email table column, oxygen verify email, the oxygen_verify_email MCP tool, and POST /api/cli/verify/run. The same address cannot get a different verdict, or a different price, depending on where you asked.
Verdicts
| Verdict | Meaning | Send to it? |
|---|---|---|
valid | A verifier confirmed the mailbox exists. | Yes |
invalid | The mailbox does not exist, or the address is disposable, a spam trap, or do-not-mail. | No |
catch_all | The domain accepts mail for every address, so no verifier can confirm this specific mailbox. | Judgement call — see below |
unknown | No verifier could answer. This is an absent answer, not a bad address. | Don't drop these; re-run |
The two that get misread are catch_all and unknown. Filtering "everything that isn't valid" throws away good contacts: unknown usually means a verifier had a bad moment, and catch_all is extremely common on well-run corporate domains.
Catch-all domains
A catch-all (or "accept-all") domain answers yes to every address, so probing anything@company.com succeeds whether or not the mailbox is real. A generalist verifier can only flag this.
When that happens, Oxygen automatically escalates the address to a catch-all specialist that resolves a large share of them, including addresses behind secure email gateways. You do not configure this; it is the default chain.
Two rules keep the cost sane:
- Escalation fires only on a
catch_allverdict — never speculatively, and never onunknown(an inconclusive cheap pass is not evidence of a catch-all domain). - If the specialist cannot resolve it either, the address keeps
catch_all. Oxygen never upgrades a catch-all to verified because the escalation was unavailable.
For addresses that stay catch_all, the honest play is to send a small watched batch first, or confirm the person independently — verification alone cannot clear them.
Cost, and sizing the ceiling
Cost is not flat per address. Every address pays the first pass; only catch-all addresses additionally pay the escalation, which is several times more. A ceiling sized as "N addresses × first pass" runs out as soon as a few turn out to be catch-all.
Run the free preview first. It returns real numbers:
oxygen verify email a@acme.com b@acme.com --json{
"estimate": {
"first_pass_credits_per_email": 2.245,
"catch_all_escalation_credits_per_email": 17,
"min_credits": 4.49,
"max_credits": 38.49,
"recommended_max_credits": 38.49
}
}Pass recommended_max_credits (the all-catch-all worst case) as --max-credits. It is a ceiling, not a charge — you are billed for what actually runs, so over-reserving costs nothing.
oxygen verify email a@acme.com b@acme.com --mode live --max-credits 38.49 --jsonIf the ceiling runs out mid-batch, verification does not fail. The remaining catch-all addresses come back unescalated with escalation_skipped: budget_exhausted — a budget signal, not a provider outage. Re-run those with a higher ceiling.
escalation_skipped | Cause | Fix |
|---|---|---|
budget_exhausted | Remaining ceiling could not cover an escalation, so it was never dialled. | Re-run with recommended_max_credits. |
escalation_unavailable | The call was attempted and failed (no credential, or a provider error). | Check the connection, then retry. |
not_configured | Escalation was explicitly disabled. | Nothing, if intended. |
Verifying a whole table
Add a verify_email enrichment column pointed at the column holding your addresses. It runs the same chain, including the automatic catch-all escalation, and writes the verdict into the cell so you can filter and segment on it.
Do not hand-build a single-provider action column for this — that skips the escalation, so every catch-all row comes back unresolved.
Related
- Waterfalls — finding a value you don't have
- Runs — per-cell provenance and attempt traces