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 [email protected] 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 [email protected] [email protected] --json
{
  "estimate": {
    "first_pass_credits_per_email": 0.23,
    "availability_fallback_credits_per_email": 0,
    "catch_all_escalation_credits_per_email": 1.7,
    "min_credits": 0.46,
    "max_credits": 3.86,
    "recommended_max_credits": 3.86
  }
}

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 [email protected] [email protected] --mode live --max-credits 3.86 --json

If the ceiling runs out mid-batch, verification does not fail. A catch-all address whose escalation no longer fits comes back unescalated with escalation_skipped: budget_exhausted — a budget signal, not a provider outage. Once the ceiling is fully spent, the remaining addresses are not checked at all: they come back unknown with skipped: credit_ceiling_reached. Re-run those with a higher ceiling.

Re-verifying the exact same address in this workspace within 30 days reuses the stored first-pass answer at no charge, so a repeat run can report credits_used: 0.

oxygen verify email takes up to 5,000 addresses. The free preview prices the whole list at once, and a live run sends it in pages of 100 under the one --max-credits ceiling. If a page fails partway, the command stops with verify_email_incomplete: details.verified holds the finished results, failed_page_emails the page that failed, and not_sent_emails the rest. oxygen_verify_email takes at most 100 addresses per call. For a longer list, use an Email verification column.

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.
outcome_unknownThe provider was dialled but never confirmed a result. Its credits are held, not charged, until the outcome settles.Don't re-run the address yet; a repeat could be charged twice. Check the hold with oxygen billing usage --meter holds.
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.

If a verifier was called but its answer never arrived — for example, the request timed out after it went out — the attempt shows error_code: provider_outcome_unknown. Its credits are held, not charged, until that outcome is settled. List open holds, and whether each returns automatically, with oxygen billing usage --meter holds --json, and don't re-run that address while its hold is open.

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. An unknown verdict writes no value, so those rows show an empty cell — re-run them rather than filtering them out.

In the app, open a table and click + Add column → Email verification. It is pinned under Most used, with Work email and Mobile phone. 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 and mapped to a column named email or work_email when the table has one. With no such column it is still created, and the response names the columns update --input-mapping command that points it at yours. --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.

The run also adds two companion columns, email_verification_source (the verifier that answered) and email_verified_at (when), so an old verdict is easy to spot.

To grade with one verifier instead of the chain, name it with --provider-order: millionverifier, zerobounce, icypeas, dropleads, or hunter. A named verifier runs alone — the catch-all escalation is switched off for that column, so catch-all rows keep catch_all.

oxygen enrich-column preview <table> --capability verify_email --provider-order zerobounce --json

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