# Deliverability

Source: https://agentboxd.com/docs/deliverability

> How to keep your agents’ mail out of spam: warm-up, SPF/DKIM/DMARC alignment, content, our sending limits and why they exist, and the deliverability API.

Whether a message lands in the inbox is decided by the receiving provider, per message, from the sender’s reputation, authentication, the content and how recipients react. We handle the infrastructure (our own mail server, DKIM signing, reverse DNS, blocklist monitoring, bounce handling). What your agents send, to whom and how often is up to you, and it matters more.

## Inbox, Promotions, spam

- **Spam** means the provider doesn’t trust the sender or the message: failed authentication, a poor reputation, many complaints or bounces, or content that looks like fraud.
- **Promotions** (and similar tabs) is not spam. It is where a provider files mail that looks like marketing: heavy HTML layouts, many images and links, tracking, discount language, sent to many people at once. It is a sorting decision, and you can’t switch it off.
- **Inbox** is what a personal, one-to-one message gets: plain wording, few links, a reply to something the recipient wrote, from an address they have exchanged mail with before.

Agents that write one-to-one mail (support replies, scheduling, sign-ups, follow-ups) should look like a person writing: short plain text, or light HTML with a text part, one or two links, no tracking. That is also what we send: every message has a `text/plain` part, and we add no footer, tracking pixel or link rewriting.

## Authentication and alignment

Receivers check three things: SPF (is this server allowed to send for the envelope domain?), DKIM (is the message signed by a key published in DNS?) and DMARC (does one of them pass for the domain in the visible From address, and what should happen if not?). DMARC needs **alignment**: the domain that passes SPF or DKIM must match the From domain (or share its organizational domain).

- **On our shared domain** (`homingbox.net`, or `agents.agentboxd.com` for inboxes created before the move) everything is set up for you: SPF, DKIM with our key, and DMARC all align.
- **On your own domain**, [connect it](https://agentboxd.com/docs/custom-domains) and publish the records we show. We sign with your domain’s own DKIM key, so DKIM aligns with your From address, and the SPF `include:homingbox.net` covers our server. Start DMARC at `p=none`, read the reports for a few weeks, then move to `p=quarantine` and `p=reject`.
- **Use a subdomain** such as `mail.yourcompany.com` for agents, so their reputation is separate from your people’s mail on the main domain.
- **Rotate the DKIM key yearly** ([how](https://agentboxd.com/docs/custom-domains#rotating-the-dkim-key)); the domain page reminds you.

## Warm-up

Providers are suspicious of a new domain or address that suddenly sends a lot. Build volume over a few weeks:

- Week 1: tens of messages a day, to people who expect them (colleagues, test accounts, customers who wrote first).
- Then roughly double the daily volume every few days while bounces stay under 2 % and complaints near zero.
- Prefer replies over cold mail. A conversation the recipient started is the strongest positive signal there is.
- Keep volume steady. Spikes (a whole list in one hour) look like a compromised account.

## Content and recipients

- Only write to people who expect it. Never buy or scrape lists; a hard bounce or a spam complaint costs far more than one message gains.
- Stop at the first bounce. An address that hard-bounces is suppressed for your workspace (sends to it answer `422 recipient_suppressed`), and a complaint suppresses the address too. Suppressions are per workspace: another customer’s bounce or complaint never stops you from writing to an address, and yours never stops them. If a suppressed address works again, you can remove it yourself (see [Your suppression list](https://agentboxd.com/docs/deliverability#your-suppression-list)).
- Honour "stop" and unsubscribe requests at once, and make them easy: a line such as “reply STOP and we won’t write again” works for one-to-one mail.
- Write a real subject and a greeting, keep links few and to your own domain, avoid URL shorteners and attachments nobody asked for.
- Give the inbox a clear display name (“Acme Support”) and keep the same address for the same purpose.
- Watch for replies: an agent that answers out-of-office messages or other agents can loop. Check `Auto-Submitted` headers and the `ai:auto-reply` label, and use [pause](https://agentboxd.com/docs/api#inboxes) as a kill switch.

## Our limits and why

Every workspace shares our sending IP and, unless it uses its own domain, our sending domain. One workspace’s spam would land everybody else’s mail in spam, so we enforce limits before providers do.

| Limit | Value | Why |
| --- | --- | --- |
| Sends per day | From your plan (Free 100, Builder 2,000, Team 10,000); a new workspace sends up to 20 a day (10 per 5 minutes) for its first 3 days, on any plan including promo codes; `429 org_daily_limit_exceeded` | Caps the damage a runaway or compromised agent can do in a day, and keeps a brand-new account from harming the shared sending reputation before it has any history. |
| Sends per 5 minutes | From your plan (Free 20, Builder 200, Team 1,000); `429 rate_limited` with `Retry-After` | Stops a loop within minutes, long before the daily cap. |
| Recipients per message | 50 (to + cc + bcc) | One-to-one and small-group mail, not bulk campaigns. |
| Hard-bounce rate | Sending pauses above 5 % over 7 days (at least 20 sends); the owners are warned above 3 %. A bounce counts when the receiving server refuses the message while we deliver it. These bounces are recorded on the message but don’t count: a bounce email that arrives later, a bounce on a reply to addresses that mail you received gave (its From, Reply-To, To or Cc), and an address on a domain that doesn’t exist or takes no mail, or an unknown address on our own domains. `GET /v1/deliverability` counts them the same way. | Around the level at which sending services put an account under review. |
| Complaint rate | Sending pauses above 0.1 % over 7 days (at least 20 sends); the owners are warned above 0.06 %, and at least 3 complaints at three times the limit suspend the workspace. A complaint that a mail provider reports to us by email is recorded on the message and suppresses the address; it doesn’t count, and a person reviews a workspace that would be over the limit with it. | The threshold large mailbox providers publish for bulk senders. |
| Suppression list | Hard bounces and complaints, for the workspace that sent the mail | Mailing a dead or unwilling address again is the fastest way to lose reputation. |

Over a limit, sending pauses (`403 sending_paused`): mail keeps arriving, reading and the API keep working, and queued mail waits. Clean up the recipient list, then press **Request review** on the dashboard banner; a person reads it and resumes sending. A second pause within 30 days, or clear abuse, suspends the workspace instead (`403 org_suspended`, queued mail is not sent). [Sending health](https://agentboxd.com/docs/send-refusals#sending-health) has every step, and [Why was my send refused?](https://agentboxd.com/docs/send-refusals) every error.

## Your suppression list

The dashboard’s **Deliverability** page lists your workspace’s suppressed addresses with the reason and the date, and lets you remove one. The API does the same: `GET /v1/suppressions` lists them (permission `contacts:read`) and `DELETE /v1/suppressions/:address` removes one (`contacts:write`). Both need a workspace-wide key.

- Remove an address only when you know it works again, for example a mailbox that was full or a person who asked you to write. Sending again to an address that bounced or complained hurts your deliverability and can get your sending paused.
- So removing a bounce or a complaint asks you to confirm: in the dashboard, and with `?confirm=true` in the API (`409 confirmation_required` without it).
- Every removal is recorded as a `suppression.removed` event with who removed which address. If the address bounces again, it goes back on the list.
- You only ever see and remove your own workspace’s entries. Sign-in links and other mail we send ourselves keep their own list.

## The deliverability API

`GET /v1/deliverability` (permission `metrics:read`, workspace keys only) summarises what affects your mail: your bounce and complaint rates over 7 and 30 days next to the thresholds at which sending pauses, how many of your contacts are suppressed, the SPF, DKIM and DMARC status of your domains, and whether our shared sending IP is on a blocklist.

curl:

```bash
curl https://api.agentboxd.com/v1/deliverability \
  -H "Authorization: Bearer $AGENTBOXD_API_KEY"
```

response:

```json
{
  "rates": {
    "last_7_days":  { "sent": 412, "bounced": 3, "complained": 0, "bounce_rate": 0.0073, "complaint_rate": 0 },
    "last_30_days": { "sent": 1630, "bounced": 11, "complained": 1, "bounce_rate": 0.0067, "complaint_rate": 0.0006 }
  },
  "thresholds": { "bounce_rate": 0.05, "complaint_rate": 0.001, "min_sent": 20 },
  "suppressed_contacts": 9,
  "domains": [
    { "id": "…", "domain": "mail.acme.com", "status": "verified", "spf": "ok", "dkim": "ok", "dmarc": "ok", "dkim_rotation_due": false }
  ],
  "shared": {
    "sending_domain": "homingbox.net",
    "ip_reputation": {
      "status": "clean",
      "checked_at": "2026-09-25T09:00:00.000Z",
      "lists": [
        { "name": "Spamhaus ZEN", "status": "clean" },
        { "name": "Barracuda", "status": "clean" },
        { "name": "SpamCop", "status": "clean" },
        { "name": "UCEPROTECT L1", "status": "clean" }
      ]
    }
  }
}
```

| Field | Meaning |
| --- | --- |
| `rates` | Outbound messages that left us (`sent`, delivered, bounced or complained) and the bounce and complaint rates, over the last 7 and 30 days. |
| `suppressed_contacts` | Your contacts whose address is on the suppression list (hard bounce or complaint). Sends to them are refused. |
| `domains` | Your custom domains with the last check of their SPF, DKIM and DMARC records (`ok`, `missing`, `mismatch`). |
| `shared.ip_reputation.status` | `clean`, `listed`, `partial` (some lists could not be checked), `unknown`, `stale` (no check in 3 hours) or `not_checked`. |
| `shared.ip_reputation.lists` | Per list: `clean`, `listed`, `unknown` (the list refused to answer or timed out; that says nothing about our IP) or `skipped`. |

We check our IP once an hour on Spamhaus ZEN, Barracuda, SpamCop and UCEPROTECT Level 1, and we are alerted when it is listed or when bounce or complaint rates across all workspaces rise. The TypeScript SDK has `mr.deliverability()`, Python `client.deliverability()`, and MCP clients `get_deliverability`. For the suppression list: `mr.suppressions.list()` / `mr.suppressions.remove(address, { confirm: true })`, `client.suppressions.list()` / `client.suppressions.remove(address, confirm=True)`, and the MCP tool `list_suppressions` (removing stays a person’s or your code’s decision).
