---
title: "Email Testing API"
description: "REST API for domains, accounts, mailboxes, messages, and tokens. Authenticate with an X-API-KEY header; stream new messages over SSE via Mercure."
canonical_url: "https://smtp.dev/docs/api/"
last_updated: "2026-08-16T22:38:03.808Z"
---

## General information

The API is described by an [OpenAPI v3 document](https://api.smtp.dev/docs.jsonld), and you can try every endpoint in the [interactive reference](https://api.smtp.dev).

<note>

Usage of our service for illegal activity is strictly prohibited.

</note>

## Integrations

Built something on the API - an SDK, a test helper, a CI action? Let us know and we'll list it here.

## API Documentation

**Base URL:**

```text
https://api.smtp.dev
```

### Error handling

Successful requests return 200, 201, or 204. Errors return a 4xx code:

**400 Bad Request:** The payload is missing or malformed.

**401 Unauthorized:** The `X-API-KEY` header is missing or the key is invalid.

**404 Not Found:** The resource doesn't exist - check the id and the path.

**405 Method Not Allowed:** Wrong method for the path, e.g. `PUT /tokens` or `POST /domains/{id}`.

**422 Unprocessable Entity:** The payload didn't validate - a username too short, a domain that isn't yours.

**429 Too Many Requests:** Rate limit exceeded. Wait for the limit window to reset before retrying.

### Authentication

All API requests require authentication using an API key.

To authenticate, add the following header to each request:

```text
X-API-KEY: smtplabs_your_api_key_here
```

> **How to get it?**

If you don't have an API key yet, create a new one on the [API Keys](/tokens) page.

**Example request with authentication:**

```bash
curl -X GET "https://api.smtp.dev/accounts" \
  -H "X-API-KEY: smtplabs_your_api_key_here" \
  -H "Accept: application/json"
```

## Rate Limiting

The API has the following rate limits:

- **4096 requests per minute** per authenticated user (sliding window)
- When rate limited, the API will return a `429 Too Many Requests` status code
- Responses include rate limit headers: `RateLimit-Limit`, `RateLimit-Remaining`, and `RateLimit-Reset`

If you receive a 429 response, you should:

1. Check the `RateLimit-Reset` header to know when you can retry
2. Implement exponential backoff in your requests
3. Add a delay between batches of requests
4. Consider optimizing your code to make fewer API calls if possible

## Domain

### List Domains

```text
GET /domains
```

You have to use this when [creating an account](#create-an-account), to retrieve the domain.

Returns a list of domains.

**Body:**

*None*

**Params:**

<field-group>
<field name="domain" type="string">

Filter by domain name (partial match)

</field>

<field name="isActive" type="boolean">

Filter by active status

</field>

<field name="page" type="int">

The collection page number

</field>
</field-group>

**curl Example:**

```bash
curl -X GET "https://api.smtp.dev/domains?isActive=true&page=1" \
  -H "X-API-KEY: smtplabs_your_api_key_here" \
  -H "Accept: application/json"
```

**Response:**

```json
{
  "member": [
    {
      "id": "string",
      "domain": "string",
      "isActive": true,
      "createdAt": "2025-04-01T00:00:00.000Z",
      "updatedAt": "2025-04-01T00:00:00.000Z"
    }
  ],
  "view": {
    "id": "string",
    "type": "string",
    "first": "string",
    "last": "string",
    "previous": "string",
    "next": "string"
  },
  "search": {
    "type": "string",
    "template": "string",
    "variableRepresentation": "string",
    "mapping": [
      {
        "type": "string",
        "variable": "string",
        "property": "string",
        "required": true
      }
    ]
  }
}
```

### Create a Domain

```text
POST /domains
```

Creates a Domain resource.

**Body:**

<field-group>
<field name="domain" type="string" :required="true">

Domain name. Example: `example.com`

</field>

<field name="isActive" type="boolean">

Domain activation status

</field>
</field-group>

**Params:**

*None*

**curl Example:**

```bash
curl -X POST "https://api.smtp.dev/domains" \
  -H "X-API-KEY: smtplabs_your_api_key_here" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "domain": "example.com",
    "isActive": true
  }'
```

**Response:**

```json
{
  "@context": "string",
  "@id": "string",
  "@type": "string",
  "id": "string",
  "domain": "example.com",
  "isActive": true,
  "createdAt": "2025-04-01T00:00:00.000Z",
  "updatedAt": "2025-04-01T00:00:00.000Z"
}
```

### Get a Domain

```text
GET /domains/{id}
```

Retrieves a Domain resource by its id.

**Body:**

*None*

**Params:**

<field-group>
<field name="id" type="string" :required="true">

The domain identifier

</field>
</field-group>

**curl Example:**

```bash
curl -X GET "https://api.smtp.dev/domains/{id}" \
  -H "X-API-KEY: smtplabs_your_api_key_here" \
  -H "Accept: application/json"
```

**Response:**

```json
{
  "@context": "string",
  "@id": "string",
  "@type": "string",
  "id": "string",
  "domain": "example.com",
  "isActive": true,
  "createdAt": "2025-04-01T00:00:00.000Z",
  "updatedAt": "2025-04-01T00:00:00.000Z"
}
```

### Delete a Domain

```text
DELETE /domains/{id}
```

Deletes the Domain resource.

**Body:**

*None*

**Params:**

<field-group>
<field name="id" type="string" :required="true">

The domain identifier

</field>
</field-group>

**curl Example:**

```bash
curl -X DELETE "https://api.smtp.dev/domains/{id}" \
  -H "X-API-KEY: smtplabs_your_api_key_here"
```

**Response:**

*None*

**(Returns status code 204 if successful.)**

### Update a Domain

```text
PATCH /domains/{id}
```

Updates the Domain resource.

**Body:**

<field-group>
<field name="isActive" type="boolean" :required="true">

Domain activation status

</field>
</field-group>

**Params:**

<field-group>
<field name="id" type="string" :required="true">

The domain identifier

</field>
</field-group>

**curl Example:**

```bash
curl -X PATCH "https://api.smtp.dev/domains/{id}" \
  -H "X-API-KEY: smtplabs_your_api_key_here" \
  -H "Content-Type: application/merge-patch+json" \
  -H "Accept: application/json" \
  -d '{
    "isActive": true
  }'
```

**Response:**

```json
{
  "@context": "string",
  "@id": "string",
  "@type": "string",
  "id": "string",
  "domain": "example.com",
  "isActive": true,
  "createdAt": "2025-04-01T00:00:00.000Z",
  "updatedAt": "2025-04-01T00:00:00.000Z"
}
```

---

## Account

### List Accounts

```text
GET /accounts
```

Retrieves the collection of Account resources.

**Body:**

*None*

**Params:**

<field-group>
<field name="address" type="string">

Filter by account address

</field>

<field name="isActive" type="boolean">

Filter by active status

</field>

<field name="page" type="int">

The collection page number

</field>
</field-group>

**curl Example:**

```bash
curl -X GET "https://api.smtp.dev/accounts?address=user@example.com&isActive=true&page=1" \
  -H "X-API-KEY: smtplabs_your_api_key_here" \
  -H "Accept: application/json"
```

**Response**

```json
{
  "member": [
    {
      "id": "string",
      "address": "user@example.com",
      "quota": 0,
      "used": 0,
      "isActive": true,
      "isDeleted": false,
      "createdAt": "2025-04-01T00:00:00.000Z",
      "updatedAt": "2025-04-01T00:00:00.000Z"
    }
  ],
  "view": {
    "id": "string",
    "type": "string",
    "first": "string",
    "last": "string",
    "previous": "string",
    "next": "string"
  },
  "search": {
    "type": "string",
    "template": "string",
    "variableRepresentation": "string",
    "mapping": [
      {
        "type": "string",
        "variable": "string",
        "property": "string",
        "required": true
      }
    ]
  }
}
```

### Create an Account

```text
POST /accounts
```

Creates an Account resource

**Body:**

<field-group>
<field name="address" type="string" :required="true">

Account's address. Example: [user@example.com](mailto:user@example.com)

</field>

<field name="password" type="string" :required="true">

Account's password.

</field>

<field name="isActive" type="boolean">

Account active status. Defaults to `true`.

</field>
</field-group>

**Params**

*None*

**curl Example:**

```bash
curl -X POST "https://api.smtp.dev/accounts" \
  -H "X-API-KEY: smtplabs_your_api_key_here" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "address": "user@example.com",
    "password": "SecurePassword123"
  }'
```

**Response**

```json
{
  "@context": "string",
  "@id": "string",
  "@type": "string",
  "id": "string",
  "address": "user@example.com",
  "quota": 0,
  "used": 0,
  "isActive": true,
  "isDeleted": false,
  "mailboxes": [
    {
      "id": "string",
      "path": "INBOX",
      "isSystem": true,
      "totalMessages": 0,
      "totalUnreadMessages": 0,
      "createdAt": "2025-04-01T00:00:00.000Z",
      "updatedAt": "2025-04-01T00:00:00.000Z"
    }
  ],
  "createdAt": "2025-04-01T00:00:00.000Z",
  "updatedAt": "2025-04-01T00:00:00.000Z"
}
```

When an account is created, 5 default mailboxes are automatically created: **INBOX**, **Sent**, **Trash**, **Drafts**, and **Junk**. The address can receive mail immediately.

### Get an Account

```text
GET /accounts/{id}
```

Get an Account resource by its id

**Body:**

*None*

**Params:**

<field-group>
<field name="id" type="string" :required="true">

The account identifier

</field>
</field-group>

**curl Example:**

```bash
curl -X GET "https://api.smtp.dev/accounts/{accountId}" \
  -H "X-API-KEY: smtplabs_your_api_key_here" \
  -H "Accept: application/json"
```

**Response:**

```json
{
  "@context": "string",
  "@id": "string",
  "@type": "string",
  "id": "string",
  "address": "user@example.com",
  "quota": 0,
  "used": 0,
  "isActive": true,
  "isDeleted": false,
  "mailboxes": [
    {
      "id": "string",
      "path": "INBOX",
      "isSystem": true,
      "totalMessages": 0,
      "totalUnreadMessages": 0,
      "createdAt": "2025-04-01T00:00:00.000Z",
      "updatedAt": "2025-04-01T00:00:00.000Z"
    }
  ],
  "createdAt": "2025-04-01T00:00:00.000Z",
  "updatedAt": "2025-04-01T00:00:00.000Z"
}
```

### Delete an Account

```text
DELETE /accounts/{id}
```

Deletes the Account resource.

<warning>

Deleted accounts can't be restored. Their mailboxes and messages are gone too.

</warning>

**Body:**

*None*

**Params:**

<field-group>
<field name="id" type="string" :required="true">

The account identifier

</field>
</field-group>

**curl Example:**

```bash
curl -X DELETE "https://api.smtp.dev/accounts/{accountId}" \
  -H "X-API-KEY: smtplabs_your_api_key_here"
```

**Response:**

*None*

**(Returns status code 204 if successful.)**

### Update an Account

```text
PATCH /accounts/{id}
```

Updates the Account resource.

**Body:**

<field-group>
<field name="password" type="string">

Account's password.

</field>

<field name="isActive" type="boolean">

Account active status

</field>
</field-group>

**Params:**

<field-group>
<field name="id" type="string" :required="true">

The account identifier

</field>
</field-group>

**curl Example:**

```bash
curl -X PATCH "https://api.smtp.dev/accounts/{accountId}" \
  -H "X-API-KEY: smtplabs_your_api_key_here" \
  -H "Content-Type: application/merge-patch+json" \
  -H "Accept: application/json" \
  -d '{
    "password": "NewSecurePassword456",
    "isActive": true
  }'
```

**Response:**

```json
{
  "@context": "string",
  "@id": "string",
  "@type": "string",
  "id": "string",
  "address": "user@example.com",
  "quota": 0,
  "used": 0,
  "isActive": true,
  "isDeleted": false,
  "mailboxes": [],
  "createdAt": "2025-04-01T00:00:00.000Z",
  "updatedAt": "2025-04-01T00:00:00.000Z"
}
```

## Mailboxes

### List Mailboxes

```text
GET /accounts/{accountId}/mailboxes
```

Gets all the Mailbox resources of a given account.

**Body:**

*None*

**Params:**

<field-group>
<field name="accountId" type="string" :required="true">

The account identifier.

</field>

<field name="page" type="int">

The collection page number

</field>
</field-group>

**curl Example:**

```bash
curl -X GET "https://api.smtp.dev/accounts/{accountId}/mailboxes?page=1" \
  -H "X-API-KEY: smtplabs_your_api_key_here" \
  -H "Accept: application/json"
```

**Response:**

```json
{
  "member": [
    {
      "@context": "string",
      "@id": "string",
      "@type": "string",
      "id": "string",
      "path": "string",
      "isSystem": false,
      "totalMessages": 0,
      "totalUnreadMessages": 0,
      "account": "string",
      "createdAt": "2025-04-07T05:53:58.747Z",
      "updatedAt": "2025-04-07T05:53:58.748Z"
    }
  ],
  "view": {
    "@id": "string",
    "type": "string",
    "first": "string",
    "last": "string",
    "previous": "string",
    "next": "string"
  },
  "search": {
    "@type": "string",
    "template": "string",
    "variableRepresentation": "string",
    "mapping": [
      {
        "@type": "string",
        "variable": "string",
        "property": "string",
        "required": true
      }
    ]
  }
}
```

### Create a Mailbox

```text
POST /accounts/{accountId}/mailboxes
```

Creates a Mailbox resource for a given account.

**Body:**

<field-group>
<field name="path" type="string" :required="true">

Mailbox path. Example: `Promotions`

</field>
</field-group>

**Params**

<field-group>
<field name="accountId" type="string" :required="true">

The account identifier.

</field>
</field-group>

**curl Example:**

```bash
curl -X POST "https://api.smtp.dev/accounts/{accountId}/mailboxes" \
  -H "X-API-KEY: smtplabs_your_api_key_here" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "path": "Promotions"
  }'
```

**Response**

```json
{
  "@context": "string",
  "@id": "string",
  "@type": "string",
  "id": "string",
  "path": "Promotions",
  "isSystem": false,
  "totalMessages": 0,
  "totalUnreadMessages": 0,
  "account": "string",
  "createdAt": "2025-04-01T00:00:00.000Z",
  "updatedAt": "2025-04-01T00:00:00.000Z"
}
```

### Get a Mailbox

```text
GET /accounts/{accountId}/mailboxes/{id}
```

Retrieves a Mailbox resource by its id.

**Body:**

*None*

**Params:**

<field-group>
<field name="accountId" type="string" :required="true">

The account identifier.

</field>

<field name="id" type="string" :required="true">

The mailbox identifier.

</field>
</field-group>

**curl Example:**

```bash
curl -X GET "https://api.smtp.dev/accounts/{accountId}/mailboxes/{mailboxId}" \
  -H "X-API-KEY: smtplabs_your_api_key_here" \
  -H "Accept: application/json"
```

**Response**

```json
{
  "@context": "string",
  "@id": "string",
  "@type": "string",
  "id": "string",
  "path": "string",
  "isSystem": false,
  "totalMessages": 0,
  "totalUnreadMessages": 0,
  "account": "string",
  "createdAt": "2025-04-01T00:00:00.000Z",
  "updatedAt": "2025-04-01T00:00:00.000Z"
}
```

### Delete a Mailbox

```text
DELETE /accounts/{accountId}/mailboxes/{id}
```

Deletes the Mailbox resource.

**Body:**

*None*

**Params:**

<field-group>
<field name="accountId" type="string" :required="true">

The account identifier.

</field>

<field name="id" type="string" :required="true">

The mailbox identifier.

</field>
</field-group>

**curl Example:**

```bash
curl -X DELETE "https://api.smtp.dev/accounts/{accountId}/mailboxes/{mailboxId}" \
  -H "X-API-KEY: smtplabs_your_api_key_here"
```

**Response:**

*None*

**(Returns status code 204 if successful.)**

### Update a Mailbox

```text
PATCH /accounts/{accountId}/mailboxes/{id}
```

Updates the Mailbox resource.

**Body:**

<field-group>
<field name="path" type="string">

Mailbox path. Example: `Primary`

</field>
</field-group>

**Params:**

<field-group>
<field name="accountId" type="string" :required="true">

The account identifier.

</field>

<field name="id" type="string" :required="true">

The mailbox identifier.

</field>
</field-group>

**curl Example:**

```bash
curl -X PATCH "https://api.smtp.dev/accounts/{accountId}/mailboxes/{mailboxId}" \
  -H "X-API-KEY: smtplabs_your_api_key_here" \
  -H "Content-Type: application/merge-patch+json" \
  -H "Accept: application/json" \
  -d '{
    "path": "Primary"
  }'
```

**Response:**

```json
{
  "@context": "string",
  "@id": "string",
  "@type": "string",
  "id": "string",
  "path": "Primary",
  "isSystem": false,
  "totalMessages": 0,
  "totalUnreadMessages": 0,
  "account": "string",
  "createdAt": "2025-04-01T00:00:00.000Z",
  "updatedAt": "2025-04-01T00:00:00.000Z"
}
```

## Messages

### List Messages

```text
GET /accounts/{accountId}/mailboxes/{mailboxId}/messages
```

Retrieves the collection of Message resources for a given mailbox.

**Body:**

*None*

**Params:**

<field-group>
<field name="accountId" type="string" :required="true">

The account identifier.

</field>

<field name="mailboxId" type="string" :required="true">

The mailbox identifier.

</field>

<field name="page" type="int">

The collection page number

</field>
</field-group>

**curl Example:**

```bash
curl -X GET "https://api.smtp.dev/accounts/{accountId}/mailboxes/{mailboxId}/messages?page=1" \
  -H "X-API-KEY: smtplabs_your_api_key_here" \
  -H "Accept: application/json"
```

**Response:**

```json
{
  "member": [
    {
      "id": "string",
      "msgid": "string",
      "from": {
        "address": "string",
        "name": "string"
      },
      "to": [
        {
          "address": "string",
          "name": "string"
        }
      ],
      "cc": [
        {
          "address": "string",
          "name": "string"
        }
      ],
      "bcc": [
        {
          "address": "string",
          "name": "string"
        }
      ],
      "replyTo": [
        {
          "address": "string",
          "name": "string"
        }
      ],
      "date": "2025-04-01T00:00:00.000Z",
      "subject": "string",
      "intro": "string",
      "text": "string",
      "html": {},
      "isRead": false,
      "isFlagged": false,
      "isDeleted": false,
      "hasAttachments": false,
      "size": 0,
      "autoDeleteEnabled": false,
      "expiresAt": null,
      "flags": [],
      "verifications": {},
      "headers": [],
      "attachments": [],
      "downloadUrl": "string",
      "sourceUrl": "string",
      "createdAt": "2025-04-01T00:00:00.000Z",
      "updatedAt": "2025-04-01T00:00:00.000Z"
    }
  ],
  "view": {
    "id": "string",
    "type": "string",
    "first": "string",
    "last": "string",
    "previous": "string",
    "next": "string"
  }
}
```

Messages are ordered by `createdAt` descending (newest first). There are up to 30 messages per page. Use the `view` links (`next`, `previous`) to navigate between pages.

### Get a Message

```text
GET /accounts/{accountId}/mailboxes/{mailboxId}/messages/{id}
```

Retrieves a Message resource by its id (this has way more information than a message retrieved with [List Messages](#list-messages))

**Body:**

*None*

**Params:**

<field-group>
<field name="accountId" type="string" :required="true">

The account identifier.

</field>

<field name="mailboxId" type="string" :required="true">

The mailbox identifier.

</field>

<field name="id" type="string" :required="true">

The message identifier.

</field>
</field-group>

**curl Example:**

```bash
curl -X GET "https://api.smtp.dev/accounts/{accountId}/mailboxes/{mailboxId}/messages/{id}" \
  -H "X-API-KEY: smtplabs_your_api_key_here" \
  -H "Accept: application/json"
```

**Response:**

```json
{
  "id": "string",
  "msgid": "string",
  "from": {
    "address": "string",
    "name": "string"
  },
  "to": [
    {
      "address": "string",
      "name": "string"
    }
  ],
  "cc": [
    {
      "address": "string",
      "name": "string"
    }
  ],
  "bcc": [
    {
      "address": "string",
      "name": "string"
    }
  ],
  "replyTo": [
    {
      "address": "string",
      "name": "string"
    }
  ],
  "date": "2025-04-01T00:00:00.000Z",
  "subject": "string",
  "intro": "string",
  "text": "string",
  "html": {},
  "isRead": false,
  "isFlagged": false,
  "isDeleted": false,
  "hasAttachments": false,
  "size": 0,
  "autoDeleteEnabled": false,
  "expiresAt": null,
  "flags": [],
  "verifications": {},
  "threadId": "string",
  "headers": [],
  "attachments": [
    {
      "id": "string",
      "filename": "string",
      "contentType": "string",
      "disposition": "string",
      "size": 0,
      "downloadUrl": "string"
    }
  ],
  "downloadUrl": "string",
  "sourceUrl": "string",
  "createdAt": "2025-04-01T00:00:00.000Z",
  "updatedAt": "2025-04-01T00:00:00.000Z"
}
```

### Delete a Message

```text
DELETE /accounts/{accountId}/mailboxes/{mailboxId}/messages/{id}
```

Deletes the `Message` resource.

**Body:**

*None*

**Params:**

<field-group>
<field name="accountId" type="string" :required="true">

The account identifier.

</field>

<field name="mailboxId" type="string" :required="true">

The mailbox identifier.

</field>

<field name="id" type="string" :required="true">

The message identifier.

</field>
</field-group>

**curl Example:**

```bash
curl -X DELETE "https://api.smtp.dev/accounts/{accountId}/mailboxes/{mailboxId}/messages/{id}" \
  -H "X-API-KEY: smtplabs_your_api_key_here"
```

**Response:**

*None*

**(Returns status code 204 if successful.)**

### Update a Message

```text
PATCH /accounts/{accountId}/mailboxes/{mailboxId}/messages/{id}
```

Updates the Message resource. For example, mark a message as read or flagged.

**Body:**

<field-group>
<field name="isRead" type="boolean">

Set to true to mark the message as read

</field>

<field name="isFlagged" type="boolean">

Set to true to mark the message as flagged

</field>

<field name="autoDeleteEnabled" type="boolean">

Enable/disable auto-deletion of the message

</field>

<field name="expiresAt" type="string">

ISO date-time when the message should expire

</field>
</field-group>

**Params:**

<field-group>
<field name="accountId" type="string" :required="true">

The account identifier

</field>

<field name="mailboxId" type="string" :required="true">

The mailbox identifier

</field>

<field name="id" type="string" :required="true">

The message identifier

</field>
</field-group>

**curl Example:**

```bash
curl -X PATCH "https://api.smtp.dev/accounts/{accountId}/mailboxes/{mailboxId}/messages/{id}" \
  -H "X-API-KEY: smtplabs_your_api_key_here" \
  -H "Content-Type: application/merge-patch+json" \
  -H "Accept: application/json" \
  -d '{
    "isRead": true,
    "isFlagged": true,
    "autoDeleteEnabled": true,
    "expiresAt": "2025-05-01T00:00:00.000Z"
  }'
```

**Response:**

The response is the full updated Message object (same schema as [Get a Message](#get-a-message)).

### Get a Message Source

```text
GET /accounts/{accountId}/mailboxes/{mailboxId}/messages/{id}/source
```

Gets a Message's Source resource: the raw message as it arrived over SMTP - headers, MIME boundaries, encoded body parts.

**Body:**

*None*

**Params:**

<field-group>
<field name="accountId" type="string" :required="true">

The account identifier.

</field>

<field name="mailboxId" type="string" :required="true">

The mailbox identifier.

</field>

<field name="id" type="string" :required="true">

The message identifier.

</field>
</field-group>

**curl Example:**

```bash
curl -X GET "https://api.smtp.dev/accounts/{accountId}/mailboxes/{mailboxId}/messages/{id}/source" \
  -H "X-API-KEY: smtplabs_your_api_key_here" \
  -H "Accept: application/json"
```

**Response:**

```json
{
  "@context": "string",
  "@id": "string",
  "@type": "string",
  "raw": "string"
}
```

The `raw` field contains the full RFC 822 message source. You can also use the [Download a Message](#download-a-message) endpoint to download it as a `.eml` file.

### Download a Message Attachment

```text
GET /accounts/{accountId}/mailboxes/{mailboxId}/messages/{id}/attachment/{attachmentId}
```

Gets a Message's attachment.

**Body:**

*None*

**Params:**

<field-group>
<field name="accountId" type="string" :required="true">

The account identifier.

</field>

<field name="mailboxId" type="string" :required="true">

The mailbox identifier.

</field>

<field name="id" type="string" :required="true">

The message identifier.

</field>

<field name="attachmentId" type="string" :required="true">

The attachment identifier.

</field>
</field-group>

**curl Example:**

```bash
curl -X GET "https://api.smtp.dev/accounts/{accountId}/mailboxes/{mailboxId}/messages/{id}/attachment/{attachmentId}" \
  -H "X-API-KEY: smtplabs_your_api_key_here" \
  -H "Accept: application/json" \
  -o welcome.pdf
```

**Response:**

Binary file content (saved to the specified output file).

For JSON metadata about the attachment:

```bash
curl -X GET "https://api.smtp.dev/accounts/{accountId}/mailboxes/{mailboxId}/messages/{id}" \
  -H "X-API-KEY: smtplabs_your_api_key_here" \
  -H "Accept: application/json"
```

```json
{
  "id": "string",
  "filename": "string",
  "contentType": "string",
  "disposition": "string",
  "transferEncoding": "string",
  "related": true,
  "size": 0,
  "downloadUrl": "string"
}
```

### Download a Message

```text
GET /accounts/{accountId}/mailboxes/{mailboxId}/messages/{id}/download
```

Downloads a Message resource.

**Body:**

*None*

**Params:**

<field-group>
<field name="accountId" type="string" :required="true">

The account identifier.

</field>

<field name="mailboxId" type="string" :required="true">

The mailbox identifier.

</field>

<field name="id" type="string" :required="true">

The message identifier.

</field>
</field-group>

**curl Example:**

```bash
curl -X GET "https://api.smtp.dev/accounts/{accountId}/mailboxes/{mailboxId}/messages/{id}/download" \
  -H "X-API-KEY: smtplabs_your_api_key_here" \
  -o message.eml
```

**Response:**

Binary file content (saved to the specified output file).

**(Returns status code 200 if successful.)**

### Move a Message

```text
PUT /accounts/{accountId}/mailboxes/{mailboxId}/messages/{id}/move
```

Moves a Message resource to a different mailbox.

**Body:**

<field-group>
<field name="mailbox" type="string" :required="true">

The target mailbox identifier

</field>
</field-group>

**Params**

<field-group>
<field name="accountId" type="string" :required="true">

The account identifier.

</field>

<field name="mailboxId" type="string" :required="true">

The mailbox identifier.

</field>

<field name="id" type="string" :required="true">

The message identifier.

</field>
</field-group>

**curl Example:**

```bash
curl -X PUT "https://api.smtp.dev/accounts/{accountId}/mailboxes/{mailboxId}/messages/{id}/move" \
  -H "X-API-KEY: smtplabs_your_api_key_here" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "mailbox": "{mailboxId}"
  }'
```

**Response:**

The response is the full Message object in its new mailbox (same schema as [Get a Message](#get-a-message)).

### Send a Message

```text
POST /accounts/{accountId}/messages/send
```

Sends an email message from the specified account.

<note>

The `from` address is automatically set to the account's address, regardless of what you provide in the request body.

</note>

**Body:**

<field-group>
<field name="from" type="object" :required="true">

Sender information. Must contain `address` (string) and optionally `name` (string). The `address` will be overwritten with the account's address.

</field>

<field name="to" type="array" :required="true">

Array of recipient objects, each with `address` (string, required) and `name` (string, optional). At least one recipient is required.

</field>

<field name="cc" type="array">

Array of CC recipient objects, each with `address` and `name`.

</field>

<field name="bcc" type="array">

Array of BCC recipient objects, each with `address` and `name`.

</field>

<field name="replyTo" type="array">

Array of Reply-To recipient objects, each with `address` and `name`.

</field>

<field name="subject" type="string">

Email subject line.

</field>

<field name="text" type="string">

Plain text body of the email.

</field>

<field name="html" type="string">

HTML body of the email.

</field>

<field name="headers" type="array">

Array of custom header objects, each with `name` (string) and `value` (string).

</field>

<field name="attachments" type="array">

Array of attachment objects. See below.

</field>
</field-group>

**Attachment object:**

<field-group>
<field name="content" type="string" :required="true">

Base64-encoded file content.

</field>

<field name="filename" type="string" :required="true">

Attachment filename.

</field>

<field name="contentType" type="string" :required="true">

MIME type. Example: `application/pdf`, `image/png`.

</field>

<field name="disposition" type="string">

Either `attachment` (default) or `inline`.

</field>

<field name="cid" type="string">

Content-ID for inline attachments.

</field>
</field-group>

<warning>

At least one of `text`, `html`, or `attachments` must be provided.

</warning>

**Params:**

<field-group>
<field name="accountId" type="string" :required="true">

The account identifier.

</field>
</field-group>

**curl Example:**

```bash
curl -X POST "https://api.smtp.dev/accounts/{accountId}/messages/send" \
  -H "X-API-KEY: smtplabs_your_api_key_here" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "from": {
      "address": "user@example.com",
      "name": "John Doe"
    },
    "to": [
      {
        "address": "recipient@example.com",
        "name": "Jane Doe"
      }
    ],
    "subject": "Hello from smtp.dev",
    "text": "This is a test email.",
    "html": "<p>This is a <strong>test</strong> email.</p>"
  }'
```

**Response:**

*None*

**(Returns status code 204 if successful.)**

### Handling Attachments

<note>

Attachment downloads are raw binary - write them to disk as-is. Use `contentType` to decide how to open the file, and `disposition` to tell inline images apart from regular attachments.

</note>

## Tokens

### List Tokens

```text
GET /tokens
```

Retrieves the collection of Token resources.

**Body:**

*None*

**Params:**

<field-group>
<field name="name" type="string">

Filter by token name (partial match)

</field>

<field name="page" type="int">

The collection page number

</field>
</field-group>

**curl Example:**

```bash
curl -X GET "https://api.smtp.dev/tokens?page=1" \
  -H "X-API-KEY: smtplabs_your_api_key_here" \
  -H "Accept: application/json"
```

**Response:**

```json
{
  "member": [
    {
      "id": "string",
      "name": "My API Token",
      "description": "Used for automated testing",
      "createdAt": "2025-04-01T00:00:00.000Z",
      "updatedAt": "2025-04-01T00:00:00.000Z"
    }
  ]
}
```

### Create a Token

```text
POST /tokens
```

Creates a new API token.

**Body:**

<field-group>
<field name="name" type="string" :required="true">

A name for the token to identify its purpose

</field>

<field name="description" type="string">

Optional description of what the token is used for

</field>
</field-group>

**Params:**

*None*

**curl Example:**

```bash
curl -X POST "https://api.smtp.dev/tokens" \
  -H "X-API-KEY: smtplabs_your_api_key_here" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "name": "Integration Token",
    "description": "Used for automated testing"
  }'
```

**Response:**

```json
{
  "id": "string",
  "token": "smtplabs_abcdef123456",
  "name": "My API Token",
  "description": "Used for automated testing",
  "createdAt": "2025-04-01T00:00:00.000Z",
  "updatedAt": "2025-04-01T00:00:00.000Z"
}
```

The `token` field is **only returned once** when the token is created. It cannot be retrieved again.

<warning>

Save the token value immediately as it will only be shown once and cannot be retrieved again.

</warning>

### Get a Token

```text
GET /tokens/{id}
```

Retrieves a Token resource by its id.

**Body:**

*None*

**Params:**

<field-group>
<field name="id" type="string" :required="true">

The token identifier

</field>
</field-group>

**curl Example:**

```bash
curl -X GET "https://api.smtp.dev/tokens/{id}" \
  -H "X-API-KEY: smtplabs_your_api_key_here" \
  -H "Accept: application/json"
```

**Response:**

```json
{
  "id": "string",
  "name": "My API Token",
  "description": "Used for automated testing",
  "createdAt": "2025-04-01T00:00:00.000Z",
  "updatedAt": "2025-04-01T00:00:00.000Z"
}
```

### Delete a Token

```text
DELETE /tokens/{id}
```

Deletes the Token resource.

**Body:**

*None*

**Params:**

<field-group>
<field name="id" type="string" :required="true">

The token identifier

</field>
</field-group>

**curl Example:**

```bash
curl -X DELETE "https://api.smtp.dev/tokens/{id}" \
  -H "X-API-KEY: smtplabs_your_api_key_here"
```

**Response:**

*None*

**(Returns status code 204 if successful.)**

## Real-time Updates

### Mercure for Server-Sent Events (SSE)

Instead of traditional webhooks, we use [Mercure](https://mercure.rocks/) to provide real-time updates via Server-Sent Events (SSE).

SSE is a standard technology that allows servers to push updates to web clients over HTTP, maintaining a single connection. This is more efficient than polling the API for updates.

### Getting a Mercure Token

Before subscribing to real-time updates, you need to obtain a Mercure token:

```text
GET /mercure/token
```

**curl Example:**

```bash
curl -X GET "https://api.smtp.dev/mercure/token" \
  -H "X-API-KEY: smtplabs_your_api_key_here" \
  -H "Accept: application/json"
```

**Response:**

```json
{
  "token": "eyJhbGciOiJIUzI1NiJ9..."
}
```

### Listening for New Messages

To listen for real-time updates about new messages:

#### Base URL:

```text
https://mercure.smtp.dev/.well-known/mercure
```

#### Topic:

```text
/accounts/{id}{+path}
```

#### Example implementation (JavaScript):

The standard `EventSource` API does not support custom headers. Use a library like [`@microsoft/fetch-event-source`](https://github.com/Azure/fetch-event-source) instead:

```javascript
import { fetchEventSource } from '@microsoft/fetch-event-source'

const tokenResponse = await fetch('https://api.smtp.dev/mercure/token', {
  headers: { 'X-API-KEY': 'smtplabs_your_api_key_here' }
})
const { token } = await tokenResponse.json()

const mercureUrl = new URL('https://mercure.smtp.dev/.well-known/mercure')
// e.g. /accounts/6789abcdef012345{+path}
mercureUrl.searchParams.append('topic', `/accounts/${accountId}{+path}`)

await fetchEventSource(mercureUrl.toString(), {
  headers: {
    Authorization: `Bearer ${token}`
  },
  onmessage(event) {
    const data = JSON.parse(event.data)

    if (data['@type'] === 'Message') {
      // new message received
    }
  }
})
```

You'll receive updates with `Account`, `Mailbox`, or `Message` types depending on what changed.

## Questions and suggestions

If you have any questions or suggestions, please contact us.

## Tech stack

Our stack includes [API-Platform](https://api-platform.com/), [Mercure](https://mercure.rocks/), [Nuxt.js](https://nuxtjs.org), [Haraka](https://haraka.github.io), [Caddy](https://caddyserver.com/), [MongoDB](https://www.mongodb.com/), [Node.js](https://nodejs.org), [RockyLinux](https://rockylinux.org)
