Developers
.mdAPI reference
Updated 9 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
- 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.GET /mailboxes · GET /threads · GET /threads/{id} · GET /messages · GET /messages/{id} · GET /messages/{id}/raw · GET /attachments/{id} · GET /search |
| send | Send mail. Send a new email and reply to a conversation.POST /threads/{id}/reply · POST /send |
| write | Manage mail. Mark read, star, archive, move, label and save reply drafts. Never sends.PATCH /threads/{id} · POST /threads/{id}/draft |
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. |
Endpoints
12 operations in 5 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 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 | |
| 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).
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 | |
| 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) |
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. |
| 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 | Inbound SPF / DKIM / DMARC verdicts. |
| deliveredAt | string (date-time) | When the recipient's server accepted an outbound message. |
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/api/mcp.
Next: webhooks or connect your AI over MCP.
Was this page helpful?