[SuperMailOS](/)

[Product](/#product)[Customers](/#customers)[Agents](/#agents)[Pricing](/pricing)[Compare](/compare)[Tools](/tools)[Docs](/docs)

[Log in](/login)[Start free](/signup)

Docs/API reference

Getting started

-   [Overview](/docs)
-   [Set up your domain’s DNS](/docs/dns-setup)
-   [Connect a mail app (IMAP/SMTP)](/docs/connect-mail-app)
-   [Migrate your existing mail](/docs/migrate)

Running your mail

-   [Deliverability & inbox placement](/docs/deliverability)
-   [Security: 2FA & sessions](/docs/security)

Developers

-   [API reference](/docs/api)
-   [Webhooks](/docs/webhooks)
-   [Connect your AI (MCP)](/docs/mcp)

Resources

-   [Guides](/learn)
-   [Email checker](/tools/email-checker)
-   [System status](/status)
-   [Changelog](/changelog)

Getting started

-   [Overview](/docs)
-   [Set up your domain’s DNS](/docs/dns-setup)
-   [Connect a mail app (IMAP/SMTP)](/docs/connect-mail-app)
-   [Migrate your existing mail](/docs/migrate)

Running your mail

-   [Deliverability & inbox placement](/docs/deliverability)
-   [Security: 2FA & sessions](/docs/security)

Developers

-   [API reference](/docs/api)
-   [Webhooks](/docs/webhooks)
-   [Connect your AI (MCP)](/docs/mcp)

Resources

-   [Guides](/learn)
-   [Email checker](/tools/email-checker)
-   [System status](/status)
-   [Changelog](/changelog)

[Home](/)/[Docs](/docs)/API reference

Developers

Copy page[.md](/docs/api.md "Open this page as Markdown")

# API reference

Updated 9 Oct 2026

On this page 10 sections

-   [Quick start](#quick-start)
-   [Authentication](#authentication)
-   [Scopes](#scopes)
-   [Rate limits](#rate-limits)
-   [Idempotency](#idempotency)
-   [Pagination](#pagination)
-   [Errors](#errors)
-   [Endpoints](#endpoints)
-   [List mailboxes](#list-mailboxes)
-   [List threads](#list-threads)
-   [Get a thread](#get-a-thread)
-   [Update a thread](#update-a-thread)
-   [Reply to a thread](#reply-to-a-thread)
-   [Draft a reply](#draft-a-reply)
-   [List recent messages](#list-recent-messages)
-   [Get a message](#get-a-message)
-   [Download a message as .eml](#download-a-message-as-eml)
-   [Download an attachment](#download-an-attachment)
-   [Search mail](#search-mail)
-   [Send an email](#send-an-email)
-   [Objects](#objects)
-   [Mailbox](#mailbox)
-   [Thread](#thread)
-   [Message](#message)
-   [Attachment](#attachment)
-   [EmailAddress](#emailaddress)
-   [Machine-readable](#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](https://supermailos.com/api/v1/openapi.json), 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](/docs/mcp). To get a signed callback when mail arrives, see [webhooks](/docs/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](#search-mail), read a conversation with [get a thread](#get-a-thread), and when you are ready, [send one](#send-an-email).

## 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:** `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.

| 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](#list-mailboxes) | `GET /mailboxes` | read |
| [List threads](#list-threads) | `GET /threads` | read |
| [Get a thread](#get-a-thread) | `GET /threads/{id}` | read |
| [Update a thread](#update-a-thread) | `PATCH /threads/{id}` | write |
| [Reply to a thread](#reply-to-a-thread) | `POST /threads/{id}/reply` | send |
| [Draft a reply](#draft-a-reply) | `POST /threads/{id}/draft` | write |
| [List recent messages](#list-recent-messages) | `GET /messages` | read |
| [Get a message](#get-a-message) | `GET /messages/{id}` | read |
| [Download a message as .eml](#download-a-message-as-eml) | `GET /messages/{id}/raw` | read |
| [Download an attachment](#download-an-attachment) | `GET /attachments/{id}` | read |
| [Search mail](#search-mail) | `GET /search` | read |
| [Send an email](#send-an-email) | `POST /send` | send |

### List mailboxes

GET`/api/v1/mailboxes`Read 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](#errors)).

Response

| Name | Type | Description |
| --- | --- | --- |
| mailboxesrequired | Mailbox\[\] |  |

### List threads

GET`/api/v1/threads`Read mail

Conversations 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

```
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](#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

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

One 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

```
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](#errors)).

Response · ThreadDetail

| Name | Type | Description |
| --- | --- | --- |
| threadrequired | Thread |  |
| messagesrequired | Message\[\] | Oldest first. |
| permissionsrequired | object |  |

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

| 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

```
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](#errors)).

Response

| Name | Type | Description |
| --- | --- | --- |
| threadrequired | Thread |  |

### Reply to a thread

POST`/api/v1/threads/{id}/reply`Idempotency-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

| 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

```
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](#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

POST`/api/v1/threads/{id}/draft`Manage 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

| 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

```
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](#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

GET`/api/v1/messages`Read mail

Recent 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

```
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](#errors)).

Response

| Name | Type | Description |
| --- | --- | --- |
| messagesrequired | MessageSummary\[\] |  |
| countrequired | integer |  |

### Get a message

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

One message by id — the `emailId` in webhook payloads.

Parameters

| Name | Type | Description |
| --- | --- | --- |
| idrequiredpath | string |  |
| htmlquery | boolean | `false` 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](#errors)).

Response

| Name | Type | Description |
| --- | --- | --- |
| messagerequired | Message |  |

### Download a message as .eml

GET`/api/v1/messages/{id}/raw`Read 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

| Name | Type | Description |
| --- | --- | --- |
| idrequiredpath | string |  |

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](#errors)).

### Download an attachment

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

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

Parameters

| Name | Type | Description |
| --- | --- | --- |
| idrequiredpath | string |  |

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](#errors)).

### Search mail

GET`/api/v1/search`Read 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

| 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

```
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](#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

POST`/api/v1/send`Idempotency-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)

| 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

```
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](#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](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](/.well-known/agent-skills/supermailos/SKILL.md), and the MCP endpoint lives at `https://supermailos.com/api/mcp`.

Next: [webhooks](/docs/webhooks) or [connect your AI over MCP](/docs/mcp).

Was this page helpful?

YesNo

[← PreviousSecurity: 2FA & sessions](/docs/security)[Next →Webhooks](/docs/webhooks)

Stuck?

Write to [hello@supermailos.com](mailto:hello@supermailos.com). A person who can fix it reads every message.

[SuperMailOS](/)

Every domain you own, in one beautiful inbox. From $3 a mailbox.

Product

-   [Tour](/#product)
-   [Pricing](/pricing)
-   [Features](/features)
-   [iPhone app (soon)](/#iphone)
-   [Agents & API](/#agents)
-   [Changelog](/changelog)

Compare

-   [Google Workspace](/compare/google-workspace)
-   [Microsoft 365](/alternatives/microsoft-365-alternative)
-   [Zoho Mail](/compare/zoho)
-   [Fastmail](/compare/fastmail)
-   [All comparisons](/compare)

Resources

-   [Docs](/docs)
-   [Developers](/docs/api)
-   [Learn](/learn)
-   [Use cases](/use-cases)
-   [Free tools](/tools)
-   [Glossary](/glossary)
-   [Status](/status)

Company

-   [About](/about)
-   [Contact](/contact)
-   [Brand](/brand)
-   [Privacy](/privacy)
-   [Terms](/terms)
-   [Refunds](/refund)
-   [Security](/security-policy)

© 2026 SuperMailOSEvery domain · one inbox