OXYGENOxygen/ Docs
Providers

Email verification

Check whether addresses you already have are safe to send to, including catch-all domains.

Verification grades an address you already have: is it safe to send to? Finding an address you don't have is a different chain — see Waterfalls.

One verifier chain serves every surface — the verify_email enrichment column, oxygen verify email, the oxygen_verify_email MCP tool, and POST /api/cli/verify/run — so the same address cannot get a different verdict, or a different price, depending on where you asked. The chain's current members are listed under Waterfall defaults.

Verdicts

VerdictMeaningSend to it?
validA verifier confirmed the mailbox exists.Yes
invalidThe mailbox does not exist, or the address is disposable, a spam trap, or do-not-mail.No
catch_allThe domain accepts mail for every address, so no verifier can confirm this specific mailbox.Judgement call — see below
unknownNo 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_all verdict — never speculatively, and never on unknown (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.

Previewing readiness

The free preview checks the selected route without verifying the address. Read catch_all_escalation_available: false reports a known blocker and a next_action; null with catch_all_escalation_availability: "unknown" means Oxygen could not check availability, so retry the preview. true means the snapshot found no blocker. It does not prove a fresh vendor wallet balance or guarantee that a later live call will succeed. A tool labelled runnable has the same limit.

Use the specific tool's availability and the verification preview for the selected managed or BYOK route. The integrations catalog describes connection options. A configured route does not authorize spending: live execution checks the provider, eligibility and your approved ceiling again.

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 estimate's worst case: every verifier plus an escalation on every address) 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 --json

If 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.

A catch_all whose escalation did not run is recorded as unconfirmed, not as verified. The address keeps catch_all; treat it as an address nobody has cleared yet, and re-run it when the reason below says re-running will help.

Every skipped escalation carries a next_action sentence — on the address, and again at the top of the envelope for the most serious one in the batch.

escalation_skippedCauseFix
budget_exhaustedRemaining ceiling could not cover an escalation, so it was never dialled. Nothing was charged for it.Re-run with recommended_max_credits.
escalation_unavailableThe escalation could not run or could not answer. A known account refusal or staff hold can stop it before a provider call. Read escalation_unavailable_reason.See the next table.
not_configuredEscalation was explicitly disabled.Nothing, if intended.
escalation_unavailable_reasonCauseFix
provider_account_dryThe account the escalation bills against has no provider credits left. On the managed lane that is Oxygen's account with the vendor — not your workspace credit balance — and you were not charged for the escalation.Nothing on your side: Oxygen staff have to top the managed account up, and re-running before that fails the same way. Re-run the affected addresses after the top-up. On a BYOK connection it is your own vendor account: top it up, then re-run.
provider_account_heldOxygen staff has placed a hold on the managed account. The escalation was not charged; an earlier first pass may still have cost credits.Oxygen staff must review and release the hold. Funding the account or waiting for a retry interval alone does not release it. Keep the address unconfirmed.
not_configuredNo usable credential for the escalation provider on this workspace. Nothing was charged.Connect your own key (oxygen integrations connect bounceban --api-key <key>), or ask Oxygen support to enable the managed lane.
provider_errorThe provider was dialled and returned an error or timed out. The failed leg is not counted in credits_used.Usually transient — re-run later. The attempt's provider_message holds what the vendor actually said.

Each attempt in the trace carries a plain-sentence message plus the vendor's own provider_message; message is the one that explains the outcome.

Verifying a whole table

Add an Email verification 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.

In the app, open a table and click + Add columnEmail verification. It sits with Work email, Mobile phone, and LinkedIn URL. If your table has a column named email or work_email, Oxygen maps it automatically; otherwise pick it in the column editor.

From the CLI, the same click is one command — it creates the column and stops there. It costs 0 credits and verifies nothing; the column sits ready until you run it.

oxygen columns add <table> --capability verify_email --json

The column comes back as kind: enrichment with a jsonb cell, labelled Email Verification with the key email_verification, already pointed at the same MillionVerifier → BounceBan chain the picker wires up. --label and --key override the defaults. Use this whenever you want the column defined now and run later — enrich-column run refuses without --approved and --max-credits, and creates nothing when it refuses.

To verify, preview for free first — the preview returns recommended_max_credits, which is the number to pass:

oxygen enrich-column preview <table> --capability verify_email --json
oxygen enrich-column run <table> --capability verify_email \
  --max-credits <recommended> --approved --json

--email-column <key> names the source column explicitly when auto-detection picks the wrong one.

Do not hand-build a single-provider action column for this — that skips the escalation, so every catch-all row comes back unresolved. If you searched the column picker for a verifier and found a provider operation, that is the shape this warning is about: use the Email verification column instead.

  • Runs — per-cell provenance and attempt traces
  • Columns — adding the verify_email enrichment column

On this page