OXYGENOxygen/ Docs
Guides

Set up email sending

Buy or bring sending domains, provision mailboxes, fix DNS, and warm up — so a Sequence has somewhere to send from.

Cold email needs separate sending domains, mailboxes on them, correct DNS, and a warm-up before the first campaign. Pick a road first — switching later means buying twice.

Managed (OXYGEN buys)BYOK (your Cloudflare)
Prerequisitenonea Cloudflare account with Registrar access
Domain purchasebilled in OXYGEN credits at the live pricebilled to your Cloudflare card, 0 OXYGEN credits
SPF / DKIM / DMARC / MXset by the vendoryou run domains dns plan then dns apply
Mailboxbilled in OXYGEN credits per inbox monthlyyou supply the Google/Microsoft accounts

Managed inboxes bill monthly per inbox. From the 2026-09 repricing date a managed Google inbox is 500 credits a month all-in: a flat 400-credit mailbox plus the 100-credit connection every sending mailbox holds, with warm-up included. Microsoft and Azure inboxes stay priced from the vendor's cost and hold the same connection. Until then warm-up is its own monthly line. Inbox placement is always an optional add-on. Warm-up is not a managed-road-only cost: a BYOK mailbox enrolled with oxygen mailboxes warmup enable carries the same monthly per-inbox line, so both roads pay it while warm-up runs. From the 2026-09 repricing, warm-up on a mailbox you connected yourself is included in that mailbox's 100-credit monthly connection once the connection is charged; the warm-up preview's included_in_connection says whether it applies to you yet, and then quotes 0. Every price is in the pricing reference. A per-connected-mailbox monthly line exists in the billing code but is not charged today; it is listed there under Built, not currently charged.

Where this lives

Sidebar Sequencer → Accounts (/sequencer/accounts), tabs Senders / LinkedIn / Email / WhatsApp / Phone numbers. The Email tab nests inboxes under their domains; top-right is Add inboxes. Domains have their own page at /sequencer/domains — by URL only, deliberately not in the sidebar.

Road A — managed inboxes

In the app: Email tab → Add inboxes → Buy managed. The wizard steps Domains → Inboxes → Review; the last button is Buy · <n> cr. The first order asks for a Registrant contact (the WHOIS record); later orders reuse it. Closing mid-order saves the cart — the button becomes Resume order.

From the CLI, preview first — without --approved nothing is ordered or charged:

oxygen managed-inboxes subscribe send-acme.com --provider google \
  --file ./mailboxes.json --billing ./registrant.json --json

The order includes warm-up by default (--no-warmup opts out); inbox placement is added only with --placement. The preview lists the mailbox line, the add-on lines and, once the all-in price applies, each inbox's mailbox connection (connection_monthly_credits, reserved from your balance once the inbox connects, not charged with the order); total_monthly_credits is what the inboxes cost every month. Check quote_id, the lines, and warmup_activation, then order:

oxygen managed-inboxes subscribe send-acme.com --provider google \
  --file ./mailboxes.json --billing ./registrant.json \
  --approved --quote <quote_id>

--approved spends real credits and registers a real domain. Without a fresh --quote it is refused with quote_required, never charged; a stale quote is refused with quote_mismatch.

subscribe registers a new domain, or puts the first inboxes on a domain you bought on its own through OXYGEN (below). A domain that already has inboxes grows with add-inboxes instead. A domain holds at most 5 Google or Microsoft inboxes (100 on azure), counting existing ones:

oxygen managed-inboxes add-inboxes send-acme.com --count 3 --prefix ada --json
oxygen managed-inboxes add-inboxes send-acme.com --count 3 --prefix ada --approved --quote <quote_id>

Who the inboxes are ordered for — the sender profile and its photo

An order is placed for a person: pass --sender <id> (from oxygen senders profiles list --json) and every mailbox in it is stamped with that sender profile's first name, last name, and profile picture. The vendor cannot change any of the three after provisioning, so the profile is the identity to get right, once.

The photo follows one rule. A sender profile with a LinkedIn account attached carries that account's LinkedIn photo automatically — attaching the account (oxygen senders profiles attach <id> --senders <sender-id>, or picking it when creating the sender) mirrors the picture into Oxygen's own storage, and profiles connected before this rule are backfilled. A photo you upload yourself (oxygen senders profiles set-photo <id> --file <path>) or set by URL is your choice and is never overwritten by the LinkedIn one, so an upload always wins; removing the photo of a LinkedIn-connected sender is temporary, because the LinkedIn photo is adopted again on the next sync. Read the result back on the profile: avatar_source says where the picture came from, and avatar_durable: true means Oxygen hosts it and the vendor will still be able to fetch it hours after the order. avatar_durable: false means only an expiring LinkedIn link could be kept — Oxygen re-syncs the LinkedIn account and retries on its own; to fix it right now, upload a file.

Buy a domain on its own

Without a Cloudflare account of your own, Buy domains on /sequencer/domains registers the domain through OXYGEN. It costs the registrar's price in credits for one year (a .com is 1,250 credits) and renews yearly at the registrar's renewal price, charged 30 days before expiry. If your balance cannot pay the renewal, OXYGEN retries for 7 days and then lets the domain lapse.

oxygen domains search acme-mail --json                  # free: names with their yearly price in credits
oxygen domains buy acme-mail.com --json                 # free preview + quote_id
oxygen domains buy acme-mail.com --approved --quote <quote_id> --billing ./registrant.json
oxygen domains check acme-mail.com try-acme.com         # free: exact names, same prices
oxygen domains renewal acme-mail.com --off              # stop renewing; it lapses at expiry

The first purchase needs the registrant (--billing); later ones reuse it. oxygen domains list --json shows the domain under registered_domains with its expiry and next renewal charge. OXYGEN hosts its DNS and sets the mail records when inboxes are added; editing them yourself is not offered.

To send from it, order its first inboxes with the same subscribe command as Road A, or Add inboxes on its row on /sequencer/domains. The preview shows domain_source: owned and no registration charge; Google and Microsoft inboxes are offered:

oxygen managed-inboxes subscribe acme-mail.com --provider google --sender <id> --locals ada,ada.lee --json

oxygen domains delete acme-mail.com (or Delete on its row) ends the registration: any inboxes on it are cancelled, renewal stops, and the domain leaves the workspace. It stays registered until its expiry, which is not refunded, and this cannot be undone.

Road B — your own Cloudflare

Connect the account once at Connections (/connections), or Connect Cloudflare on /sequencer/domains. The dialog needs a user-owned Cloudflare API token (account-owned tokens cannot use the Registrar API) scoped Zone → Zone: Read, Zone → DNS: Read (DNS Edit to let OXYGEN fix records), and Account → Registrar: Domains: Edit; finish with Verify & connect. Until then the Domains page shows the connect card, not the domain table.

With Cloudflare connected, Buy domains buys through your own Registrar instead. In the app: Buy domains → Get quote → Approve purchase, or from the CLI:

oxygen domains check acme-mail.com try-acme.com   # free
oxygen domains buy acme-mail.com --json           # priced preview + quote_id
oxygen domains buy acme-mail.com --approved --quote <quote_id>

That purchase is non-refundable and bills your Cloudflare account. Registration can stay pending — oxygen domains registration-status acme-mail.com --wait polls to a terminal state.

Already own the domain? Add domain → Apex domain → Create zone, then set the printed nameservers at your registrar (CLI: oxygen domains add acme.com). Nothing works until the nameservers move.

Then write the mail records: plan is free, apply writes into your zone for 0 credits:

oxygen domains dns plan send-acme.com --provider google --json
oxygen domains dns apply send-acme.com --provider google --approved --plan <plan_hash>

A stale plan_hash is rejected — re-run plan. Both only work on Cloudflare zones; a managed domain's records are vendor-held, so read them with oxygen domains dns send-acme.com --json instead (--deep is ignored there).

Connect the mailboxes

Provisioning and native campaign sending use separate authorization contracts. For an InboxKit-managed Google order, OXYGEN completes its destination-bound OAuth authorization automatically and unattended after the domain approval lands; verify the exact mailbox with oxygen mailboxes oauth-health instead of opening a second customer sign-in by default. The vendor approval usually lands within minutes, but that is not an SLA: while oauth-health reports client_id_not_approved, no customer action is required and the worker keeps retrying. Zapmail-linked rows use the same operator rule when automatic_repair is non-null: queued or cooldown means OXYGEN owns the next retry, even if a lower-level compatibility verdict still says consent_required; do not ask the mailbox owner to sign in or run connect-oauth. Only rows with automatic_repair: null follow their explicit manual remedy. Only oxygen_native_send.state: connected proves the transport is authorized, and even that does not mean warm-up or the actual-sending campaign ramp is complete. BYOK/imported Google and ordinary Microsoft accounts sign in individually, in reviews of at most 10. In the app: Add inboxes → Connect existing (paste up to 500 exact addresses) or Import a file for a CSV/JSON/JSONL/XLSX export. A dedicated Microsoft sending tenant can instead use one Entra Global Administrator or Privileged Role Administrator authorization per resolved tenant, followed by read-only verification of only the exact addresses you submitted. Those built-in roles are required because tenant mode requests Microsoft Graph application permissions; an ordinary Application Administrator cannot grant them.

oxygen mailboxes import --file ./mailboxes.csv --validate-only --json   # local, no network
oxygen mailboxes import --file ./mailboxes.csv
oxygen mailboxes connect-oauth --provider google --vendor oxygen \
  --mailboxes [email protected] --json                                  # preview
oxygen mailboxes connect-oauth --provider google --vendor oxygen \
  --mailboxes [email protected] --approved

Verify with oxygen mailboxes oauth-health --json: it lists every Google/Microsoft inbox with no usable grant — a token on one mailbox proves nothing about another. The detailed existing-mailbox guide covers the Entra tenant and individual paths, secure file fields, and managed-mailbox fallback. Which origins support warm-up and monitoring at all: mailbox compatibility.

Which IP address connected mailboxes are used from, for sends, reply reads and token refreshes: oxygen egress status. That is the address your IT team allowlists in Microsoft Conditional Access or Google context-aware access; a workspace-only IP is described under dedicated sending IP.

Warm up before you send

A managed order with the warm-up add-on activates itself after provisioning — do not enable it a second time. A non-managed Microsoft mailbox first needs one separate OXYGEN Warm-up Outlook authorization for that exact mailbox, even when an Entra administrator authorized the selected tenant for native sending; there is no tenant-wide warm-up shortcut. A compatible Google import instead uses its mailbox-scoped app password. Then, for BYOK, imported, or opted-out inboxes:

oxygen mailboxes warmup enable --mailboxes [email protected] --json
oxygen mailboxes warmup enable --mailboxes [email protected] \
  --approved --plan <plan_hash> --max-credits <n>

Both --plan and --max-credits must echo the fresh preview. The JSON field plan_hash supplies the value for the CLI flag --plan. A pending enrollment hold is reserved credit, not a captured charge; its recovery preview reports existing and additional credits separately. In the app: the flame icon on a mailbox row (Enable OXYGEN Warm-up); the dialog quotes 4 weeks and confirms with Approve <n> credits/month, or Start warm-up when warm-up is included in the mailbox connection (--max-credits 0). Track it with oxygen mailboxes warmup status --json.

For read-only activity inspection, use oxygen mailboxes get <email> --json. The status refresh above persists state and may auto-pause a runaway mailbox. An enabled warm-up or a missing graph is not proof of sending or zero sending: see warm-up activity and history troubleshooting.

Campaign ramp and warm-up are separate. Campaign limits progress from actual days with accepted sends: 5/day with 0–2 completed UTC sending days, 10/day with 3–5, 20/day with 6–8, then the mailbox's configured cap from 9 completed sending days onward (25/day by default, maximum 50/day). Idle days since import do not advance this ramp. Historical accepted sends count. oxygen mailboxes get <email> --json exposes configured and effective limits and the reason for a lower cap; oxygen limits show reports technical limits.

Optional standard warm-up starts at 1/day, increases by 1/day and targets 10/day. Dedicated infrastructure retains its more restrictive domain-density profile. Warm-up settings remain configurable; OXYGEN Warm-up does not support the configured reply-rate percentage, which the panel and API report explicitly. Before launching, account for campaign plus known warm-up volume; Oxygen warns when that combined volume exceeds 50/day and cannot measure mail sent outside Oxygen. This combined-volume warning is separate from the 50/day maximum campaign cap per mailbox. These defaults do not guarantee inbox placement.

Click a mailbox address to open its panel — OXYGEN Warm-up / Settings / Campaigns — and set Daily campaign limit on Settings. The ramp override is CLI-only:

oxygen mailboxes cap set [email protected] --daily-cap 30
oxygen mailboxes ramp set [email protected] --start-per-day 5 --increase-every-days 3 --step 5 --cap 25
oxygen mailboxes ramp set [email protected] --clear

Check before launching

Check recipient addresses separately from your sending mailboxes: Email verification explains the free preview, catch-all handling, provider availability and the credit ceiling needed for a live check.

oxygen mailboxes health --json      # deliverability reputation per mailbox
oxygen mailboxes oauth-health --json
oxygen billing balance --json

If one inbox degrades, pull it from rotation without losing its warm-up: tick its checkbox and hit Pause in the selection bar, or oxygen mailboxes status [email protected] --status paused. Sequences pick from this pool at their Senders stage.

On this page