Mailboxes

Connect mailboxes without handling passwords, buy domains and mailboxes or use domains you own, 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 Swarmhit.
  • Buy domains and mailboxes through Swarmhit, over the API or on a hosted link, or buy only the mailboxes, on a domain you already own. 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, testing or sending from 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 Swarmhit

Mailboxes can also be connected in Swarmhit, on the Accounts page (Email tab): 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 Swarmhit, 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).
  • senderId: the sender the mailbox belongs to, or null. PATCH /email-accounts/{id} with {"senderId": "..."} sets it, null clears it.
  • picture: the profile photo, when known. A bought mailbox's is set by you; a mailbox signed in with Google or Microsoft shows its account's photo.
curl https://app.swarmhit.com/api/v1/email-accounts \
  -H "Authorization: Bearer swh_live_..."

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. To put mailboxes on a domain you already own instead, connect it first.

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. After the purchase, the registry may email contact.email a link to confirm the address (ICANN requires it for .com, .net and .org, and .fr asks too). Open it within 14 days, or the domain is suspended (its website and mailboxes stop working) until the link is opened. An address confirmed once is not asked again.
  • warmup is on by default with the standard strategy; see Warmup.
  • To add mailboxes to a domain you already bought, or to one you own once it is ready, 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 one of your domains 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), domain-admin-cancelled, or domain-not-ready (a domain you own that is still connecting, or whose nameservers moved away). 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.
409domain-full, domain-admin-cancelled, domain-not-readyA domain you already have can't take these mailboxes now. Check its canAdd and reason with GET /managed-domains/{id}, then quote 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). To list the orders of every workspace in an organization, see Across an organization.

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.

Using a domain you own

Instead of buying a domain, the workspace can use one it already owns: POST /managed-domains with its name and the platform its mailboxes will live on (google or microsoft, one per domain). Nothing is charged for the domain, only for its mailboxes, and Swarmhit never renews or bills it: it stays registered where it is, and you renew it there. Adding one needs what buying needs: the managed mailboxes feature (403, code: feature-disabled), billing rights for the key's creator (403, code: not-allowed) and a card on file (402, code: no-payment-method).

curl -X POST https://app.swarmhit.com/api/v1/managed-domains \
  -H "Authorization: Bearer swh_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "name": "getacme.com", "platform": "google" }'

The answer is the domain, with source: "own" and a connection that says where it stands (trimmed below):

{
  "data": {
    "id": "dMn4Rk8Qw2Zp5Xs3L",
    "name": "getacme.com",
    "platform": "google",
    "source": "own",
    "slotsLeft": 5,
    "canAdd": false,
    "reason": "connecting",
    "connection": {
      "state": "nameservers",
      "stateLabel": "Waiting for nameservers",
      "nameservers": ["ns1.example.net", "ns2.example.net"],
      "currentNameservers": ["ns1.registrar.example", "ns2.registrar.example"],
      "websiteRecords": [{ "type": "A", "host": "@", "value": "203.0.113.10" }],
      "websiteRecordsCopied": true,
      "dnssec": false,
      "expiresAt": "2026-11-12T10:00:00.000Z"
    }
  }
}
  1. Set the nameservers. At the domain's registrar, replace connection.currentNameservers with the two connection.nameservers. They can be empty for a minute right after adding: read the domain again. When connection.dnssec is true, turn DNSSEC off at the registrar too, or the new nameservers are never seen.
  2. Wait for it to connect. connection.state moves to setting_up once the new nameservers are seen (its email records are being written, usually for a few minutes), then to ready. Swarmhit checks on its own; right after changing the nameservers, POST /managed-domains/{id}/check looks again within a minute. GET /managed-domains/{id} returns where it stands, and managed_domain.ready fires when it is ready.
  3. Add mailboxes. Once canAdd is true, quote them as on a bought domain, with { "name": "getacme.com", "existing": true, "mailboxes": [...] }, then buy. Only the mailboxes are charged, and they are usually ready within minutes.

The domain's DNS moves to Swarmhit. Its website records (A or AAAA at the root, and www) are copied to its new DNS (connection.websiteRecords; websiteRecordsCopied is true once they are in), so its website keeps working. Nothing else is copied: subdomains, other records and any email the domain has today stop working once its nameservers change.

  • Set the nameservers within about 12 hours. A pair not seen by then is replaced by a new one: connection.nameserversChangedAt is set and managed_domain.action_required fires with reason: nameservers_changed. Set the new pair instead.
  • Nameservers never changed within 7 days (connection.expiresAt): the domain becomes expired, and managed_domain.action_required fires with reason: expired. The same call adds it again. A name whose nameservers were never seen can be added again twice; after that, contact support.
  • failed means it is delayed and our team is on it.

Once it is ready:

  • Its nameservers must keep pointing to Swarmhit. If they are changed, connection.movedAt is set, canAdd turns false with reason: moved, and managed_domain.action_required fires with reason: nameservers_moved. Set them back, or its mailboxes may stop working.
  • Its registration stays with you. connection.registrarExpiresAt says when it lapses, when the registry tells us, and managed_domain.action_required with reason: registration_expiring warns as that date nears: renew it at the registrar.

Adding is refused with:

  • 400, code: invalid-domain;
  • 409, code: domain-exists, when it is already one of the workspace's domains;
  • 409, code: domain-taken, when it is, or was, live in another account (contact support if it is yours);
  • 409, code: too-many-domains, when 5 domains are already connecting;
  • 422, code: domain-refused, when it cannot be connected, for example because it is not a registered domain;
  • 429, code: rate-limited, after 5 adds in a minute.

DELETE /managed-domains/{id} lets a domain you own go, once no mailbox runs there or is on its way (409, code: domain-in-use: cancel them first). Its DNS at Swarmhit is deleted, so the domain stops resolving until you set its nameservers back at the registrar (connection.currentNameservers holds the ones it had). Removing needs billing rights, and a domain bought here returns 409 with code: not-own-domain.

In Swarmhit, Use a domain I own under Settings, Domains and in the Buy mailboxes wizard walks through the same steps. On the MCP server, add_own_domain, get_managed_domain, check_own_domain and remove_own_domain do the same.

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, and neither does sendEmail (409, code: mailbox_warming). 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 or owns (source: purchased or own), and GET /managed-domains/{id} returns one. 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). When one can't, reason says why:

  • full, admin-cancelled, not-active or removed;
  • connecting, for a domain you own that is not ready yet, or a bought one still being set up;
  • moved, for a domain you own whose nameservers no longer point to Swarmhit.

adminEmail is the domain's admin mailbox, and takenEmails the addresses a new mailbox cannot reuse. A domain you own also carries its connection.

Bought domains renew every year; domains you own are never renewed or billed here. 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.

Across an organization

By default, nothing changes: an organization key acting as the organization lists the organization's own orders and domains, and a key acting as one workspace (X-Swarmhit-Workspace), or a workspace's own key, lists that workspace's. Two optional query parameters widen or pick the list, only when you send them:

  • orgWide=true also lists every workspace inside the organization, for an organization key acting as the organization. Any other key keeps its one workspace.
  • workspaceId lists one workspace of that scope: the organization itself or one of its workspaces. Any other id is a 404 (code: not-found), so a workspace never learns whether another one exists.

With either one, each order and domain also carries workspaceId and workspaceName; without them, the answer has exactly the fields described above. GET /mailbox-orders and GET /managed-domains take both, and GET /mailbox-orders/{id} and GET /managed-domains/{id} take orgWide=true to find an order or a domain of any workspace in the organization. Acting on a workspace's domain (checking or removing it) takes X-Swarmhit-Workspace naming that workspace. Webhook payloads do not change.

curl "https://app.swarmhit.com/api/v1/mailbox-orders?orgWide=true" \
  -H "Authorization: Bearer $SWARMHIT_KEY"

On the MCP server, list_mailbox_orders, get_mailbox_order and list_managed_domains take the same orgWide and workspaceId arguments, for an organization key with no workspaceId on the connection, and get_managed_domain takes orgWide.

A bought mailbox's picture

POST /email-accounts/{id}/picture sets a bought mailbox's profile photo from { "url": "https://..." }, a public JPG or PNG (square works best). Gmail shows it next to the mailbox's emails, which can take up to a day. A mailbox you connected yourself keeps its own account photo (409, code: not-managed). In Swarmhit, the mailbox settings take an upload, and copy it to your other bought mailboxes.

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