Skip to main content
A mailbox is an inbox Cargo owns and sends from — jane@acme-outreach.com, not a connected Gmail or Outlook account. Mailboxes live on a sending domain you register through Cargo. You send from them with the native sendEmail action, the same way a play calls allocate or a connector action. There is no HTTP “send” route. Delivery is a native action so workflows reuse orchestration’s pacing, retries, and credit charge. Direct delivery would skip all three.

How the pieces fit

  1. Register a domain with defineDomain (or buy one in the UI).
  2. Declare mailboxes on that domain with defineMailbox. Google, shared, or private inboxes. Warm-up starts on its own once the mailbox becomes active, so the daily send ceiling can climb from 5 to 40 over 45 days.
  3. Send from a play or tool with sendEmail({ mailboxUuid: jane.uuid, … }). Orchestration leaves a short gap between sends and waits rather than failing once the rolling 24-hour allowance is spent.
  4. Watch the thread — opens, clicks, replies, and unsubscribes land as events on the conversation.
See Sending for pacing, warm-up, and the sendEmail() helper, and Using the UI for the same flow in the app.

Register a sending domain

A mailbox can only be created on a domain the workspace already owns and that is active. defineDomain is the declarative resource for that:
domains/outreach.ts
Omit adopt to register a new domain through Cargo. Registration charges workspace credits and is not refundable — a + create domain:… line in cargo-ai project plan is the signal, and the deploy confirmation is where it’s approved.
dnsRecords is the set of records the deploy manages, not the whole zone. They are merged into the live zone, so the records the registrar wrote at purchase — and anything added in the Cargo UI — stay where they are, unless one of them can’t share a name with a declared CNAME, which DNS forbids. The deploy owns what it published: removing a record from the list deletes it on the next deploy, and cargo-ai project plan prints the per-record diff (+, ~, -) under the domain before anything is written. Leave dnsRecords off to manage only the domain’s lifecycle; [] manages no records, which deletes the ones earlier deploys published. A domain adopted into code owns nothing to begin with, so the first deploy adds its records alongside whatever the zone already carries and deletes none of it. Edit a managed record in code, not in the registrar’s UI. A record’s value is part of how the deploy recognises it, so an edit reads as the record it published being deleted and an unrelated one appearing — and publishing beside it would leave two SPF records at the apex, which breaks mail authentication. Neither value is the deploy’s to choose, so it stops:
Put the live value in the code to keep it, or drop the record from dnsRecords to hand it back to whoever edited it. Records that come from an app are published, not owned: the zone adds them and keeps them current, and never deletes them. An app’s hostnames are attached and detached where the app is, so retiring one is detaching it in the Cargo UI — the records it leaves behind are named in plan and removed there too. A freshly registered domain sits at pending while the registrar provisions it. The deploy waits until it is active before publishing DNS or creating mailboxes. The domain is addressed by its name, the way a member is addressed by email. If the workspace already owns that name and the spec does not say adopt: true, deploy fails with the way out (cargo-ai project import or adopt: true) rather than buying it a second time.

Declare a mailbox

defineMailbox provisions an inbox on that domain. Pass the domain handle — the deploy waits until the domain is active, then creates the mailbox and moves on. The mailbox itself may still be pending; it becomes active once the provider has issued credentials. A sendEmail before then fails with credentialsMissing.
mailboxes/jane.ts
That yields jane@acme-outreach.com. The slug is the project identity (mailbox:jane); username defaults to the slug. When two inboxes share a local part on different domains, pick a unique slug and set username explicitly:
jane.uuid is a deferred token. Pass it to sendEmail the same way you pass a capacity handle to allocate:
The workspace must have credits on file. Creating a mailbox charges a monthly fee in credits for as long as it exists (Google 125, shared and private 100). A send then charges 0.1 credits per delivered email. destroy deletes a mailbox the project created (and stops the fee); an adopted mailbox is released, never deleted. dailySendLimit is a brake on the ramp, never a bypass: declaring 40 on a mailbox created this morning still sends 5, because the effective ceiling is the lower of the two. 0 pauses outreach from the mailbox entirely — sends fail with mailboxSendingPaused rather than waiting. Like folder, the declared state wins — omit it and a deploy clears a cap somebody set in the UI, which cargo-ai project plan prints before it applies.
Provisioning is asynchronous. A new mailbox starts pending and becomes active once the provider has issued credentials. The deploy does not wait for that — unlike a domain registration, which must be active before DNS or mailboxes can be written. Only an active mailbox can send. Warm-up starts in the same status poll that marks it active, so you do not have to call start-warmup for a new inbox. Auth failure or a spam flag from the provider moves it to inactive and sending stops immediately. Domain, username, and type are create-only. Changing them is a destroy plus a new defineMailbox. If the workspace already owns that address and the spec does not say adopt: true, deploy fails with the way out rather than provisioning a second inbox. A mailbox created in the UI (or by an earlier deploy whose state file was lost) is bound the same way as a domain:

Organize with folders

Mailbox folders are a separate namespace from model or agent folders. Declare one with defineFolder and pass the handle:
folders/outreach.ts

What a mailbox is not

Cargo mailboxes are not a replacement for Resend, SendGrid, or Lemlist. Those remain connector actions against your ESP or sequencer. A Cargo mailbox is an inbox the workspace owns, warmed and paced so cold outreach from a new domain does not burn the domain’s reputation.

From the CLI

Prefer defineMailbox for the inbox itself. Use the CLI for pause/stop, allowance, threads, events, and suppressions — the same surface as the mailbox management API. start-warmup is the retry after a failed enrolment, or after stop-warmup.
Ad-hoc sends still go through orchestration, so they pick up the same rate limit and credit charge as a play: