Developers

API reference

Everything this interface does — creating an address, watching the inbox, reading a message, downloading attachments — is a plain HTTP + JSON call you can make from your own code. The API lives under /api/v1.

Base URL

/api/v1 — the examples below use the shell variable $API for it. The same origin serves both this page and the API (the web server proxies /api to the mail server), so no CORS setup is needed.

Authentication

There is no API key. POST /api/v1/addresses returns a token and every other call carries it in the path: /api/v1/{token}/emails.

Quick start

shell
# 1. a domain you can use
curl "$API/api/v1/domains"

# 2. create an address (keep the token)
curl -X POST "$API/api/v1/addresses" -H "Content-Type: application/json" -d '{}'

# 3. list the inbox
curl "$API/api/v1/$TOKEN/emails"

# 4. read one message (body_html is untrusted, render it sandboxed)
curl "$API/api/v1/$TOKEN/emails/$EMAIL_ID"

Endpoints

GET/api/v1/health

Service and database status

status, database and the configured domains. Cached for 5 seconds.

Request

shell
curl "$API/api/v1/health"

Response

json
{
  "status": "healthy",
  "database": "connected",
  "domains": ["rota.ath.cx"]
}

Errors

  • 503 Database unreachable (status is unhealthy).
GET/api/v1/domains

List the domains you can create addresses on

domains: the domains configured on the server, in the order they are used.

Request

shell
curl "$API/api/v1/domains"

Response

json
{
  "domains": ["rota.ath.cx"]
}
POST/api/v1/addresses

Create a temporary address

The created address. Keep token — every other call is authenticated by it. expires_at is 24 hours by default and the year 9999 when lifetime_hours is 0 (permanent).

Parameters

  • usernamestring?

    bodyCustom local part: 3–64 characters, letters, numbers, dot, underscore or hyphen. Omitted or empty generates a random one.

  • domainstring?

    bodyOne of the values from /api/v1/domains. Defaults to the first configured domain.

  • lifetime_hoursinteger?

    bodyHow long the address lives. Omitted uses the server default (24 h), 0 means it never expires (permanent address), and 1–876000 asks for that many hours.

Request

shell
curl -X POST "$API/api/v1/addresses" \
  -H "Content-Type: application/json" \
  -d '{"username": "signup", "domain": "rota.ath.cx"}'

Response

json
{
  "id": "c89fbe6f-078c-4940-b2d4-4bf193c8a92b",
  "email": "signup@rota.ath.cx",
  "token": "FgKngb1jXhCfHHJAAVU5oN67Xt-Wu7aqz4An-LYqO7rp6ULd55_6GSyJ39o6ycE_",
  "created_at": "2026-09-12T22:26:45.179Z",
  "expires_at": "2026-09-13T22:26:45.179Z"
}

Errors

  • 400 Domain 'x' is not available. Use GET /api/v1/domains to see available domains
  • 400 Username 'x' is reserved and cannot be used
  • 403 Custom usernames are not allowed on this server
  • 409 Email address 'x' is already taken
  • 422 Invalid username or domain format (validation detail list)
GET/api/v1/{token}/emails

List the inbox

A page of message summaries plus counters (total, page, per_page, has_next, has_prev).

Parameters

  • tokenstringrequired

    pathAccess token returned by POST /api/v1/addresses. It is the only credential.

  • pageinteger

    queryPage number, starting at 1. Default 1.

  • per_pageinteger

    queryItems per page, 1–100. Default 50.

  • unread_onlyboolean

    queryReturn only unread messages. Default false.

  • searchstring

    queryCase-insensitive search across subject, sender and body.

Request

shell
curl "$API/api/v1/$TOKEN/emails?per_page=20&search=invoice"

Response

json
{
  "emails": [
    {
      "id": "22222222-2222-4222-8222-222222222222",
      "subject": "Your invoice for August",
      "from_address": "billing@acme-corp.example",
      "to_address": "signup@rota.ath.cx",
      "received_at": "2026-09-12T20:31:09.412Z",
      "is_read": false,
      "has_attachments": true,
      "size_bytes": 264133
    }
  ],
  "total": 1,
  "page": 1,
  "per_page": 20,
  "has_next": false,
  "has_prev": false
}

Errors

  • 404 Address not found
  • 404 Address has expired — create a new address and use its token
GET/api/v1/{token}/emails/{email_id}

Read one message

Headers, both body versions and the DKIM/SPF/DMARC verdicts. body_html is untrusted markup — render it in a sandboxed iframe.

Parameters

  • tokenstringrequired

    pathAccess token returned by POST /api/v1/addresses. It is the only credential.

  • email_iduuidrequired

    pathMessage identifier taken from the inbox listing.

  • mark_readboolean

    querySets is_read while reading. Send false to peek without changing the flag. Default true.

Request

shell
curl "$API/api/v1/$TOKEN/emails/22222222-2222-4222-8222-222222222222?mark_read=false"

Response

json
{
  "id": "22222222-2222-4222-8222-222222222222",
  "message_id": "<invoice-88213@acme-corp.example>",
  "subject": "Your invoice for August",
  "from_address": "billing@acme-corp.example",
  "to_address": "signup@rota.ath.cx",
  "raw_headers": "From: billing@acme-corp.example\r\nSubject: Your invoice for August\r\n",
  "body_plain": "Please find your invoice attached.",
  "body_html": null,
  "size_bytes": 264133,
  "dkim_valid": false,
  "spf_result": "softfail",
  "dmarc_result": "none",
  "has_attachments": true,
  "received_at": "2026-09-12T20:31:09.412Z",
  "is_read": false,
  "attachments": [
    {
      "id": "aaaaaaa1-aaaa-4aaa-8aaa-aaaaaaaaaaa1",
      "filename": "invoice-august.pdf",
      "content_type": "application/pdf",
      "size_bytes": 184320
    }
  ]
}

Errors

  • 404 Email not found
GET/api/v1/{token}/emails/{email_id}/raw

Download the raw message

The original RFC 822 message as a .eml file.

Parameters

  • tokenstringrequired

    pathAccess token returned by POST /api/v1/addresses. It is the only credential.

  • email_iduuidrequired

    pathMessage identifier taken from the inbox listing.

Request

shell
curl -OJ "$API/api/v1/$TOKEN/emails/$EMAIL_ID/raw"

Body

http
Binary attachment (`message/rfc822`).

Errors

  • 404 Email not found
GET/api/v1/{token}/emails/{email_id}/attachments/{attachment_id}

Download an attachment

The decoded file, with its original filename and content type.

Parameters

  • tokenstringrequired

    pathAccess token returned by POST /api/v1/addresses. It is the only credential.

  • email_iduuidrequired

    pathMessage identifier taken from the inbox listing.

  • attachment_iduuidrequired

    pathIdentifier from the attachments array of the message detail.

Request

shell
curl -OJ "$API/api/v1/$TOKEN/emails/$EMAIL_ID/attachments/$ATTACHMENT_ID"

Body

http
Binary attachment.

Errors

  • 404 Email not found
  • 404 Attachment not found
DELETE/api/v1/{token}/emails/{email_id}

Delete a message

Empty body with status 204. The message disappears from the listing.

Parameters

  • tokenstringrequired

    pathAccess token returned by POST /api/v1/addresses. It is the only credential.

  • email_iduuidrequired

    pathMessage identifier taken from the inbox listing.

Request

shell
curl -X DELETE -i "$API/api/v1/$TOKEN/emails/$EMAIL_ID"

Response

json
HTTP/1.1 204 No Content

Errors

  • 404 Email not found

Worth knowing

  • The token is the only credential: anyone holding it can read the inbox of that address until it expires, so treat it like a password.
  • Addresses live for 24 hours by default and accept up to 100 messages, both configurable on the server (address_lifetime_hours, max_emails_per_address).
  • Custom usernames must be 3-64 characters using letters, numbers, dot, underscore or hyphen; reserved words (admin, postmaster, abuse, ...) are rejected with 400.
  • body_html is untrusted markup: never inject it into your page. Render it inside a sandboxed iframe exactly as this interface does.
  • Attachments and the raw .eml are streams, not JSON: call them with the token in the path and save the response as a file.

The project is open source: source code.