DocsAPI reference

Developers

.md

API reference

Updated 9 Oct 2026

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.

SettingValue
Base URLhttps://supermailos.com/api/v1
OpenAPI spechttps://supermailos.com/api/v1/openapi.json
AuthAuthorization: Bearer smk_…
FormatJSON 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

  1. 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.
  2. Export it. Keep it in an environment variable or a secret manager, never in code that ships to a browser or an app.
  3. Call the API. List the mailboxes the key can reach:
curl
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.

ScopeWhat it allows
readRead 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
sendSend mail. Send a new email and reply to a conversation.POST /threads/{id}/reply · POST /send
writeManage 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: 422 idempotency_key_reused.
  • Same key while the first request is still running: 409 idempotency_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.

CodeStatusMeaning
bad_request400The request is malformed: a missing or invalid field, a bad cursor, an unreadable body.
unauthorized401The key is missing, wrong, revoked, or its creator has left the workspace.
missing_scope403The key is valid but was not created with the scope this endpoint needs.
forbidden403The 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_found404No such thread, message or attachment, or one the key cannot reach. The two are deliberately indistinguishable.
conflict409The request conflicts with the current state of the resource.
idempotency_in_progress409A request with this Idempotency-Key is still running. Wait, then retry with the same key.
idempotency_key_reused422This Idempotency-Key was already used for a different request. Use a new key for a different email.
payload_too_large413The body is over the 1 MB limit for text and HTML together.
unprocessable422Valid JSON the server cannot act on: a domain or sender that is not verified yet, an invalid, suppressed or too many recipients.
locked423Sending is paused for this mailbox or workspace. The message says why.
rate_limited429Too 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_storage507The mailbox is out of storage.
server_error500Something failed on our side. Safe to retry; with an Idempotency-Key, sends are safe too.
upstream_failed502The 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.

OperationEndpointScope
List mailboxesGET /mailboxesread
List threadsGET /threadsread
Get a threadGET /threads/{id}read
Update a threadPATCH /threads/{id}write
Reply to a threadPOST /threads/{id}/replysend
Draft a replyPOST /threads/{id}/draftwrite
List recent messagesGET /messagesread
Get a messageGET /messages/{id}read
Download a message as .emlGET /messages/{id}/rawread
Download an attachmentGET /attachments/{id}read
Search mailGET /searchread
Send an emailPOST /sendsend

List mailboxes

GET/api/v1/mailboxesRead mail

The mailboxes this key can read, their aliases, what the key may do on each, and unread counts.

curl
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

NameTypeDescription
mailboxesrequiredMailbox[]

List threads

GET/api/v1/threadsRead mail

Conversations across the mailboxes this key can read, newest first. Never marks anything read.

Parameters

NameTypeDescription
mailboxquerystringMailbox id, address, or alias address.
folderqueryinbox | sent | drafts | archive | spam | trash | starred | snoozed | allDefault inbox (all = everything but trash and spam).
unreadquerybooleantrue → only unread conversations.
labelquerystring
hasAttachmentsqueryboolean
qquerystringSearch query (see /search). With q and no folder, every folder but trash and spam is searched.
cursorquerystringnextCursor from the previous page.
limitqueryinteger
curl
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

NameTypeDescription
threadsrequiredThread[]
nextCursorrequiredstringPass as cursor for the next page.
unreadrequiredintegerUnread conversations matching the filters (all pages).

Get a thread

GET/api/v1/threads/{id}Read mail

One conversation with every message, oldest first. Does not mark it read.

Parameters

NameTypeDescription
idrequiredpathstringThread id.
htmlquerybooleanfalse omits HTML bodies (smaller responses).
curl
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

NameTypeDescription
threadrequiredThread
messagesrequiredMessage[]Oldest first.
permissionsrequiredobject

Update a thread

PATCH/api/v1/threads/{id}Manage mail

Mark 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

NameTypeDescription
idrequiredpathstringThread id.

Request body (JSON)

NameTypeDescription
unreadbooleanNeeds read access.
readbooleanAlias for !unread.
starredboolean
folderinbox | archive | trash | spamMove to a system folder. spam also trains the mailbox on the sender; spam → inbox forgives them.
labelsstring[]Replace every label.
addLabelsstring[]
removeLabelsstring[]Case-insensitive.
curl
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

NameTypeDescription
threadrequiredThread

Reply to a thread

POST/api/v1/threads/{id}/replyIdempotency-KeySend mail

Reply 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

NameTypeDescription
idrequiredpathstringThread id.

Request body (JSON)

NameTypeDescription
textstringtext or html is required.
htmlstring
replyAllbooleanReply to everyone on the last message (To + Cc).
fromstring (email)The conversation's mailbox or one of its aliases. Default: the address the mail arrived on.
tostring or string[]Override the computed recipients.
ccstring or string[]Added to the computed Cc.
bccstring or string[]
subjectstringDefault: Re: <subject>.
quotebooleanAppend the previous message, quoted.
idempotencyKeystringAlternative to the Idempotency-Key header (/reply only).
curl
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

NameTypeDescription
okrequiredboolean
idrequiredstringRFC Message-ID (kept for compatibility; same as messageId).
messageIdrequiredstringRFC 5322 Message-ID of the sent message.
emailIdrequiredstringInternal id of the stored Sent copy.
threadIdrequiredstring
mailboxIdrequiredstring
fromrequiredstring (email)

Draft a reply

POST/api/v1/threads/{id}/draftManage mail

Save a reply as a draft in the conversation's mailbox for a person to review and send from the app. Nothing is sent.

Parameters

NameTypeDescription
idrequiredpathstringThread id.

Request body (JSON)

NameTypeDescription
textstringtext or html is required.
htmlstring
replyAllbooleanReply to everyone on the last message (To + Cc).
fromstring (email)The conversation's mailbox or one of its aliases. Default: the address the mail arrived on.
tostring or string[]Override the computed recipients.
ccstring or string[]Added to the computed Cc.
bccstring or string[]
subjectstringDefault: Re: <subject>.
quotebooleanAppend the previous message, quoted.
idempotencyKeystringAlternative to the Idempotency-Key header (/reply only).
curl
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

NameTypeDescription
okrequiredboolean
draftIdrequiredstringThe draft conversation's id (folder drafts).
replyToThreadIdrequiredstring
fromrequiredstring
torequiredstring
ccstring
subjectrequiredstring

List recent messages

GET/api/v1/messagesRead mail

Recent individual messages across the mailboxes this key can read, newest first. For conversations, prefer GET /threads.

Parameters

NameTypeDescription
mailboxquerystring (email)
folderqueryinbox | sent | drafts | archive | spam | trash
directionqueryinbound | outbound
limitqueryinteger
curl
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

NameTypeDescription
messagesrequiredMessageSummary[]
countrequiredinteger

Get a message

GET/api/v1/messages/{id}Read mail

One message by id — the emailId in webhook payloads.

Parameters

NameTypeDescription
idrequiredpathstring
htmlquerybooleanfalse omits HTML bodies (smaller responses).
curl
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

NameTypeDescription
messagerequiredMessage

Download a message as .eml

GET/api/v1/messages/{id}/rawRead mail

The 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

NameTypeDescription
idrequiredpathstring
curl
curl "https://supermailos.com/api/v1/messages/MESSAGE_ID/raw" \
  -H "Authorization: Bearer $SUPERMAILOS_API_KEY" \
  -o message.eml
TypeScript
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

GET/api/v1/attachments/{id}Read mail

The attachment's bytes with its content type, served as a download.

Parameters

NameTypeDescription
idrequiredpathstring
curl
curl "https://supermailos.com/api/v1/attachments/ATTACHMENT_ID" \
  -H "Authorization: Bearer $SUPERMAILOS_API_KEY" \
  -o attachment.bin
TypeScript
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

GET/api/v1/searchRead mail

Full-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

NameTypeDescription
qrequiredquerystring
mailboxquerystringMailbox id, address, or alias address.
folderqueryinbox | sent | drafts | archive | spam | trash | starred | snoozed | allDefault inbox (all = everything but trash and spam).
cursorquerystringnextCursor from the previous page.
limitqueryinteger
curl
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

NameTypeDescription
threadsrequiredThread[]
nextCursorrequiredstringPass as cursor for the next page.
unreadrequiredintegerUnread conversations matching the filters (all pages).

Send an email

POST/api/v1/sendIdempotency-KeySend mail

Send 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)

NameTypeDescription
fromrequiredstring (email)A mailbox (not an alias) the key can send as.
torequiredstring or string[]Comma-separated string or array of addresses.
ccstring or string[]
bccstring or string[]
subjectstring
textstringPlain-text body. text or html is required; together at most 1 MB.
htmlstringHTML body (scripts, handlers and javascript: URLs are stripped).
idempotencyKeystringAlternative to the Idempotency-Key header.
curl
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

NameTypeDescription
okrequiredboolean
idrequiredstringRFC Message-ID (kept for compatibility; same as messageId).
messageIdrequiredstringRFC 5322 Message-ID of the sent message.
emailIdrequiredstringInternal id of the stored Sent copy.
threadIdrequiredstring
mailboxIdrequiredstring
fromrequiredstring (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.

NameTypeDescription
idrequiredstring
addressrequiredstring (email)
displayNamerequiredstring
aliasesrequiredstring (email)[]Aliases that deliver into this mailbox. Replies may be sent from an alias.
sourcerequiredowner | admin | grantHow the key reaches it: the key user's own mailbox, as a workspace admin, or shared with them.
accessrequiredobject
unreadrequiredintegerUnread conversations in the inbox.

Thread

A conversation, as listed.

NameTypeDescription
idrequiredstring
mailboxIdrequiredstring
mailboxrequiredstring (email)The mailbox's address.
receivedOnrequiredstring (email)Where the conversation arrived (an alias, if so) or the address it was sent from.
subjectrequiredstring
snippetrequiredstring
fromrequiredEmailAddressThe other party: latest inbound sender, else the first recipient of the latest sent message.
participantsrequiredEmailAddress[]From/To/Cc of every message — never Bcc.
folderrequiredinbox | sent | drafts | archive | spam | trash
unreadrequiredboolean
starredrequiredboolean
labelsrequiredstring[]
lastMessageAtrequiredstring (date-time)
snoozedUntilstring (date-time)
hasAttachmentsrequiredboolean
messageCountrequiredinteger

Message

One message, with its bodies and attachment list.

NameTypeDescription
idrequiredstringInternal message id (webhooks: emailId).
threadIdrequiredstring
mailboxIdrequiredstring
directionrequiredinbound | outbound
messageIdstringRFC 5322 Message-ID header, e.g. <abc@acme.com>.
fromrequiredEmailAddress
torequiredEmailAddress[]
ccrequiredEmailAddress[]
bccEmailAddress[]Present only when the key's user sent the message or was Bcc'd on it.
subjectrequiredstring
daterequiredstring (date-time)
readrequiredboolean
textrequiredstringPlain-text body.
htmlstringHTML body with scripts and event handlers removed (omitted with ?html=false).
attachmentsrequiredAttachment[]
authobjectInbound SPF / DKIM / DMARC verdicts.
deliveredAtstring (date-time)When the recipient's server accepted an outbound message.

Attachment

A file on a message.

NameTypeDescription
idrequiredstring
filenamerequiredstring
contentTyperequiredstring
sizerequiredintegerBytes.
inlinerequiredbooleanEmbedded in the HTML body via cid: rather than attached as a file.
contentIdstringMatches cid: references in the HTML body.
urlrequiredstringDownload path (GET, same bearer key).

EmailAddress

A person or address.

NameTypeDescription
emailrequiredstring (email)
namestring

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?