Developers
.mdAPI reference
Updated 17 Oct 2026
On this page 10 sections
- Quick start
- Authentication
- Scopes
- Rate limits
- Idempotency
- Pagination
- Errors
- Endpoints
- List mailboxes
- List threads
- Get a thread
- Update a thread
- Reply to a thread
- Draft a reply
- List recent messages
- Get a message
- Download a message as .eml
- Download an attachment
- Search mail
- Send an email
- List agent inboxes
- Create an agent inbox
- Get an agent inbox
- Update or pause an agent inbox
- Delete an agent inbox
- List an agent's keys
- Create an agent key
- Revoke an agent key
- List mail held back from agents
- Release held mail to the agent
- Keep held mail from the agent
- List agent emails waiting for a person
- Get an approval
- Approve and send an agent's email
- Reject an agent's email
- List domains
- Add a domain
- Get a domain
- Check a domain's DNS now
- Download a domain's zone file
- Objects
- Mailbox
- Thread
- Message
- Attachment
- EmailAddress
- Machine-readable
The SuperMailOS API reads, searches, organizes and sends mail from your workspace’s real mailboxes: the same mail, rules and guardrails as the web app. It is plain JSON over HTTPS, authenticated with a key you create and can revoke at any time. Everything on this page is generated from the OpenAPI 3.1 spec, so the two cannot disagree.
| Setting | Value |
|---|---|
| Base URL | https://supermailos.com/api/v1 |
| OpenAPI spec | https://supermailos.com/api/v1/openapi.json |
| Auth | Authorization: Bearer smk_… |
| Format | JSON in, JSON out (attachments and .eml are binary) |
Looking to connect an AI assistant instead? That is the same set of operations behind an MCP server. To get a signed callback when mail arrives, see webhooks.
Quick start
- Create a key. A workspace admin opens Settings → Developers, names the key and picks its scopes. Start with Read mail alone. The key (it starts with
smk_) is shown once, so copy it somewhere safe. - Export it. Keep it in an environment variable or a secret manager, never in code that ships to a browser or an app.
- Call the API. List the mailboxes the key can reach:
curl "https://supermailos.com/api/v1/mailboxes" \
-H "Authorization: Bearer $SUPERMAILOS_API_KEY"Then find mail with search, read a conversation with get a thread, and when you are ready, send one.
Authentication
Send the key on every request as Authorization: Bearer <key>. A key acts for the person who created it and reaches exactly the mailboxes that person can open in the app. Owners and admins reach every mailbox in the workspace. A member reaches their own mailboxes plus mailboxes shared with them, with the limits of the share (view-only, view and send, or full access).
Anything a key cannot reach, such as another workspace or a teammate’s unshared mailbox, answers 404, never 403, so a key learns nothing about mail it cannot see. Access follows the person’s current role, and a key stops working (401) once its creator leaves the workspace. Only a hash of the key is stored, so a lost key cannot be recovered: revoke it and create another.
Bcc stays private on the API exactly as in the inbox: a message’s Bcc recipients are returned only to the person who sent it or who was themselves Bcc’d. Reading a thread over the API never marks it read; update it to change that.
Scopes
Each key carries the scopes you gave it when you created it. A request without the scope an endpoint needs answers 403 with the code missing_scope.
| Scope | What it allows |
|---|---|
| read | Read mail. Mailboxes, threads, messages, attachments and search. An owner's or admin's key also sees the workspace's domains and checks their DNS.GET /mailboxes · GET /threads · GET /threads/{id} · GET /messages · GET /messages/{id} · GET /messages/{id}/raw · GET /attachments/{id} · GET /search · GET /inboxes · GET /inboxes/{id} · GET /inboxes/{id}/keys · GET /holds · GET /approvals · GET /approvals/{id} · GET /domains · GET /domains/{id} · POST /domains/{id}/verify · GET /domains/{id}/zone |
| send | Send mail. Send a new email and reply to a conversation.POST /threads/{id}/reply · POST /send · POST /approvals/{id}/approve |
| write | Manage mail. Mark read, star, archive, move, label and save reply drafts. Never sends. An owner's or admin's key also adds domains and creates agent inboxes.PATCH /threads/{id} · POST /threads/{id}/draft · POST /inboxes · PATCH /inboxes/{id} · DELETE /inboxes/{id} · POST /inboxes/{id}/keys · DELETE /inboxes/{id}/keys/{keyId} · POST /holds/{id}/release · POST /holds/{id}/dismiss · POST /approvals/{id}/reject · POST /domains |
Rate limits
Each key may make 120 requests a minute, and each source IP 60 a minute before the key is even read. Go over either and you wait about 60 seconds. Every response carries X-RateLimit-Limit and X-RateLimit-Remaining; a 429 also carries Retry-After in seconds.
Sending has limits of its own on top: an hourly cap per mailbox and per workspace, and a lower daily ceiling for a newly registered sending domain while it warms up. Those also answer 429, with a message that says which one you hit.
Idempotency
Sending is the one thing you must never do twice. Add an Idempotency-Key header (any 1 to 255 printable ASCII characters; a UUID is ideal) to POST /send and POST /threads/{id}/reply. Keys belong to your API key and are kept for 24 hours.
- Same key, same body: the original response is replayed, with
Idempotency-Replayed: true. Nothing is sent again. - Same key, different body:
422idempotency_key_reused. - Same key while the first request is still running:
409idempotency_in_progress. Wait a moment and retry.
Only successful responses are stored. A request that was refused sent nothing, so you can fix it and retry with the same key. After a timeout or a 5xx, always retry with the key you first used.
Pagination
List endpoints return nextCursor. Pass it back as ?cursor= to get the next page; it is null on the last page. limit is 1 to 100 (default 25). Treat cursors as opaque.
Errors
Errors are JSON: { "error": "…", "code": "…" }. The error text is for people and may change; the code is stable, so branch on that. New fields can appear in any response, so ignore the ones you do not know.
| Code | Status | Meaning |
|---|---|---|
bad_request | 400 | The request is malformed: a missing or invalid field, a bad cursor, an unreadable body. |
unauthorized | 401 | The key is missing, wrong, revoked, or its creator has left the workspace. |
missing_scope | 403 | The key is valid but was not created with the scope this endpoint needs. |
forbidden | 403 | The key's user can see the mailbox but is not allowed to do this on it (for example sending from a view-only share). |
not_found | 404 | No such thread, message or attachment, or one the key cannot reach. The two are deliberately indistinguishable. |
conflict | 409 | The request conflicts with the current state of the resource. |
idempotency_in_progress | 409 | A request with this Idempotency-Key is still running. Wait, then retry with the same key. |
idempotency_key_reused | 422 | This Idempotency-Key was already used for a different request. Use a new key for a different email. |
payload_too_large | 413 | The body is over the 1 MB limit for text and HTML together. |
unprocessable | 422 | Valid JSON the server cannot act on: a domain or sender that is not verified yet, an invalid, suppressed or too many recipients. |
locked | 423 | Sending is paused for this mailbox or workspace. The message says why. |
rate_limited | 429 | Too many requests, or a sending limit was reached (hourly per mailbox or workspace, or a new domain's warm-up cap). Wait the Retry-After seconds when it is sent. |
insufficient_storage | 507 | The mailbox is out of storage. |
server_error | 500 | Something failed on our side. Safe to retry; with an Idempotency-Key, sends are safe too. |
upstream_failed | 502 | The mail server did not accept the message. Nothing was sent, so retrying is safe. |
limit_reached | 403 | The workspace already has all the agent inboxes its plan includes. Add a seat for more, or delete an agent. |
Endpoints
32 operations in 8 groups. Each shows the scope it needs, its parameters, a request in curl (TypeScript and Python are one click away), and the response.
| Operation | Endpoint | Scope |
|---|---|---|
| List mailboxes | GET /mailboxes | read |
| List threads | GET /threads | read |
| Get a thread | GET /threads/{id} | read |
| Update a thread | PATCH /threads/{id} | write |
| Reply to a thread | POST /threads/{id}/reply | send |
| Draft a reply | POST /threads/{id}/draft | write |
| List recent messages | GET /messages | read |
| Get a message | GET /messages/{id} | read |
| Download a message as .eml | GET /messages/{id}/raw | read |
| Download an attachment | GET /attachments/{id} | read |
| Search mail | GET /search | read |
| Send an email | POST /send | send |
| List agent inboxes | GET /inboxes | read |
| Create an agent inbox | POST /inboxes | write |
| Get an agent inbox | GET /inboxes/{id} | read |
| Update or pause an agent inbox | PATCH /inboxes/{id} | write |
| Delete an agent inbox | DELETE /inboxes/{id} | write |
| List an agent's keys | GET /inboxes/{id}/keys | read |
| Create an agent key | POST /inboxes/{id}/keys | write |
| Revoke an agent key | DELETE /inboxes/{id}/keys/{keyId} | write |
| List mail held back from agents | GET /holds | read |
| Release held mail to the agent | POST /holds/{id}/release | write |
| Keep held mail from the agent | POST /holds/{id}/dismiss | write |
| List agent emails waiting for a person | GET /approvals | read |
| Get an approval | GET /approvals/{id} | read |
| Approve and send an agent's email | POST /approvals/{id}/approve | send |
| Reject an agent's email | POST /approvals/{id}/reject | write |
| List domains | GET /domains | read |
| Add a domain | POST /domains | write |
| Get a domain | GET /domains/{id} | read |
| Check a domain's DNS now | POST /domains/{id}/verify | read |
| Download a domain's zone file | GET /domains/{id}/zone | read |
List mailboxes
/api/v1/mailboxesRead mailThe mailboxes this key can read, their aliases, what the key may do on each, and unread counts.
curl "https://supermailos.com/api/v1/mailboxes" \
-H "Authorization: Bearer $SUPERMAILOS_API_KEY"TypeScript
const res = await fetch("https://supermailos.com/api/v1/mailboxes", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SUPERMAILOS_API_KEY}`,
},
});
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error}`);
const data = await res.json();Python
import os
import requests
res = requests.get(
"https://supermailos.com/api/v1/mailboxes",
headers={
"Authorization": f"Bearer {os.environ['SUPERMAILOS_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
data = res.json()Returns 200 Mailboxes. Can also answer 401, 403, 404, 429 (see Errors).
Response
| Name | Type | Description |
|---|---|---|
| mailboxesrequired | Mailbox[] |
List threads
/api/v1/threadsRead mailConversations across the mailboxes this key can read, newest first. Never marks anything read.
Parameters
| Name | Type | Description |
|---|---|---|
| mailboxquery | string | Mailbox id, address, or alias address. |
| folderquery | inbox | sent | drafts | archive | spam | trash | starred | snoozed | all | Default inbox (all = everything but trash and spam). |
| unreadquery | boolean | true → only unread conversations. |
| labelquery | string | |
| hasAttachmentsquery | boolean | |
| qquery | string | Search query (see /search). With q and no folder, every folder but trash and spam is searched. |
| cursorquery | string | nextCursor from the previous page. |
| limitquery | integer |
curl "https://supermailos.com/api/v1/threads?folder=inbox&unread=true&limit=25" \
-H "Authorization: Bearer $SUPERMAILOS_API_KEY"TypeScript
const res = await fetch("https://supermailos.com/api/v1/threads?folder=inbox&unread=true&limit=25", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SUPERMAILOS_API_KEY}`,
},
});
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error}`);
const data = await res.json();Python
import os
import requests
res = requests.get(
"https://supermailos.com/api/v1/threads",
params={
"folder": "inbox",
"unread": "true",
"limit": "25",
},
headers={
"Authorization": f"Bearer {os.environ['SUPERMAILOS_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
data = res.json()Returns 200 A page of threads. Can also answer 400, 401, 403, 404, 429 (see Errors).
Response · ThreadList
| Name | Type | Description |
|---|---|---|
| threadsrequired | Thread[] | |
| nextCursorrequired | string | Pass as cursor for the next page. |
| unreadrequired | integer | Unread conversations matching the filters (all pages). |
Get a thread
/api/v1/threads/{id}Read mailOne conversation with every message, oldest first. Does not mark it read.
Parameters
| Name | Type | Description |
|---|---|---|
| idrequiredpath | string | Thread id. |
| htmlquery | boolean | false omits HTML bodies (smaller responses). |
curl "https://supermailos.com/api/v1/threads/THREAD_ID" \
-H "Authorization: Bearer $SUPERMAILOS_API_KEY"TypeScript
const res = await fetch("https://supermailos.com/api/v1/threads/THREAD_ID", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SUPERMAILOS_API_KEY}`,
},
});
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error}`);
const data = await res.json();Python
import os
import requests
res = requests.get(
"https://supermailos.com/api/v1/threads/THREAD_ID",
headers={
"Authorization": f"Bearer {os.environ['SUPERMAILOS_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
data = res.json()Returns 200 The thread. Can also answer 401, 403, 404, 429 (see Errors).
Response · ThreadDetail
| Name | Type | Description |
|---|---|---|
| threadrequired | Thread | |
| messagesrequired | Message[] | Oldest first. |
| permissionsrequired | object |
Update a thread
/api/v1/threads/{id}Manage mailMark read/unread (needs read access), star, move (inbox, archive, trash, spam) or relabel (need manage access: the mailbox's owner, an admin, or a full-access share). Returns the updated thread.
Parameters
| Name | Type | Description |
|---|---|---|
| idrequiredpath | string | Thread id. |
Request body (JSON)
| Name | Type | Description |
|---|---|---|
| unread | boolean | Needs read access. |
| read | boolean | Alias for !unread. |
| starred | boolean | |
| folder | inbox | archive | trash | spam | Move to a system folder. spam also trains the mailbox on the sender; spam → inbox forgives them. |
| labels | string[] | Replace every label. |
| addLabels | string[] | |
| removeLabels | string[] | Case-insensitive. |
curl -X PATCH "https://supermailos.com/api/v1/threads/THREAD_ID" \
-H "Authorization: Bearer $SUPERMAILOS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"unread":false,"folder":"archive","addLabels":["Invoices"]}'TypeScript
const res = await fetch("https://supermailos.com/api/v1/threads/THREAD_ID", {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.SUPERMAILOS_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"unread": false,
"folder": "archive",
"addLabels": [
"Invoices"
]
}),
});
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error}`);
const data = await res.json();Python
import os
import requests
res = requests.patch(
"https://supermailos.com/api/v1/threads/THREAD_ID",
headers={
"Authorization": f"Bearer {os.environ['SUPERMAILOS_API_KEY']}",
},
json={
"unread": False,
"folder": "archive",
"addLabels": ["Invoices"],
},
timeout=30,
)
res.raise_for_status()
data = res.json()Returns 200 Updated. Can also answer 400, 401, 403, 404, 422, 429 (see Errors).
Response
| Name | Type | Description |
|---|---|---|
| threadrequired | Thread |
Reply to a thread
/api/v1/threads/{id}/replyIdempotency-KeySend mailReply or reply-all, threaded with In-Reply-To/References, through the same send pipeline as the app (verified domain, suppression list, rate limits, warm-up, storage quota). Supports Idempotency-Key.
Parameters
| Name | Type | Description |
|---|---|---|
| idrequiredpath | string | Thread id. |
Request body (JSON)
| Name | Type | Description |
|---|---|---|
| text | string | text or html is required. |
| html | string | |
| replyAll | boolean | Reply to everyone on the last message (To + Cc). |
| from | string (email) | The conversation's mailbox or one of its aliases. Default: the address the mail arrived on. |
| to | string or string[] | Override the computed recipients. |
| cc | string or string[] | Added to the computed Cc. |
| bcc | string or string[] | |
| subject | string | Default: Re: <subject>. |
| quote | boolean | Append the previous message, quoted. |
| idempotencyKey | string | Alternative to the Idempotency-Key header (/reply only). |
curl -X POST "https://supermailos.com/api/v1/threads/THREAD_ID/reply" \
-H "Authorization: Bearer $SUPERMAILOS_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"text":"Thanks — received, I'\''ll get back to you today.","replyAll":false}'TypeScript
const res = await fetch("https://supermailos.com/api/v1/threads/THREAD_ID/reply", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUPERMAILOS_API_KEY}`,
"Content-Type": "application/json",
// Reuse the same key when retrying this send; never mails twice.
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
"text": "Thanks — received, I'll get back to you today.",
"replyAll": false
}),
});
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error}`);
const data = await res.json();Python
import os, uuid
import requests
res = requests.post(
"https://supermailos.com/api/v1/threads/THREAD_ID/reply",
headers={
"Authorization": f"Bearer {os.environ['SUPERMAILOS_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()), # reuse it on retries
},
json={
"text": "Thanks — received, I'll get back to you today.",
"replyAll": False,
},
timeout=30,
)
res.raise_for_status()
data = res.json()Returns 202 Accepted for delivery (or a replayed earlier response). Can also answer 400, 401, 403, 404, 409, 413, 422, 423, 429, 502, 507 (see Errors).
Response · SendResponse
| Name | Type | Description |
|---|---|---|
| okrequired | boolean | |
| status | string | Present on sends from an agent inbox. |
| idrequired | string | RFC Message-ID (kept for compatibility; same as messageId). |
| messageIdrequired | string | RFC 5322 Message-ID of the sent message. |
| emailIdrequired | string | Internal id of the stored Sent copy. |
| threadIdrequired | string | |
| mailboxIdrequired | string | |
| fromrequired | string (email) |
Draft a reply
/api/v1/threads/{id}/draftManage mailSave a reply as a draft in the conversation's mailbox for a person to review and send from the app. Nothing is sent.
Parameters
| Name | Type | Description |
|---|---|---|
| idrequiredpath | string | Thread id. |
Request body (JSON)
| Name | Type | Description |
|---|---|---|
| text | string | text or html is required. |
| html | string | |
| replyAll | boolean | Reply to everyone on the last message (To + Cc). |
| from | string (email) | The conversation's mailbox or one of its aliases. Default: the address the mail arrived on. |
| to | string or string[] | Override the computed recipients. |
| cc | string or string[] | Added to the computed Cc. |
| bcc | string or string[] | |
| subject | string | Default: Re: <subject>. |
| quote | boolean | Append the previous message, quoted. |
| idempotencyKey | string | Alternative to the Idempotency-Key header (/reply only). |
curl -X POST "https://supermailos.com/api/v1/threads/THREAD_ID/draft" \
-H "Authorization: Bearer $SUPERMAILOS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"text":"Draft for review: thanks, we can do Thursday at 10.","replyAll":true}'TypeScript
const res = await fetch("https://supermailos.com/api/v1/threads/THREAD_ID/draft", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUPERMAILOS_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"text": "Draft for review: thanks, we can do Thursday at 10.",
"replyAll": true
}),
});
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/threads/THREAD_ID/draft",
headers={
"Authorization": f"Bearer {os.environ['SUPERMAILOS_API_KEY']}",
},
json={
"text": "Draft for review: thanks, we can do Thursday at 10.",
"replyAll": True,
},
timeout=30,
)
res.raise_for_status()
data = res.json()Returns 201 Draft saved. Can also answer 400, 401, 403, 404, 413, 422, 429 (see Errors).
Response · DraftResponse
| Name | Type | Description |
|---|---|---|
| okrequired | boolean | |
| draftIdrequired | string | The draft conversation's id (folder drafts). |
| replyToThreadIdrequired | string | |
| fromrequired | string | |
| torequired | string | |
| cc | string | |
| subjectrequired | string |
List recent messages
/api/v1/messagesRead mailRecent individual messages across the mailboxes this key can read, newest first. For conversations, prefer GET /threads.
Parameters
| Name | Type | Description |
|---|---|---|
| mailboxquery | string (email) | |
| folderquery | inbox | sent | drafts | archive | spam | trash | |
| directionquery | inbound | outbound | |
| limitquery | integer |
curl "https://supermailos.com/api/v1/messages?direction=inbound&limit=20" \
-H "Authorization: Bearer $SUPERMAILOS_API_KEY"TypeScript
const res = await fetch("https://supermailos.com/api/v1/messages?direction=inbound&limit=20", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SUPERMAILOS_API_KEY}`,
},
});
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error}`);
const data = await res.json();Python
import os
import requests
res = requests.get(
"https://supermailos.com/api/v1/messages",
params={
"direction": "inbound",
"limit": "20",
},
headers={
"Authorization": f"Bearer {os.environ['SUPERMAILOS_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
data = res.json()Returns 200 Messages. Can also answer 401, 403, 404, 429 (see Errors).
Response
| Name | Type | Description |
|---|---|---|
| messagesrequired | MessageSummary[] | |
| countrequired | integer |
Get a message
/api/v1/messages/{id}Read mailOne message by id — the emailId in webhook payloads.
Parameters
| Name | Type | Description |
|---|---|---|
| idrequiredpath | string | |
| htmlquery | boolean | false omits HTML bodies (smaller responses). |
curl "https://supermailos.com/api/v1/messages/MESSAGE_ID" \
-H "Authorization: Bearer $SUPERMAILOS_API_KEY"TypeScript
const res = await fetch("https://supermailos.com/api/v1/messages/MESSAGE_ID", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SUPERMAILOS_API_KEY}`,
},
});
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error}`);
const data = await res.json();Python
import os
import requests
res = requests.get(
"https://supermailos.com/api/v1/messages/MESSAGE_ID",
headers={
"Authorization": f"Bearer {os.environ['SUPERMAILOS_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
data = res.json()Returns 200 The message. Can also answer 401, 403, 404, 429 (see Errors).
Response
| Name | Type | Description |
|---|---|---|
| messagerequired | Message |
Download a message as .eml
/api/v1/messages/{id}/rawRead mailThe message as an RFC 5322 file, rebuilt from the stored headers, bodies and attachments. Not byte-identical to the original (transport headers and signatures are not kept). An AI agent's own key gets 403: it reads messages' visible text instead.
Parameters
| Name | Type | Description |
|---|---|---|
| idrequiredpath | string |
curl "https://supermailos.com/api/v1/messages/MESSAGE_ID/raw" \
-H "Authorization: Bearer $SUPERMAILOS_API_KEY" \
-o message.emlTypeScript
import { writeFile } from "node:fs/promises";
const res = await fetch("https://supermailos.com/api/v1/messages/MESSAGE_ID/raw", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SUPERMAILOS_API_KEY}`,
},
});
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error}`);
await writeFile("message.eml", Buffer.from(await res.arrayBuffer()));Python
import os
import requests
res = requests.get(
"https://supermailos.com/api/v1/messages/MESSAGE_ID/raw",
headers={
"Authorization": f"Bearer {os.environ['SUPERMAILOS_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
with open("message.eml", "wb") as f:
f.write(res.content)Returns 200 The .eml file. Can also answer 401, 403, 404, 429 (see Errors).
Download an attachment
/api/v1/attachments/{id}Read mailThe attachment's bytes with its content type, served as a download.
Parameters
| Name | Type | Description |
|---|---|---|
| idrequiredpath | string |
curl "https://supermailos.com/api/v1/attachments/ATTACHMENT_ID" \
-H "Authorization: Bearer $SUPERMAILOS_API_KEY" \
-o attachment.binTypeScript
import { writeFile } from "node:fs/promises";
const res = await fetch("https://supermailos.com/api/v1/attachments/ATTACHMENT_ID", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SUPERMAILOS_API_KEY}`,
},
});
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error}`);
await writeFile("attachment.bin", Buffer.from(await res.arrayBuffer()));Python
import os
import requests
res = requests.get(
"https://supermailos.com/api/v1/attachments/ATTACHMENT_ID",
headers={
"Authorization": f"Bearer {os.environ['SUPERMAILOS_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
with open("attachment.bin", "wb") as f:
f.write(res.content)Returns 200 The file. Can also answer 401, 403, 404, 429 (see Errors).
Search mail
/api/v1/searchRead mailFull-text search with operators: from:, to:, subject:, label:, has:attachment, is:unread, is:starred, before:YYYY-MM-DD, after:YYYY-MM-DD, quoted phrases. Searches every folder except trash and spam unless folder is given.
Parameters
| Name | Type | Description |
|---|---|---|
| qrequiredquery | string | |
| mailboxquery | string | Mailbox id, address, or alias address. |
| folderquery | inbox | sent | drafts | archive | spam | trash | starred | snoozed | all | Default inbox (all = everything but trash and spam). |
| cursorquery | string | nextCursor from the previous page. |
| limitquery | integer |
curl "https://supermailos.com/api/v1/search?q=from%3Abilling+has%3Aattachment+after%3A2026-01-01&limit=10" \
-H "Authorization: Bearer $SUPERMAILOS_API_KEY"TypeScript
const res = await fetch("https://supermailos.com/api/v1/search?q=from%3Abilling+has%3Aattachment+after%3A2026-01-01&limit=10", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SUPERMAILOS_API_KEY}`,
},
});
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error}`);
const data = await res.json();Python
import os
import requests
res = requests.get(
"https://supermailos.com/api/v1/search",
params={
"q": "from:billing has:attachment after:2026-01-01",
"limit": "10",
},
headers={
"Authorization": f"Bearer {os.environ['SUPERMAILOS_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
data = res.json()Returns 200 Matching threads. Can also answer 400, 401, 403, 404, 429 (see Errors).
Response · ThreadList
| Name | Type | Description |
|---|---|---|
| threadsrequired | Thread[] | |
| nextCursorrequired | string | Pass as cursor for the next page. |
| unreadrequired | integer | Unread conversations matching the filters (all pages). |
Send an email
/api/v1/sendIdempotency-KeySend mailSend a new message as one of your mailboxes. Every guardrail of the app applies: verified domain, suppression list, per-mailbox/workspace hourly limits, new-domain warm-up, storage quota. Supports Idempotency-Key.
Request body (JSON)
| Name | Type | Description |
|---|---|---|
| fromrequired | string (email) | A mailbox (not an alias) the key can send as. |
| torequired | string or string[] | Comma-separated string or array of addresses. |
| cc | string or string[] | |
| bcc | string or string[] | |
| subject | string | |
| text | string | Plain-text body. text or html is required; together at most 1 MB. |
| html | string | HTML body (scripts, handlers and javascript: URLs are stripped). |
| idempotencyKey | string | Alternative to the Idempotency-Key header. |
curl -X POST "https://supermailos.com/api/v1/send" \
-H "Authorization: Bearer $SUPERMAILOS_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"from":"you@acme.com","to":"client@example.com","subject":"Your invoice","text":"Hi — your invoice is attached in the portal. Thanks!"}'TypeScript
const res = await fetch("https://supermailos.com/api/v1/send", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUPERMAILOS_API_KEY}`,
"Content-Type": "application/json",
// Reuse the same key when retrying this send; never mails twice.
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
"from": "you@acme.com",
"to": "client@example.com",
"subject": "Your invoice",
"text": "Hi — your invoice is attached in the portal. Thanks!"
}),
});
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error}`);
const data = await res.json();Python
import os, uuid
import requests
res = requests.post(
"https://supermailos.com/api/v1/send",
headers={
"Authorization": f"Bearer {os.environ['SUPERMAILOS_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()), # reuse it on retries
},
json={
"from": "you@acme.com",
"to": "client@example.com",
"subject": "Your invoice",
"text": "Hi — your invoice is attached in the portal. Thanks!",
},
timeout=30,
)
res.raise_for_status()
data = res.json()Returns 202 Accepted for delivery (or a replayed earlier response). Can also answer 400, 401, 403, 404, 409, 413, 422, 423, 429, 502, 507 (see Errors).
Response · SendResponse
| Name | Type | Description |
|---|---|---|
| okrequired | boolean | |
| status | string | Present on sends from an agent inbox. |
| idrequired | string | RFC Message-ID (kept for compatibility; same as messageId). |
| messageIdrequired | string | RFC 5322 Message-ID of the sent message. |
| emailIdrequired | string | Internal id of the stored Sent copy. |
| threadIdrequired | string | |
| mailboxIdrequired | string | |
| fromrequired | string (email) |
List agent inboxes
/api/v1/inboxesRead mailThe agent inboxes this key can see: a person's key, the ones its person can open (plus the workspace's allowance); an agent key, its own.
curl "https://supermailos.com/api/v1/inboxes" \
-H "Authorization: Bearer $SUPERMAILOS_API_KEY"TypeScript
const res = await fetch("https://supermailos.com/api/v1/inboxes", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SUPERMAILOS_API_KEY}`,
},
});
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error}`);
const data = await res.json();Python
import os
import requests
res = requests.get(
"https://supermailos.com/api/v1/inboxes",
headers={
"Authorization": f"Bearer {os.environ['SUPERMAILOS_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
data = res.json()Returns 200 Agent inboxes. Can also answer 401, 403, 404, 429 (see Errors).
Response
| Name | Type | Description |
|---|---|---|
| inboxesrequired | AgentInbox[] | |
| allowance | object |
Create an agent inbox
/api/v1/inboxesManage mailA real inbox for an AI agent on one of the workspace's verified domains (a workspace owner/admin's key). Agents don't count as paid seats; each plan includes some. With createKey, the answer carries a new agent key (shown once).
Request body (JSON)
| Name | Type | Description |
|---|---|---|
| namerequired | string | |
| description | string | |
| sponsorUserId | string | A person in the workspace (admins only). |
| sendMode | approval | known | open | |
| inboundMode | all | verified | allowlist | |
| dailySendCap | integer | |
| newRecipientsDailyCap | integer | |
| allowSenders | string[] | |
| blockSenders | string[] | |
| allowRecipients | string[] | |
| disclosure | boolean | End its mail with a line saying an AI agent sent it, for whom (admins only). |
| localPart | string | The part before the @. Made from the name when omitted. |
| domain | string | A verified domain name or id. The first verified one when omitted. |
| createKey | boolean or AgentKeyRequest | true (or key settings) to also create an agent key, shown once. |
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()Returns 201 Created. Can also answer 400, 401, 403, 404, 409, 422, 429 (see Errors).
Response
| Name | Type | Description |
|---|---|---|
| inboxrequired | AgentInbox | |
| key | AgentKey |
Get an agent inbox
/api/v1/inboxes/{id}Read mailOne agent inbox and its settings.
Parameters
| Name | Type | Description |
|---|---|---|
| idrequiredpath | string | Agent inbox id (its mailbox id). |
curl "https://supermailos.com/api/v1/inboxes/INBOX_ID" \
-H "Authorization: Bearer $SUPERMAILOS_API_KEY"TypeScript
const res = await fetch("https://supermailos.com/api/v1/inboxes/INBOX_ID", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SUPERMAILOS_API_KEY}`,
},
});
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error}`);
const data = await res.json();Python
import os
import requests
res = requests.get(
"https://supermailos.com/api/v1/inboxes/INBOX_ID",
headers={
"Authorization": f"Bearer {os.environ['SUPERMAILOS_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
data = res.json()Returns 200 The agent inbox. Can also answer 401, 403, 404, 429 (see Errors).
Response
| Name | Type | Description |
|---|---|---|
| inboxrequired | AgentInbox |
Update or pause an agent inbox
/api/v1/inboxes/{id}Manage mailChange its settings, or pause/resume it with status (paused + optional pauseReason, or active). Its sponsor or an admin; only an admin changes the sponsor or disclosure. A pause for complaints or bounces can't be lifted early (409), and the one that waits for a person needs an admin. Agent keys can't change their own rules.
Parameters
| Name | Type | Description |
|---|---|---|
| idrequiredpath | string | Agent inbox id (its mailbox id). |
Request body (JSON)
| Name | Type | Description |
|---|---|---|
| name | string | |
| description | string | |
| sponsorUserId | string | A person in the workspace (admins only). |
| sendMode | approval | known | open | |
| inboundMode | all | verified | allowlist | |
| dailySendCap | integer | |
| newRecipientsDailyCap | integer | |
| allowSenders | string[] | |
| blockSenders | string[] | |
| allowRecipients | string[] | |
| disclosure | boolean | End its mail with a line saying an AI agent sent it, for whom (admins only). |
| status | active | paused | paused stops its sending; active resumes it. |
| pauseReason | string |
curl -X PATCH "https://supermailos.com/api/v1/inboxes/INBOX_ID" \
-H "Authorization: Bearer $SUPERMAILOS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"dailySendCap":50,"allowRecipients":["@acme.com"]}'TypeScript
const res = await fetch("https://supermailos.com/api/v1/inboxes/INBOX_ID", {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.SUPERMAILOS_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"dailySendCap": 50,
"allowRecipients": [
"@acme.com"
]
}),
});
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error}`);
const data = await res.json();Python
import os
import requests
res = requests.patch(
"https://supermailos.com/api/v1/inboxes/INBOX_ID",
headers={
"Authorization": f"Bearer {os.environ['SUPERMAILOS_API_KEY']}",
},
json={
"dailySendCap": 50,
"allowRecipients": ["@acme.com"],
},
timeout=30,
)
res.raise_for_status()
data = res.json()Returns 200 Updated. Can also answer 400, 401, 403, 404, 422, 429 (see Errors).
Response
| Name | Type | Description |
|---|---|---|
| inboxrequired | AgentInbox |
Delete an agent inbox
/api/v1/inboxes/{id}Manage mailDelete it, its mail and its keys (owner/admin). Irreversible.
Parameters
| Name | Type | Description |
|---|---|---|
| idrequiredpath | string | Agent inbox id (its mailbox id). |
curl -X DELETE "https://supermailos.com/api/v1/inboxes/INBOX_ID" \
-H "Authorization: Bearer $SUPERMAILOS_API_KEY"TypeScript
const res = await fetch("https://supermailos.com/api/v1/inboxes/INBOX_ID", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.SUPERMAILOS_API_KEY}`,
},
});
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error}`);
const data = await res.json();Python
import os
import requests
res = requests.delete(
"https://supermailos.com/api/v1/inboxes/INBOX_ID",
headers={
"Authorization": f"Bearer {os.environ['SUPERMAILOS_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
data = res.json()Returns 200 Deleted. Can also answer 401, 403, 404, 429 (see Errors).
Response
| Name | Type | Description |
|---|---|---|
| deletedrequired | boolean | |
| addressrequired | string |
List an agent's keys
/api/v1/inboxes/{id}/keysRead mailThe agent's keys, without their secrets.
Parameters
| Name | Type | Description |
|---|---|---|
| idrequiredpath | string | Agent inbox id (its mailbox id). |
curl "https://supermailos.com/api/v1/inboxes/INBOX_ID/keys" \
-H "Authorization: Bearer $SUPERMAILOS_API_KEY"TypeScript
const res = await fetch("https://supermailos.com/api/v1/inboxes/INBOX_ID/keys", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SUPERMAILOS_API_KEY}`,
},
});
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error}`);
const data = await res.json();Python
import os
import requests
res = requests.get(
"https://supermailos.com/api/v1/inboxes/INBOX_ID/keys",
headers={
"Authorization": f"Bearer {os.environ['SUPERMAILOS_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
data = res.json()Returns 200 Keys. Can also answer 401, 403, 404, 429 (see Errors).
Response
| Name | Type | Description |
|---|---|---|
| keysrequired | AgentKey[] |
Create an agent key
/api/v1/inboxes/{id}/keysManage mailA key that reaches this one agent inbox and nothing else (owner/admin). The secret is in the answer, this once.
Parameters
| Name | Type | Description |
|---|---|---|
| idrequiredpath | string | Agent inbox id (its mailbox id). |
Request body (JSON)
| Name | Type | Description |
|---|---|---|
| name | string | |
| scopes | read | write | send[] | |
| expiresInDays | integer | No expiry when omitted. |
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}'TypeScript
const res = await fetch("https://supermailos.com/api/v1/inboxes/INBOX_ID/keys", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUPERMAILOS_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"name": "Production worker",
"scopes": [
"read",
"send"
],
"expiresInDays": 30
}),
});
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/INBOX_ID/keys",
headers={
"Authorization": f"Bearer {os.environ['SUPERMAILOS_API_KEY']}",
},
json={
"name": "Production worker",
"scopes": ["read", "send"],
"expiresInDays": 30,
},
timeout=30,
)
res.raise_for_status()
data = res.json()Returns 201 Created. Can also answer 400, 401, 403, 404, 429 (see Errors).
Response
| Name | Type | Description |
|---|---|---|
| keyrequired | AgentKey |
Revoke an agent key
/api/v1/inboxes/{id}/keys/{keyId}Manage mailIt stops working at once (owner/admin).
Parameters
| Name | Type | Description |
|---|---|---|
| idrequiredpath | string | Agent inbox id (its mailbox id). |
| keyIdrequiredpath | string |
curl -X DELETE "https://supermailos.com/api/v1/inboxes/INBOX_ID/keys/KEY_ID" \
-H "Authorization: Bearer $SUPERMAILOS_API_KEY"TypeScript
const res = await fetch("https://supermailos.com/api/v1/inboxes/INBOX_ID/keys/KEY_ID", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.SUPERMAILOS_API_KEY}`,
},
});
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error}`);
const data = await res.json();Python
import os
import requests
res = requests.delete(
"https://supermailos.com/api/v1/inboxes/INBOX_ID/keys/KEY_ID",
headers={
"Authorization": f"Bearer {os.environ['SUPERMAILOS_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
data = res.json()Returns 200 Revoked. Can also answer 401, 403, 404, 429 (see Errors).
Response
| Name | Type | Description |
|---|---|---|
| revoked | boolean |
List mail held back from agents
/api/v1/holdsRead mailConversations an agent can't see yet: the sender failed authentication, is blocked, or isn't allowed by the inbox's inboundMode. People's keys only.
Parameters
| Name | Type | Description |
|---|---|---|
| inboxquery | string | One agent inbox. |
| statusquery | held | released | dismissed | |
| limitquery | integer |
curl "https://supermailos.com/api/v1/holds?inbox=INBOX_ID&status=held" \
-H "Authorization: Bearer $SUPERMAILOS_API_KEY"TypeScript
const res = await fetch("https://supermailos.com/api/v1/holds?inbox=INBOX_ID&status=held", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SUPERMAILOS_API_KEY}`,
},
});
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error}`);
const data = await res.json();Python
import os
import requests
res = requests.get(
"https://supermailos.com/api/v1/holds",
params={
"inbox": "INBOX_ID",
"status": "held",
},
headers={
"Authorization": f"Bearer {os.environ['SUPERMAILOS_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
data = res.json()Returns 200 Held mail. Can also answer 400, 401, 403, 404, 429 (see Errors).
Response
| Name | Type | Description |
|---|---|---|
| holdsrequired | AgentHold[] |
Release held mail to the agent
/api/v1/holds/{id}/releaseManage mailThe agent can read the conversation from now on, and message.inbound fires for it. A hold shows the worst reason among its messages; send the reason you reviewed, and if a worse email has arrived since, nothing is released (409).
Parameters
| Name | Type | Description |
|---|---|---|
| idrequiredpath | string | Hold id. |
Request body (JSON)
| Name | Type | Description |
|---|---|---|
| reason | auth_failed | blocked_sender | not_allowlisted | unverified_sender | The hold's reason as you reviewed it. |
curl -X POST "https://supermailos.com/api/v1/holds/HOLD_ID/release" \
-H "Authorization: Bearer $SUPERMAILOS_API_KEY"TypeScript
const res = await fetch("https://supermailos.com/api/v1/holds/HOLD_ID/release", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUPERMAILOS_API_KEY}`,
},
});
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/holds/HOLD_ID/release",
headers={
"Authorization": f"Bearer {os.environ['SUPERMAILOS_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
data = res.json()Returns 200 Released. Can also answer 400, 401, 403, 404, 409, 429 (see Errors).
Response
| Name | Type | Description |
|---|---|---|
| holdrequired | AgentHold |
Keep held mail from the agent
/api/v1/holds/{id}/dismissManage mailPeople still see it; releasing it later still works.
Parameters
| Name | Type | Description |
|---|---|---|
| idrequiredpath | string | Hold id. |
curl -X POST "https://supermailos.com/api/v1/holds/HOLD_ID/dismiss" \
-H "Authorization: Bearer $SUPERMAILOS_API_KEY"TypeScript
const res = await fetch("https://supermailos.com/api/v1/holds/HOLD_ID/dismiss", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUPERMAILOS_API_KEY}`,
},
});
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/holds/HOLD_ID/dismiss",
headers={
"Authorization": f"Bearer {os.environ['SUPERMAILOS_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
data = res.json()Returns 200 Dismissed. Can also answer 401, 403, 404, 409, 429 (see Errors).
Response
| Name | Type | Description |
|---|---|---|
| holdrequired | AgentHold |
List agent emails waiting for a person
/api/v1/approvalsRead mailA person's key: the agent inboxes its person can open, with each email's text. An agent key: its own, status and outcome only.
Parameters
| Name | Type | Description |
|---|---|---|
| inboxquery | string | |
| statusquery | pending | approved | rejected | sent | failed | expired | |
| limitquery | integer |
curl "https://supermailos.com/api/v1/approvals?status=pending" \
-H "Authorization: Bearer $SUPERMAILOS_API_KEY"TypeScript
const res = await fetch("https://supermailos.com/api/v1/approvals?status=pending", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SUPERMAILOS_API_KEY}`,
},
});
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error}`);
const data = await res.json();Python
import os
import requests
res = requests.get(
"https://supermailos.com/api/v1/approvals",
params={
"status": "pending",
},
headers={
"Authorization": f"Bearer {os.environ['SUPERMAILOS_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
data = res.json()Returns 200 Approvals. Can also answer 400, 401, 403, 404, 429 (see Errors).
Response
| Name | Type | Description |
|---|---|---|
| approvalsrequired | AgentApproval[] |
Get an approval
/api/v1/approvals/{id}Read mailOne waiting email and what became of it. Agents poll this after a send answered pending_approval.
Parameters
| Name | Type | Description |
|---|---|---|
| idrequiredpath | string | Approval id. |
curl "https://supermailos.com/api/v1/approvals/APPROVAL_ID" \
-H "Authorization: Bearer $SUPERMAILOS_API_KEY"TypeScript
const res = await fetch("https://supermailos.com/api/v1/approvals/APPROVAL_ID", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SUPERMAILOS_API_KEY}`,
},
});
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error}`);
const data = await res.json();Python
import os
import requests
res = requests.get(
"https://supermailos.com/api/v1/approvals/APPROVAL_ID",
headers={
"Authorization": f"Bearer {os.environ['SUPERMAILOS_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
data = res.json()Returns 200 The approval. Can also answer 401, 403, 404, 429 (see Errors).
Response
| Name | Type | Description |
|---|---|---|
| approvalrequired | AgentApproval |
Approve and send an agent's email
/api/v1/approvals/{id}/approveSend mailSends it as written, as the agent (every guardrail applies). A person who can send from the agent's inbox; never an agent key.
Parameters
| Name | Type | Description |
|---|---|---|
| idrequiredpath | string | Approval id. |
Request body (JSON)
| Name | Type | Description |
|---|---|---|
| note | string |
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."}'TypeScript
const res = await fetch("https://supermailos.com/api/v1/approvals/APPROVAL_ID/approve", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUPERMAILOS_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"note": "Looks right."
}),
});
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/approvals/APPROVAL_ID/approve",
headers={
"Authorization": f"Bearer {os.environ['SUPERMAILOS_API_KEY']}",
},
json={
"note": "Looks right.",
},
timeout=30,
)
res.raise_for_status()
data = res.json()Returns 200 Sent. Can also answer 401, 403, 404, 409, 423, 429, 502 (see Errors).
Response
| Name | Type | Description |
|---|---|---|
| approvalrequired | AgentApproval |
Reject an agent's email
/api/v1/approvals/{id}/rejectManage mailNothing is sent. The agent sees your note.
Parameters
| Name | Type | Description |
|---|---|---|
| idrequiredpath | string | Approval id. |
Request body (JSON)
| Name | Type | Description |
|---|---|---|
| note | string |
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."}'TypeScript
const res = await fetch("https://supermailos.com/api/v1/approvals/APPROVAL_ID/reject", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUPERMAILOS_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"note": "Don't quote a price; ask them to call."
}),
});
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/approvals/APPROVAL_ID/reject",
headers={
"Authorization": f"Bearer {os.environ['SUPERMAILOS_API_KEY']}",
},
json={
"note": "Don't quote a price; ask them to call.",
},
timeout=30,
)
res.raise_for_status()
data = res.json()Returns 200 Rejected. Can also answer 401, 403, 404, 409, 429 (see Errors).
Response
| Name | Type | Description |
|---|---|---|
| approvalrequired | AgentApproval |
List domains
/api/v1/domainsRead mailThe workspace's domains with their status and what's wrong with their DNS, plus how many the plan allows. A workspace owner's or admin's key; an agent's own key gets 403.
curl "https://supermailos.com/api/v1/domains" \
-H "Authorization: Bearer $SUPERMAILOS_API_KEY"TypeScript
const res = await fetch("https://supermailos.com/api/v1/domains", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SUPERMAILOS_API_KEY}`,
},
});
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error}`);
const data = await res.json();Python
import os
import requests
res = requests.get(
"https://supermailos.com/api/v1/domains",
headers={
"Authorization": f"Bearer {os.environ['SUPERMAILOS_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
data = res.json()Returns 200 Domains. Can also answer 401, 403, 404, 429 (see Errors).
Response
| Name | Type | Description |
|---|---|---|
| domainsrequired | DomainSummary[] | |
| allowancerequired | object |
Add a domain
/api/v1/domainsManage mailAdds a domain and answers with the DNS records to publish at its DNS host, and where to publish them (setup). Nothing changes in the domain's DNS or where its email goes today: publishing the MX records later moves its incoming email here. A domain the workspace already has comes back unchanged (200, created: false). A workspace owner's or admin's key; plan limits apply (403 limit_reached), and at most 10 domains an hour can be added through the API.
Request body (JSON)
| Name | Type | Description |
|---|---|---|
| domainrequired | string | A domain you own, or a subdomain of one (agents.acme.com). |
curl -X POST "https://supermailos.com/api/v1/domains" \
-H "Authorization: Bearer $SUPERMAILOS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"domain":"acme.com"}'TypeScript
const res = await fetch("https://supermailos.com/api/v1/domains", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUPERMAILOS_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"domain": "acme.com"
}),
});
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/domains",
headers={
"Authorization": f"Bearer {os.environ['SUPERMAILOS_API_KEY']}",
},
json={
"domain": "acme.com",
},
timeout=30,
)
res.raise_for_status()
data = res.json()Returns 200 The workspace already has it. Can also answer 201, 400, 401, 403, 404, 409, 429 (see Errors).
Response
| Name | Type | Description |
|---|---|---|
| domainrequired | Domain | |
| createdrequired | boolean |
Get a domain
/api/v1/domains/{id}Read mailOne domain by id or name: its status, every DNS record it needs with what the last check found, where its DNS is hosted, and the next step.
Parameters
| Name | Type | Description |
|---|---|---|
| idrequiredpath | string | The domain's id or name (acme.com). |
curl "https://supermailos.com/api/v1/domains/acme.com" \
-H "Authorization: Bearer $SUPERMAILOS_API_KEY"TypeScript
const res = await fetch("https://supermailos.com/api/v1/domains/acme.com", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SUPERMAILOS_API_KEY}`,
},
});
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error}`);
const data = await res.json();Python
import os
import requests
res = requests.get(
"https://supermailos.com/api/v1/domains/acme.com",
headers={
"Authorization": f"Bearer {os.environ['SUPERMAILOS_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
data = res.json()Returns 200 The domain. Can also answer 401, 403, 404, 429 (see Errors).
Response
| Name | Type | Description |
|---|---|---|
| domainrequired | Domain |
Check a domain's DNS now
/api/v1/domains/{id}/verifyRead mailChecks the domain's live DNS now, like Verify now in the app, asking its own nameservers first, and answers with its updated status. A check from the last 10 seconds is answered from its result (checked: false). Unverified domains are also re-checked on a schedule; the domain.verified webhook tells you when one goes live.
Parameters
| Name | Type | Description |
|---|---|---|
| idrequiredpath | string | The domain's id or name (acme.com). |
curl -X POST "https://supermailos.com/api/v1/domains/acme.com/verify" \
-H "Authorization: Bearer $SUPERMAILOS_API_KEY"TypeScript
const res = await fetch("https://supermailos.com/api/v1/domains/acme.com/verify", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUPERMAILOS_API_KEY}`,
},
});
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/domains/acme.com/verify",
headers={
"Authorization": f"Bearer {os.environ['SUPERMAILOS_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
data = res.json()Returns 200 Checked. Can also answer 401, 403, 404, 429 (see Errors).
Response
| Name | Type | Description |
|---|---|---|
| domainrequired | Domain | |
| checkedrequired | boolean | false: answered from a check moments ago. |
Download a domain's zone file
/api/v1/domains/{id}/zoneRead mailThe domain's records as a BIND zone file, to import at a DNS host that accepts one. Optional records are marked. 409 for the workspace's instant subdomain, which has nothing to publish.
Parameters
| Name | Type | Description |
|---|---|---|
| idrequiredpath | string | The domain's id or name (acme.com). |
curl "https://supermailos.com/api/v1/domains/acme.com/zone" \
-H "Authorization: Bearer $SUPERMAILOS_API_KEY" \
-o acme.com.zoneTypeScript
import { writeFile } from "node:fs/promises";
const res = await fetch("https://supermailos.com/api/v1/domains/acme.com/zone", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SUPERMAILOS_API_KEY}`,
},
});
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error}`);
await writeFile("acme.com.zone", Buffer.from(await res.arrayBuffer()));Python
import os
import requests
res = requests.get(
"https://supermailos.com/api/v1/domains/acme.com/zone",
headers={
"Authorization": f"Bearer {os.environ['SUPERMAILOS_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
with open("acme.com.zone", "wb") as f:
f.write(res.content)Returns 200 The zone file. Can also answer 401, 403, 404, 409, 429 (see Errors).
Objects
The shapes that come back most often. Fields marked required are always present; the rest appear when they apply.
Mailbox
A mailbox the key can reach.
| Name | Type | Description |
|---|---|---|
| idrequired | string | |
| addressrequired | string (email) | |
| displayNamerequired | string | |
| aliasesrequired | string (email)[] | Aliases that deliver into this mailbox. Replies may be sent from an alias. |
| sourcerequired | owner | admin | grant | How the key reaches it: the key user's own mailbox, as a workspace admin, or shared with them. |
| accessrequired | object | |
| unreadrequired | integer | Unread conversations in the inbox. |
Thread
A conversation, as listed.
| Name | Type | Description |
|---|---|---|
| idrequired | string | |
| mailboxIdrequired | string | |
| mailboxrequired | string (email) | The mailbox's address. |
| receivedOnrequired | string (email) | Where the conversation arrived (an alias, if so) or the address it was sent from. |
| subjectrequired | string | |
| snippetrequired | string | |
| fromrequired | EmailAddress | The other party: latest inbound sender, else the first recipient of the latest sent message. |
| participantsrequired | EmailAddress[] | From/To/Cc of every message — never Bcc. |
| folderrequired | inbox | sent | drafts | archive | spam | trash | |
| unreadrequired | boolean | |
| starredrequired | boolean | |
| labelsrequired | string[] | |
| lastMessageAtrequired | string (date-time) | |
| snoozedUntil | string (date-time) | |
| hasAttachmentsrequired | boolean | |
| messageCountrequired | integer |
Message
One message, with its bodies and attachment list.
| Name | Type | Description |
|---|---|---|
| idrequired | string | Internal message id (webhooks: emailId). |
| threadIdrequired | string | |
| mailboxIdrequired | string | |
| directionrequired | inbound | outbound | |
| messageId | string | RFC 5322 Message-ID header, e.g. <abc@acme.com>. |
| fromrequired | EmailAddress | |
| torequired | EmailAddress[] | |
| ccrequired | EmailAddress[] | |
| bcc | EmailAddress[] | Present only when the key's user sent the message or was Bcc'd on it. |
| replyTo | EmailAddress[] | Received mail's Reply-To, when it named one: where its author asked replies to go. Replying through the API uses it instead of from. |
| subjectrequired | string | |
| daterequired | string (date-time) | |
| readrequired | boolean | |
| textrequired | string | Plain-text body. |
| html | string | HTML body with scripts and event handlers removed (omitted with ?html=false). |
| attachmentsrequired | Attachment[] | |
| auth | object | Present only when we verified the sender ourselves: mail someone in the workspace sent from their own mailbox (all three pass). Absent on other received mail: the mail server doesn't give the app an SPF / DKIM / DMARC verdict, so none is claimed. |
| deliveredAt | string (date-time) | When the recipient's server accepted an outbound message. |
| trust | object | Agent keys only. sender: our own SPF/DKIM/DMARC verdict (verified, unverified, failed; self for mail this inbox sent). hiddenContent: the email carried text a person wouldn't see — text/html are the visible part only. |
Attachment
A file on a message.
| Name | Type | Description |
|---|---|---|
| idrequired | string | |
| filenamerequired | string | |
| contentTyperequired | string | |
| sizerequired | integer | Bytes. |
| inlinerequired | boolean | Embedded in the HTML body via cid: rather than attached as a file. |
| contentId | string | Matches cid: references in the HTML body. |
| urlrequired | string | Download path (GET, same bearer key). |
EmailAddress
A person or address.
| Name | Type | Description |
|---|---|---|
| emailrequired | string (email) | |
| name | string |
Machine-readable
The spec at https://supermailos.com/api/v1/openapi.json carries every operation, schema and webhook payload, with the same curl, TypeScript and Python samples under x-codeSamples. Point a client generator or an API tool at it. For agents there is also an agent skill, and the MCP endpoint lives at https://supermailos.com/mcp.
Next: webhooks or connect your AI over MCP.
Was this page helpful?