smtp.dev

Email Testing API

Email Testing API

REST API for domains, accounts, mailboxes, messages, and tokens. Authenticate with an X-API-KEY header; stream new messages over SSE.

General information

The API is described by an OpenAPI v3 document, and you can try every endpoint in the interactive reference.

Usage of our service for illegal activity is strictly prohibited.

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:

X-API-KEY: smtpla...here

How to get it? If you don't have an API key yet, create a new one on the API Keys page.

Example request with authentication:

curl -X GET "https://stmp.ink/api/accounts" \
  -H "X-API-KEY: smtpla...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:

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

Health and schema

GET /api/health

Public health check.

curl https://stmp.ink/api/health
{"status": "ok"}

GET /api/openapi.json

Public OpenAPI 3 document listing the REST paths exposed by this clone.

Domains

GET /api/domains

List domains for the authenticated user. Query: page, limit.

curl -H "X-API-KEY: $SMTPDEV_API_KEY" \
  "https://stmp.ink/api/domains?page=1&limit=25"

POST /api/domains

Body: domain required, mx_verified optional.

curl -X POST https://stmp.ink/api/domains \
  -H "X-API-KEY: $SMTPDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"domain":"dev.example.test","mx_verified":true}'
{
  "id": 1,
  "domain": "dev.example.test",
  "mx_verified": true,
  "verification_status": "verified",
  "created_at": "2026-08-06T12:00:00+00:00"
}

Accounts and mailboxes

GET /api/accounts

List accounts. Query: page, limit.

POST /api/accounts

Body: domain_id, address, optional password, is_active, is_catch_all, storage_limit.

curl -X POST https://stmp.ink/api/accounts \
  -H "X-API-KEY: $SMTPDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"domain_id":1,"address":"qa@dev.example.test","password":"account-password"}'
{
  "id": 1,
  "domain_id": 1,
  "address": "qa@dev.example.test",
  "used_bytes": 0,
  "used": 0,
  "is_active": true,
  "is_catch_all": false,
  "storage_limit": 104857600,
  "created_at": "2026-08-06T12:00:00+00:00"
}

GET /api/accounts/<account_id>

Fetch one account owned by the authenticated user.

GET /api/accounts/<account_id>/mailboxes

List mailboxes. Query: page, limit.

POST /api/accounts/<account_id>/mailboxes

Body: path required.

curl -X POST https://stmp.ink/api/accounts/1/mailboxes \
  -H "X-API-KEY: $SMTPDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"path":"Promotions"}'

Messages

GET /api/accounts/<account_id>/mailboxes/<mailbox_id>/messages

List mailbox messages newest first. Query: page, limit, q. Search matches subject, sender, recipients, and text body.

curl -H "X-API-KEY: $SMTPDEV_API_KEY" \
  "https://stmp.ink/api/accounts/1/mailboxes/1/messages?q=reset"
{
  "items": [{
    "id": 1,
    "msgid": "<abc@example.test>",
    "account_id": 1,
    "mailbox_id": 1,
    "subject": "Reset code",
    "intro": "Your code is 481516.",
    "thread_id": null,
    "from_address": "app@example.test",
    "to_addresses": ["qa@dev.example.test"],
    "received_at": "2026-08-06T12:00:00+00:00",
    "expires_at": null,
    "size_bytes": 512
  }],
  "pagination": {"page": 1, "limit": 25, "per_page": 25, "total": 1}
}

GET /api/messages/<message_id>

Fetch full parsed content: envelope, text, HTML, headers, attachments, SPF/DKIM/DMARC fields, and audit results.

GET /api/messages/<message_id>/raw

Return raw RFC 5322 source with message/rfc822 content type.

GET /api/messages/<message_id>/attachments/<attachment_id>

Download an attachment as raw binary using the stored content type and filename.

DELETE /api/messages/<message_id>

Delete a message and publish a deletion event.

POST /api/accounts/<account_id>/messages/send

Sandboxed send to local accounts. Body: to required, optional cc, bcc, replyTo, subject, text, html. Returns 204.

curl -X POST https://stmp.ink/api/accounts/1/messages/send \
  -H "X-API-KEY: $SMTPDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"to":["qa+api@dev.example.test"],"subject":"Hello","text":"Sandbox only"}'

Tokens and events

GET /api/tokens

List token metadata. Token values are not returned after creation.

POST /api/tokens

Create a named API token. Body: optional name. The response includes the token value once.

DELETE /api/tokens/<token_id>

Revoke a token and return 204.

GET /api/events

SSE stream. Auth by X-API-KEY or session cookie. Query once=1 returns the latest event and closes.

curl -N -H "X-API-KEY: $SMTPDEV_API_KEY" \
  "https://stmp.ink/api/events?once=1"
event: new-message
data: {"type":"message.received","id":1,"account_id":1,"mailbox_id":1,"subject":"Reset code"}