DocsAgent inboxes

Developers

.md

Agent inboxes

Updated 16 Oct 2026

An agent inbox is an inbox of its own for an AI agent, on your verified domain, next to your team’s mailboxes. The agent reads and sends through the API or MCP, and people see its mail in the app like any other inbox. Every agent has a sponsor, a named person who answers for it, and the guardrails are on from the start.

  • A person approves new recipients. Replies, and email to people the agent already corresponds with, go out right away. An email to anyone new waits for a person.
  • Spoofed mail is held back. Mail whose sender fails authentication doesn’t reach the agent until a person releases it.
  • It reads what a person would see. Hidden text is removed from every message, and each one says how far its sender can be trusted.
  • It says it’s an AI. Every email it sends ends with a line saying an AI agent sent it, and for whom.
  • It pauses on its own. Complaints or bounces on its mail pause that agent alone, long before they could add up to a pause for your whole workspace.

Create one

Agent inboxes live on a verified domain, so set up your domain first. Creating one is for workspace owners and admins.

In the app

Open Settings → Agents and create the agent: its name (it signs its email with it), its address, its sponsor, how freely it may send and what it may read. You can create its key in the same step. The key is shown once.

With the API

Use an owner’s or admin’s API key with the Manage mail scope. Only name is required; everything else has a safe default.

POST/api/v1/inboxesManage mail
curl
curl -X POST "https://supermailos.com/api/v1/inboxes" \
  -H "Authorization: Bearer $SUPERMAILOS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Billing assistant","localPart":"billing","sendMode":"known","inboundMode":"verified","createKey":{"scopes":["read","write","send"],"expiresInDays":90}}'
TypeScript
const res = await fetch("https://supermailos.com/api/v1/inboxes", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.SUPERMAILOS_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "name": "Billing assistant",
    "localPart": "billing",
    "sendMode": "known",
    "inboundMode": "verified",
    "createKey": {
      "scopes": [
        "read",
        "write",
        "send"
      ],
      "expiresInDays": 90
    }
  }),
});
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error}`);
const data = await res.json();
Python
import os
import requests

res = requests.post(
    "https://supermailos.com/api/v1/inboxes",
    headers={
        "Authorization": f"Bearer {os.environ['SUPERMAILOS_API_KEY']}",
    },
    json={
        "name": "Billing assistant",
        "localPart": "billing",
        "sendMode": "known",
        "inboundMode": "verified",
        "createKey": {
            "scopes": ["read", "write", "send"],
            "expiresInDays": 90,
        },
    },
    timeout=30,
)
res.raise_for_status()
data = res.json()

Request body (JSON)

NameTypeDescription
namerequiredstringWhat the agent is called, such as Billing assistant. It signs its email with this name.
localPartstringThe part of its address before the @. Made from the name when you leave it out.
domainstringOne of your verified domains, by name or id. Your first verified domain when you leave it out.
sponsorUserIdstringThe person who answers for the agent: approvals and alerts go to them. When you leave it out, the person the API key belongs to.
sendModeapproval | known | openHow freely it sends (see sending rules). Default known.
inboundModeall | verified | allowlistWhat it may read (see reading rules). Default all.
dailySendCapintegerEmails it may send in 24 hours before the rest wait for a person. Default 100, at most 5000.
newRecipientsDailyCapintegerIn open mode, new recipients a day before the rest wait for a person. Default 20.
allowRecipientsstring[]Addresses or @domains it may write to without approval.
allowSendersstring[]Addresses or @domains it may read in allowlist mode.
blockSendersstring[]Addresses or @domains whose mail is never shown to it.
disclosurebooleanEnd every email it sends with a line saying an AI agent sent it, and for whom. Default true; only a workspace admin can change it.
descriptionstringWhat the agent does, for the people who review it.
createKeyboolean or objecttrue, or { name?, scopes?, expiresInDays? }, to create its agent key in the same call. The answer then carries the key, shown once.

It answers 201 with the new inbox (an AgentInbox) and, if you asked for one, its key. The key’s secret appears in this answer only, so store it now.

The inbox’s id is also its mailbox id. Pass it as mailbox to any mail endpoint or MCP tool, and the agent’s mail works like any other mailbox’s: list it, search it, reply from it.

Every agent endpoint

Each one links to its full entry, with parameters and samples in curl, TypeScript and Python, in the API reference.

OperationEndpointScope
List agent inboxesGET /inboxesread
Create an agent inboxPOST /inboxeswrite
Get an agent inboxGET /inboxes/{id}read
Update or pause an agent inboxPATCH /inboxes/{id}write
Delete an agent inboxDELETE /inboxes/{id}write
List an agent's keysGET /inboxes/{id}/keysread
Create an agent keyPOST /inboxes/{id}/keyswrite
Revoke an agent keyDELETE /inboxes/{id}/keys/{keyId}write
List mail held back from agentsGET /holdsread
Release held mail to the agentPOST /holds/{id}/releasewrite
Keep held mail from the agentPOST /holds/{id}/dismisswrite
List agent emails waiting for a personGET /approvalsread
Get an approvalGET /approvals/{id}read
Approve and send an agent's emailPOST /approvals/{id}/approvesend
Reject an agent's emailPOST /approvals/{id}/rejectwrite

Agent keys

An agent key reaches one agent inbox and nothing else: not your team’s mailboxes, not other agents. Give each agent its own key, and keep your own keys to yourself.

POST/api/v1/inboxes/{id}/keysManage mail
curl
curl -X POST "https://supermailos.com/api/v1/inboxes/INBOX_ID/keys" \
  -H "Authorization: Bearer $SUPERMAILOS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Production worker","scopes":["read","send"],"expiresInDays":30}'
  • Create one with this endpoint, or with createKey when you create the inbox. Owners and admins only.
  • Scopes are the usual three: read, write and send, all three unless you choose. Leave out send and the agent can read and draft, but never send.
  • Expiry is optional: expiresInDays, from 1 to 3650. Without it, the key works until you revoke it.
  • It is shown once. Revoke it with DELETE /api/v1/inboxes/{id}/keys/{keyId}. Deleting the inbox deletes its keys.

An agent key can’t approve or reject email, release held mail, change its own rules, or create agents and keys. Those are for people, and the MCP server hides those tools from an agent’s key.

Agent inboxes have no IMAP or SMTP app passwords. An agent works through the API or MCP, which is where its rules are enforced. To connect it to an MCP client, use its key as the bearer token for https://supermailos.com/mcp; the setup for each client is in Connect your AI.

Sending rules and approvals

The rules belong to the inbox, so they apply whichever API key sends from it. sendMode decides how freely it sends:

sendModeWhat happens
known (default)Replies, and email to people the agent already corresponds with, go out right away. An email to a new recipient waits for a person.
approvalEvery email waits for a person.
openIt sends on its own, up to 20 new recipients a day unless you change that. Past the limit, emails wait for a person.

Two more settings apply in every mode:

  • dailySendCap: at most this many emails in 24 hours (default 100). Past it, emails wait for a person.
  • allowRecipients: addresses or whole domains (@example.com) the agent may always write to without approval.

People the agent already corresponds with are everyone who wrote to it and proved who they are (their email passed DMARC), everyone it has written to, the recipients of any email a person approved, and the senders of mail a person released to it. Anyone can write any address in an email’s From, so an unproven sender never counts.

When an email waits

A send that has to wait still answers 202, with status: "pending_approval". Nothing was sent yet. An email that did go out also answers 202, with status: "sent", so check status, not the HTTP code.

202 · pending_approval
{
  "ok": true,
  "status": "pending_approval",
  "approvalId": "apr_4f1c2b7e9a",
  "reasons": ["new recipient client@example.com"],
  "expiresAt": "2026-10-23T09:30:00.000Z",
  "mailboxId": "mbx_6e2a91c0d4",
  "from": "billing@acme.com"
}

Response · PendingSendResponse

NameTypeDescription
okrequiredbooleantrue: the request was accepted.
statusrequiredstringpending_approval: nothing was sent yet.
approvalIdrequiredstringGET /approvals/{approvalId} for what became of it.
reasonsrequiredstring[]Why it waits, in plain words.
expiresAtrequiredstring (date-time)When it expires unless a person decides: 7 days after the agent asked.
mailboxIdrequiredstringThe agent inbox it would go out from.
fromrequiredstring (email)The address it would go out from.

The agent’s sponsor is alerted by email. Then a person who can send from the agent’s inbox decides, in the app or with the API. The app shows the email exactly as it would go out: its HTML as recipients see it, its plain-text version and the disclosure line.

  • Approve sends it as written, as the agent, through every usual check. It needs the Send mail scope.
  • Reject sends nothing. An optional note tells the agent why. It needs the Manage mail scope.
Approve · curl
curl -X POST "https://supermailos.com/api/v1/approvals/APPROVAL_ID/approve" \
  -H "Authorization: Bearer $SUPERMAILOS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"note":"Looks right."}'
Reject · curl
curl -X POST "https://supermailos.com/api/v1/approvals/APPROVAL_ID/reject" \
  -H "Authorization: Bearer $SUPERMAILOS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"note":"Don'\''t quote a price; ask them to call."}'

The agent finds out by polling GET /api/v1/approvals/{id}, or from the agent.approval_decided webhook. Its own key can never approve its own email. Each approval is in one of these states:

statusMeaning
pendingWaiting for a person.
approvedA person approved it and it is being sent.
sentApproved and sent as written. result has its messageId and threadId.
rejectedA person rejected it and nothing was sent. decisionNote carries their note, if they left one.
failedApproved, but it couldn’t be sent. result says why.
expiredNobody decided within 7 days. Nothing was sent.

Retry a send with the same key and the same Idempotency-Key and you get the same approval back, so an email never waits twice. A different email under a key already used answers 422 idempotency_key_reused. Up to 200 emails can wait per agent; past that, its sends answer 429 until people catch up.

Reading rules and held mail

SuperMailOS checks SPF, DKIM and DMARC on every message to an agent inbox itself, then decides whether the agent may read it. inboundMode sets how strict that is:

inboundModeWhat the agent reads
all (default)Everything, except messages that fail authentication and mail from blocked senders.
verifiedOnly mail whose sender passed authentication.
allowlistOnly mail from senders on its allowlist, and only when they passed authentication.

Failing authentication is specific: the sender’s domain tells receivers to reject or quarantine mail that fails its checks, and this message failed them. Mail from a domain without such a policy counts as unverified, not failed. In every mode, failing mail and mail from blockSenders stay away from the agent.

Mail the agent may not read is held: its conversation doesn’t exist for the agent’s key until a person releases it. People still see it in their inbox as usual, and the agent’s sponsor is alerted by email. Each hold says why, and a conversation’s hold shows the worst reason among its messages: a spoof that arrives in a conversation held for an unverified sender turns it into a spoof’s hold.

reasonWhy it was held
auth_failedThe sender failed authentication (DMARC): it may not be who it claims to be.
blocked_senderThe sender is on this agent's block list.
not_allowlistedThe sender isn't on this agent's allowlist.
unverified_senderThe sender couldn't be verified, and this agent only reads verified mail.

A person who can manage the agent’s mail decides, in the app or with the API:

  • GET /api/v1/holds lists what is held. Add ?inbox= for one agent.
  • POST /api/v1/holds/{id}/release lets the agent read the conversation from now on, fires message.inbound for it, and counts the sender as someone the agent corresponds with. Send the reason you reviewed ({ "reason": "unverified_sender" }): if a worse email has arrived in the conversation since, nothing is released and you get 409.
  • POST /api/v1/holds/{id}/dismiss keeps it from the agent.
curl
curl "https://supermailos.com/api/v1/holds?inbox=INBOX_ID&status=held" \
  -H "Authorization: Bearer $SUPERMAILOS_API_KEY"

Release only mail you have looked at: a released message reaches the agent as it is. Until then, message.inbound doesn’t fire for it; the message.held webhook carries ids and the sender’s address only, never the subject or a snippet.

What an agent reads

An agent’s key reads the visible text of a message, nothing more. Before it gets a message, we remove what a person reading it would never see: elements hidden by an inline style or by the email’s own stylesheet (display:none, a zero font size, transparent text, text pushed off the screen), HTML comments, and invisible Unicode characters. Instructions planted in an email for an AI to follow are often hidden in exactly those places.

When an email has HTML, the agent’s text is built from what that HTML shows, with each link’s address after it. Its plain-text part is ignored: mail apps show the HTML, so nobody reads that part, which makes it a place to hide instructions in plain sight. Snippets in lists, search results and webhooks follow the same rule, and an agent’s key can’t download a message’s raw source.

Each message it reads carries trust:

trust
"trust": {
  "sender": "verified",
  "hiddenContent": true,
  "signals": ["2 hidden elements", "1 HTML comment"]
}
trust.senderMeaning
verifiedThe sender passed our own SPF, DKIM and DMARC check.
unverifiedThere was nothing to check against, or the check couldn't run. Not proven either way.
failedThe sender's domain tells receivers to reject or quarantine mail that fails its checks, and this message failed: a likely spoof. An agent only sees one after a person releases it.
selfMail this inbox sent.
  • hiddenContent is true when the email carried content a person wouldn’t see. text and html are the visible part only.
  • signals lists what was removed, such as 2 hidden elements.

The MCP server also wraps everything an agent’s key reads in a notice that it is untrusted email: data to work on, never instructions to follow. No filter catches every trick, so give the agent narrow instructions and keep email to new people behind approval.

The AI disclosure line

Every email an agent sends ends with a line naming the agent, the person it works for and your workspace:

Sent by Billing assistant, an AI agent working for Ana Ruiz at Acme.

It also carries an X-SuperMailOS-Agent header with the agent’s address, so software can tell agent mail apart too. The line is on by default (disclosure); a workspace admin can turn it off.

The line is added to every email, even one that already seems to have it, and nothing the agent writes can hide it: before adding it, we remove comments, stylesheets and the document’s head from the agent’s HTML and close anything it left open. Style an agent’s email with inline styles.

Saying plainly that an email comes from an AI, and on whose behalf, helps you meet transparency rules like the EU AI Act’s. Whether it is enough for your use is a question for your own legal adviser.

Its own pause

An agent is held to tighter limits than a person. One spam complaint, or three hard bounces (mail to addresses that don’t exist), on an agent’s mail within 24 hours pauses that agent. Each pause lasts longer than the one before: 1 hour, then 24 hours, then until a person resumes it.

Only that agent stops sending, from every app, a person writing as it included. It still receives mail, and the rest of your workspace keeps sending as before. Its sponsor gets an email and the agent.paused webhook fires. While it is paused, its sends answer 423 and nothing is queued. Its mail also counts toward your workspace’s own reputation, like anyone else’s.

A pause for complaints or bounces runs its course: the timed ones lift on their own, and the one that waits for a person needs a workspace admin. People can pause an agent themselves too, and its sponsor or a workspace admin can lift that pause, in the app or with PATCH /api/v1/inboxes/{id}.

Pause, then resume
{ "status": "paused", "pauseReason": "Checking a complaint" }

{ "status": "active" }

Webhooks

These events follow agent inboxes, and each carries the agent’s mailboxId. Payloads, signature checks and retries are on Webhooks; an admin subscribes an endpoint under Settings → Developers.

EventWhen it fires
message.heldMail to an agent inbox was held back from the agent. It carries ids and the sender's address only, never the subject or a snippet.
agent.approval_requestedAn email the agent asked to send is waiting for a person.
agent.approval_decidedA person approved the email (so it was sent) or rejected it. decision says which.
agent.pausedThe agent was paused, automatically or by a person. Its sending stops; the rest of the workspace keeps sending.

MCP tools

The MCP server at https://supermailos.com/mcp has tools for agent inboxes next to the mail tools. Tools for people are hidden from an agent’s own key, which works its inbox with the ordinary mail tools (list_threads, get_thread, reply_to_thread, send_email) under the rules above.

ToolWhat it doesScopeFor
list_agent_inboxesThe workspace's AI agent inboxes: address, sponsor, pause state, send and read rules, and emails waiting for approval.readPeople and agents
create_agent_inboxCreates an inbox of its own for an AI agent on your domain, with safe defaults. For people, not agents.writePeople
update_agent_inboxChanges an AI agent's send and read rules, or pauses and resumes its sending. For people, not agents.writePeople
list_agent_approvalsEmails AI agents asked to send that wait for a person, with why they wait. An agent sees only its own outcomes.readPeople and agents
decide_agent_approvalApproves (sends) or rejects an email an AI agent asked to send. For people, not agents.sendPeople
list_held_mailMail held back from AI agents because its sender failed authentication or isn't allowed. For people, not agents.readPeople
decide_held_mailReleases held mail to the AI agent, or keeps it from the agent. For people, not agents.writePeople

Creating an agent over MCP doesn’t create its key: that happens in the app or with the API. How to connect each client is in Connect your AI.

Limits by plan

Agent inboxes never count as paid seats. Each paid seat includes some, and the trial includes a few:

PlanAgent inboxes included
Pro2 with each paid seat
Business5 with each paid seat
14-day trial3 in total

So a Business workspace with 4 paid seats can have 20 agent inboxes. Need more? Add a seat. At the limit, creating another answers 403 with the code limit_reached. Plans and seats are on the pricing page.

Objects

The shapes the agent endpoints return. Fields marked required are always present; the rest appear when they apply.

AgentInbox

An agent inbox and its settings. Its id is its mailbox id.

NameTypeDescription
idrequiredstringAlso its mailbox id: pass it as mailbox to the mail endpoints.
addressrequiredstring (email)Its email address.
namerequiredstringWhat the agent is called; it signs its mail with it.
descriptionstringWhat the agent does, for the people who review it.
statusrequiredactive | pausedpaused while a pause is in force, otherwise active.
pausedobjectWhile it is paused: reason, by, since, and until (absent when it waits for a person to resume it).
sponsorrequiredobjectThe person who answers for the agent: approvals and alerts go to them.
sendModerequiredapproval | known | openHow freely it sends.
inboundModerequiredall | verified | allowlistWhat it may read.
dailySendCaprequiredintegerEmails it may send in 24 hours before the rest wait for a person.
newRecipientsDailyCaprequiredintegerIn open mode, new recipients a day before the rest wait for a person.
allowSendersrequiredstring[]Addresses or @domains the agent may read in allowlist mode.
blockSendersrequiredstring[]Never shown to the agent.
allowRecipientsrequiredstring[]The agent may write to these without approval.
disclosurerequiredbooleanIts mail ends with a line saying an AI agent sent it, for whom.
createdViaapp | api | mcpWhere it was created.
createdAtrequiredstring (date-time)When it was created.
updatedAtstring (date-time)When its settings last changed.
pendingApprovalsintegerEmails waiting for a person (people's keys only).

AgentKey

A key that reaches one agent inbox. The secret appears only in the answer that created it.

NameTypeDescription
idrequiredstringThe key's id, for revoking it.
namerequiredstringIts label. When you don't name it, Agent: followed by the agent's name.
prefixrequiredstringNon-secret leading characters.
scopesrequiredstring[]Any of read, write and send.
inboxIdrequiredstringThe one agent inbox it reaches.
createdAtrequiredstring (date-time)When it was created.
lastUsedAtstring (date-time)When it was last used.
expiresAtstring (date-time)When it stops working, if it expires.
revokedAtstring (date-time)When it was revoked.
secretstringOnly when it was just created: the key itself (smk_…). Shown once.

AgentApproval

An email an agent asked to send that waits, or waited, for a person. An agent's own key never gets the body or the Bcc line back.

NameTypeDescription
idrequiredstringThe approval's id, to poll, approve or reject it.
inboxIdrequiredstringThe agent inbox that asked.
kindrequiredsend | replysend for a new email, reply for a reply in a conversation.
threadIdstringThe conversation, for a reply.
fromrequiredstring (email)The address it goes out from.
torequiredstringRecipients, as the agent wrote them.
ccrequiredstringCc recipients, as the agent wrote them.
bccstringPeople's keys only.
subjectrequiredstringThe subject the agent wrote.
textstringThe body the agent wrote (people's keys only).
htmlstringIts HTML version, when it wrote one (people's keys only): what most mail apps show.
reasonsrequiredstring[]Why it waits, in plain words.
statusrequiredpending | approved | rejected | sent | failed | expiredWhere it stands. See the states under sending rules.
createdAtrequiredstring (date-time)When the agent asked.
expiresAtrequiredstring (date-time)When it expires unless a person decides.
decidedAtstring (date-time)When a person decided.
decisionNotestringWhat the person said when deciding.
resultobjectOnce sent: { messageId, threadId, mailboxId }. Once failed: { error, code }.

AgentHold

Mail held back from an agent, and what a person decided.

NameTypeDescription
idrequiredstringThe hold's id, to release or dismiss it.
inboxIdrequiredstringThe agent inbox it was held from.
threadIdrequiredstringHidden from the agent's key while held or dismissed.
emailIdstringThe held message.
senderrequiredstring (email)The From address, and only the address.
reasonrequiredauth_failed | blocked_sender | not_allowlisted | unverified_senderWhy it was held.
reasonTextrequiredstringThe reason in plain words.
verdictrequiredverified | unverified | failedOur own authentication verdict for the sender.
authobjectThe SPF, DKIM and DMARC results.
statusrequiredheld | released | dismissedHeld, released to the agent, or kept from it.
createdAtrequiredstring (date-time)When it was held.
decidedAtstring (date-time)When a person released or dismissed it.

Next: the API reference for every agent endpoint, or webhooks to hear about approvals, holds and pauses as they happen.

Was this page helpful?