Mailboxes

Connect mailboxes without handling passwords, buy domains and mailboxes, warm them up, and manage them.

A mailbox is the email counterpart of a sender: the address a campaign's email steps send from, and where replies land. There are two ways to get one:

  • Connect a mailbox someone already has, through a hosted connect link or in the app.
  • Buy domains and mailboxes through Swarmhit, over the API or on a hosted link. Bought mailboxes arrive connected, with no sign-in, and warm up before campaigns use them.

Mailboxes take no seat: a seat covers one LinkedIn sender, and a workspace on a paid plan connects as many mailboxes as it needs. A workspace inside an organization draws its plan from the organization. The email channel has to be enabled for the workspace: until it is, reads still answer, but creating a hosted link and pausing, resuming, removing, re-checking or testing a mailbox return 403. Credentials are never returned by any endpoint.

With an organization key, set X-Swarmhit-Workspace to choose the workspace a mailbox lands in or an order is placed for (see Organization).

POST /email-accounts/hosted-link is how the API connects a mailbox without ever handling its password. It works like the LinkedIn hosted link: you mint a one-time URL, send the person to it, and they connect on a page Swarmhit serves in your organization's name and logo. providers says what the page offers; the person picks one when there are several.

providers valueWhat the page offers
googleSign in with Google (Gmail and Google Workspace).
google_app_passwordA Google address and an app password.
microsoftSign in with Microsoft (Outlook.com, Hotmail and Microsoft 365).
imapAny other mailbox: its address, password, and IMAP and SMTP servers. The page fills in the servers it can find for the address.
buy_mailboxesBuying new domains and mailboxes on the page. Needs purchase, see below.
curl -X POST https://app.swarmhit.com/api/v1/email-accounts/hosted-link \
  -H "Authorization: Bearer swh_live_..." \
  -H "X-Swarmhit-Workspace: wSp042" \
  -H "Content-Type: application/json" \
  -d '{
    "providers": ["google", "microsoft", "imap"],
    "redirectUrl": "https://yourapp.com/settings/email/done",
    "externalId": "your-user-42"
  }'
{
  "data": {
    "id": "hLk789",
    "status": "pending",
    "channel": "email",
    "provider": null,
    "providers": ["google", "microsoft", "imap"],
    "workspaceId": "wSp042",
    "accountId": null,
    "orderId": null,
    "purchase": null,
    "externalId": "your-user-42",
    "redirectUrl": "https://yourapp.com/settings/email/done",
    "url": "https://app.swarmhit.com/connect/Qm9Kx...",
    "expiresAt": "2026-10-08T10:30:00.000Z"
  }
}

provider is the one-value form of providers; send one or the other, not both. The rest works as on LinkedIn links: url is returned once, here; the link expires after 30 minutes by default (expiresIn, in seconds, between 300 and 604800); and redirectUrl may be left out when your organization has a default return URL (400 with code: invalid-redirect-url when there is neither).

The link is checked when you create it, so problems surface here rather than at the end of the person's visit:

  • 400 with code: no-plan when the workspace has no paid plan, code: invalid-provider for an unknown or missing provider.
  • 403 when the email channel is not enabled for the workspace.
  • 409 with code: provider-unavailable when a provider the link names is not available yet.

Buying on the page

A link offering buy_mailboxes lets the person buy new domains and mailboxes themselves. They search for domains, choose Google Workspace or Microsoft 365 for each, and name the mailboxes. The page shows no prices: you pay.

  • purchase.maxMailboxes (1 to 100) caps how many mailboxes the person can buy through the link. purchase is required with buy_mailboxes and refused without it (400, code: invalid-purchase).
  • purchase.contact sets the registrant the domains are registered to, with the same fields as an order's contact. Set it and the page does not ask the person for it.
  • Domains bought on the page are registered for one year, and their mailboxes warm up with the standard strategy.
  • The order is charged to the card on file of the workspace that pays: the organization, for a workspace inside one. It is placed on behalf of the API key's creator, who needs billing rights there.

These are checked when the link is created: 403 with code: buying-off when buying mailboxes is not enabled for the workspace, 403 with code: not-allowed when the key's creator has no billing rights, and 409 with code: no-payment-method when there is no card on file.

Once the person buys, the link is done: its status becomes ordered and orderId names the order. Follow the order like any other (below); mailbox_order.completed and each new mailbox's account.connected carry the link's hostedLinkId and externalId.

Pass accountId and the link repairs that mailbox instead of adding one: it keeps its id, its campaigns and its settings. A reconnect link offers only the mailbox's own provider, so providers can be left out (any other value is a 400 with code: invalid-provider). A mailbox that signs in with Google or Microsoft must sign in again as the same address; a password mailbox keeps its address and servers and takes the new password.

Bought mailboxes never need reconnecting, and a link for one is refused with 409 and code: managed-mailbox. An accountId that is not a mailbox of the workspace returns 404.

Knowing how it went

  • The redirect. The person comes back to redirectUrl with ?hosted_link_id=…&status=connected|ordered|failed|expired|cancelled&account_id=…&order_id=…&external_id=…. account_id is the mailbox id; order_id is set after a purchase.
  • Webhooks. account.connected (with data.channel: "email") and account.connection_failed carry hostedLinkId and externalId.
  • Polling. GET /email-accounts/hosted-link/{id} returns the link's status (pending, connected, ordered, failed or expired), the accountId or orderId it produced, and an error when it failed.

A failed attempt, such as a wrong password, fires account.connection_failed and leaves the link open so the person can try again, a limited number of times. Only cancelling on the page (the link becomes failed with error.code: cancelled) or expiry ends it without a mailbox.

Connecting in the app

Mailboxes can also be connected in the app, under Accounts → Email: Google (sign-in or an app password), Outlook and Microsoft 365 (sign-in), or any other provider over IMAP/SMTP. They then show up in the API like any other.

Managing mailboxes

GET /email-accounts lists the workspace's mailboxes, newest first, and GET /email-accounts/{id} returns one. Each carries:

  • status: pending while it connects, active, error when its sign-in broke (reconnect it through a link), suspended, or disconnected once it is removed.
  • provider (google, microsoft or imap), authType (oauth for a mailbox signed in with its provider, password otherwise) and connectedVia (app, hosted_link, or managed for a bought mailbox).
  • The pause state, daily limits and today's usage, the tracking settings trackOpens and trackClicks, and lifetime counters (sent, bounced, complained, opened, clicked). Tracking is set in the app, in the mailbox settings.
  • dnsHealth: the sending domain's SPF, DKIM and DMARC records, each ok, missing, invalid, weak or unknown, re-read daily.
  • managed and warmup on a bought mailbox (both null on a mailbox you connected).
curl https://app.swarmhit.com/api/v1/email-accounts \
  -H "Authorization: Bearer swh_live_..."
  • POST /email-accounts/{id}/pause and .../resume work like the sender pair: campaigns stop emailing from a paused mailbox, its leads stay assigned, and both emit account.paused / account.resumed with channel: "email".
  • DELETE /email-accounts/{id} removes the mailbox and its threads, takes it out of every campaign pool and moves the leads it was emailing to the remaining mailboxes. Connecting the same address again within the restore window (15 days by default) brings back its campaigns, leads and settings, under a new id with restoredFrom set. A bought mailbox is never removed this way (409, code: managed-mailbox): cancel it instead.
  • POST /email-accounts/{id}/check-dns re-reads the domain's records now, after you change DNS. See Email deliverability for what each record does.
  • POST /email-accounts/{id}/deliverability-test sends one email to our test inbox; the mailbox's deliverabilityTest then reports where it landed and what the receiving server saw.
  • GET /email-accounts/{id}/stats reports emails sent, replies and bounces over 7 to 90 days (days, default 30), plus opens and clicks for tracked emails, with the per-campaign split.

A lead whose address bounced hard or reported spam carries emailStatus (bounced / complained) and stops at any email step it reaches from then on; see Email deliverability.

Buying domains and mailboxes

Swarmhit registers the domains, creates Google Workspace or Microsoft 365 mailboxes on them with their DNS records, and connects them to the workspace: nobody signs in, and they never need reconnecting. Buying needs the managed mailboxes feature on the workspace, and the API key's creator needs billing rights in the workspace that pays. Reading orders, domains and billing never needs the feature.

1. Find domains

GET /domains/search takes a keyword (a word, or one exact domain) and optional tlds (comma separated, e.g. .com,.org) and years:

curl "https://app.swarmhit.com/api/v1/domains/search?keyword=getacme&tlds=.com,.io" \
  -H "Authorization: Bearer swh_live_..."

Each result says whether it is available, whether Google Workspace and Microsoft 365 can host it (platforms), and the registrar's first-year price (priceCents). offered: false means it cannot be bought here: an ending we do not sell, or a first year above our price cap. The quote has the prices you actually pay. A key may search 15 times a minute (429, code: rate-limited).

2. Get a quote

POST /mailbox-orders/quote prices an order and charges nothing:

curl -X POST https://app.swarmhit.com/api/v1/mailbox-orders/quote \
  -H "Authorization: Bearer swh_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "domains": [{
      "name": "getacme.com",
      "platform": "google",
      "years": 1,
      "redirectUrl": "https://acme.com",
      "mailboxes": [
        { "username": "jane", "firstName": "Jane", "lastName": "Doe" },
        { "username": "jane.doe", "firstName": "Jane", "lastName": "Doe" }
      ]
    }],
    "contact": {
      "firstName": "Jane", "lastName": "Doe", "email": "jane@acme.com", "phone": "+14155550142",
      "addressLine1": "1 Market St", "city": "San Francisco", "state": "CA", "country": "US", "postalCode": "94105"
    },
    "warmup": { "enabled": true, "strategy": "standard" }
  }'
  • An order holds 1 to 20 domains, and each domain 1 to 5 mailboxes. A new domain needs its platform (google or microsoft) and a redirectUrl: your own https:// website, where visitors of the domain are sent. years is 1 to 10 (default 1). Each mailbox needs a username (the part before @), firstName and lastName, and may take a pictureUrl (https://).
  • contact is the registrant of the new domains. It is required when the order registers a domain, and never returned by the API.
  • warmup is on by default with the standard strategy; see Warmup.
  • To add mailboxes to a domain you already bought, send { "name": "getacme.com", "existing": true, "mailboxes": [...] }: no domain is charged, and they are usually ready within minutes. GET /managed-domains says how many each domain can still take.

The quote returns a quoteId valid for 30 minutes, the lines charged, dueNowCents (the domains and the first month, charged when you buy) and monthlyCents (charged every month after, while the mailboxes run). Domains that can no longer be registered are listed in unavailable, and addresses already taken on a domain you own in unavailableMailboxes: remove or rename them and quote again. A workspace may quote 6 times a minute (429, code: rate-limited).

Validation errors are a 400 with code: invalid-order and details.errors ([{ path, message }]). For existing domains: 404 with code: domain-not-owned, and 409 with code: domain-full (details.slotsLeft) or domain-admin-cancelled. 403 with code: feature-disabled when buying is not enabled for the workspace, or not-allowed without billing rights. 503 with code: quote-unavailable when prices cannot be read right now: retry.

3. Buy

POST /mailbox-orders with the quoteId charges dueNowCents to the card on file of the paying workspace (the organization, for a workspace inside one) and starts the order:

curl -X POST https://app.swarmhit.com/api/v1/mailbox-orders \
  -H "Authorization: Bearer swh_live_..." \
  -H "Idempotency-Key: 6c1e0f1a-buy-getacme" \
  -H "Content-Type: application/json" \
  -d '{ "quoteId": "qTe901" }'

Buying the same quote twice returns the same order without a second charge. Order fields are optional here; when sent, they must be exactly what was quoted (409, code: quote-mismatch).

StatuscodeWhat to do
402no-payment-method, payment-failedAdd or fix the card in Billing, then buy again.
409quote-expired, price-changed, domain-unavailable, mailbox-unavailableQuote again.
409payment-pendingThe charge is still being confirmed: do not buy again. The order shows up in GET /mailbox-orders within minutes if it went through.
409purchase-in-progressAnother call is buying this quote right now.
503temporarily-unavailableRetry later.

Following an order

GET /mailbox-orders/{id} returns the order with every domain and mailbox; GET /mailbox-orders lists the workspace's orders, newest first (optional status).

An order moves paid → placing → placed → provisioning → authorizing → connecting → completed, usually within a few hours. needs_attention means it is delayed and our team has been alerted: they resume it, or cancel it. cancelled means it was stopped and refunded. delayed: true flags an order that is held up, including one still running while a mailbox is stuck in setup. Treat a status you do not recognise as in progress.

Each mailbox has a step (ordered, provisioning, authorizing, connecting, connected, failed or refused) and, once connected, its accountId. A domain that could not be registered is refused (with refusedReason) and is refunded, along with its mailboxes.

You do not have to poll: mailbox_order.completed fires when the order is done and mailbox_order.failed when it needs attention or is cancelled. Each mailbox emits account.connected as it connects (data.channel: "email", and connectedVia: "managed" on the mailbox). See Webhooks.

Warmup

A new mailbox on a new domain has no sending reputation. Warmup builds one: the mailbox exchanges a growing number of emails a day with a warmup network that opens them, replies to them and rescues them from spam. Warmup is for bought mailboxes; it is on by default for every order, and its endpoints return 409 with code: not-managed for a mailbox you connected yourself.

The hold

Campaigns do not send from a warming mailbox until it is ready. The mailbox's warmup object says why it is held:

  • holdReason: "warming" while warmup starts and for the first 14 days after it does (until holdUntil).
  • holdReason: "health" after that, for as long as its warmup health is warning or critical.

When the hold lifts, warmup.ready fires and campaigns that include the mailbox start assigning leads to it right away. To send before warmup is done, POST /email-accounts/{id}/warmup/hold-override with { "on": true }; { "on": false } holds it again. A paused or stopped warmup never holds a mailbox.

Health

GET /email-accounts/{id}/warmup returns the warmup status (pending while it starts, active, paused, cancelled, past_due or error), the warmup day, the current and target daily volume, and its health (excellent, good, warning, critical, or unknown until there is enough data), with healthScore and inboxRate. GET .../warmup/stats returns the daily series (days, 1 to 365, default 30).

If health turns critical after the hold, Swarmhit pauses the mailbox for campaigns (pausedReason: "warmup_health") while warmup carries on, and resumes it on its own once health is back to good or excellent. warmup.health_changed and warmup.status_changed report each change.

Strategy

A strategy sets the daily volume (start, increase per day, target) and the share of warmup emails the network replies to, rescues from promotions and rescues from spam. Pick a preset, or send your own values:

PresetStartIncrease per dayTarget
conservative5230
standard (default)10340
aggressive15560
curl -X PATCH https://app.swarmhit.com/api/v1/email-accounts/eMa123/warmup \
  -H "Authorization: Bearer swh_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "strategy": { "preset": "standard", "targetVolume": 35 } }'

PATCH /email-accounts/{id}/warmup takes a preset name or an object: a preset fills the fields you leave out, and without one every field is required. startingVolume and targetVolume are 1 to 500 and dailyIncrease 1 to 50; the target can be at most limits.maxTarget (in every warmup endpoint's response), what the mailbox's daily email limit leaves for warmup. The starting volume only applies on the first day and cannot change after it (409, code: starting-volume-locked). Invalid values are a 400 with code: invalid-strategy and details.errors. A strategy changed before warmup starts is applied as soon as it does.

Pausing, stopping and restarting

  • .../warmup/pause and .../warmup/resume pause and resume a running warmup. A warmup paused because a payment failed resumes only once it is paid (409, code: warmup-billing-paused).
  • .../warmup/stop ends warmup for good; campaigns may use the mailbox right away.
  • .../warmup/start starts warmup again on a bought mailbox without one, once a month at most (429, code: warmup-restart-limit). A mailbox that already finished its hold is not held again.

Starting and stopping warmup need billing rights.

Managed domains

GET /managed-domains lists the domains the workspace bought. Each holds up to 5 mailboxes, and each entry says how many run on it (live), how many are on their way (pending), how many more it can take (slotsLeft), and whether a mailbox can be added (canAdd, with a reason when not: full, admin-cancelled, not-active or removed). adminEmail is the domain's admin mailbox, and takenEmails the addresses a new mailbox cannot reuse.

Domains renew every year. The renewal is charged to the card about 30 days before the domain expires; a domain with no running mailbox is not renewed and lapses. A renewal that cannot be charged fires mailbox_billing.payment_failed (with a payUrl); a domain that lapses unpaid takes its mailboxes with it.

Cancelling a bought mailbox

POST /email-accounts/{id}/cancel-managed cancels a bought mailbox: it keeps working until its renewal date (cancelAt), then it is removed and no longer billed. Its warmup stops at once. Cancelling needs billing rights (403, code: not-allowed), and a mailbox you connected yourself returns 409 with code: not-managed.

Each domain has one admin mailbox (managed.isAdmin on the mailbox, adminEmail on the domain). Once it is cancelled, no mailbox can ever be added to that domain again, so while other mailboxes still run there, cancelling it needs { "confirmAdmin": true } (409, code: admin-mailbox otherwise).

Billing

Bought mailboxes and domains are billed apart from the plan, to the card of the workspace that pays (the organization, for a workspace inside one), including purchases made on hosted links. Each purchase is charged when it is bought (the domains and the first month), then monthly from that date; each domain renews yearly.

GET /mailbox-billing returns the monthly total and next charge date, the card charged, each purchase with its status, a payUrl when a payment failed and the last receipt, and each domain's renewal. It needs billing rights.

When a monthly payment fails, mailbox_billing.payment_failed fires with a payUrl. A day later the purchase's warmup pauses, and after 14 days unpaid its mailboxes are cancelled. mailbox_billing.payment_recovered fires once it is paid.

Reference

On this page