TempMaily.co
Developer reference

TempMaily API

A small REST API to create disposable inboxes, read and delete their messages, and receive signed webhooks when mail arrives. It is a premium feature — every request is authenticated with a Bearer API key.

Base URLhttps://tempmaily.co/api/v1

The full machine-readable spec is at /openapi.json (OpenAPI 3.1).

Authentication

Bearer API keys

Create a key from the dashboard (API keys section). Keys are shown once — store them securely. Send the key on every request in the Authorization header:

Authorization header
Authorization: Bearer xtm_your_api_key_here

Requests that fail authentication return a JSON error envelope { "error": "<code>" } with one of these statuses:

StatuserrorWhen
401invalid_keyMissing, malformed, unknown, or revoked key.
402premium_requiredThe key's account has no active premium subscription.
429rate_limitedExceeded 300 requests per 60 seconds for the key.

Rate limit: 300 requests / 60s per key. Over budget returns 429 rate_limited.

Endpoints

Reference

In the examples, $XTM_KEY is your API key. Fields marked * are required.

POST/api/v1/inboxesCreate an inbox

Creates an inbox on a service-pool domain or a caller-owned custom domain. The raw token is returned exactly once (only its hash is stored) so you can also drive the realtime widget with it.

Body (application/json — all optional)
NameTypeDescription
domainIdstringService-pool domain id. Defaults to the free domain.
customDomainIdstringA verified custom domain you own. Takes precedence over domainId.
expiry"1d" | "7d" | "30d" | "never"Inbox lifetime. Defaults to 1d; never = permanent.
curl
curl -X POST "https://tempmaily.co/api/v1/inboxes" \
  -H "Authorization: Bearer $XTM_KEY" \
  -H "Content-Type: application/json" \
  -d '{"expiry":"7d"}'
Node (fetch)
const res = await fetch("https://tempmaily.co/api/v1/inboxes", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.XTM_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ expiry: "7d" }),
});
const inbox = await res.json();
Response
201 Created
{
  "id": "a1b2c3d4-0000-4000-8000-000000000000",
  "address": "[email protected]",
  "token": "Zm9vYmFy...raw-token-shown-once",
  "expiresAt": "2026-07-10T12:00:00.000Z",
  "isPermanent": false
}
GET/api/v1/inboxesList inboxes

Returns the caller's inboxes, newest first.

curl
curl "https://tempmaily.co/api/v1/inboxes" \
  -H "Authorization: Bearer $XTM_KEY"
Node (fetch)
const res = await fetch("https://tempmaily.co/api/v1/inboxes", {
  headers: { Authorization: `Bearer ${process.env.XTM_KEY}` },
});
const { inboxes } = await res.json();
Response
200 OK
{
  "inboxes": [
    {
      "id": "a1b2c3d4-0000-4000-8000-000000000000",
      "address": "[email protected]",
      "status": "active",
      "createdAt": "2026-07-09T12:00:00.000Z",
      "expiresAt": "2026-07-16T12:00:00.000Z",
      "isPermanent": false
    }
  ]
}
GET/api/v1/inboxes/{id}/messagesList messages in an inbox

Returns message summaries (no body) for a caller-owned inbox, newest first. An inbox you don't own returns 404.

Path parameters
NameTypeDescription
id *stringInbox id.
curl
curl "https://tempmaily.co/api/v1/inboxes/$INBOX_ID/messages" \
  -H "Authorization: Bearer $XTM_KEY"
Node (fetch)
const res = await fetch(`https://tempmaily.co/api/v1/inboxes/${inboxId}/messages`, {
  headers: { Authorization: `Bearer ${process.env.XTM_KEY}` },
});
const { messages } = await res.json();
Response
200 OK
{
  "messages": [
    {
      "id": "e5f6a7b8-0000-4000-8000-000000000000",
      "fromAddress": "[email protected]",
      "fromName": "Ada Lovelace",
      "subject": "Your code",
      "snippet": "Here is the login code you requested…",
      "isRead": false,
      "hasAttachments": false,
      "receivedAt": "2026-07-09T12:01:00.000Z"
    }
  ]
}
GET/api/v1/messages/{id}Get a message

Returns a full message including sanitized bodyHtml, and marks it read. Remote images are stripped unless ?images=1. Render bodyHtml inside a sandboxed iframe.

Path & query parameters
NameTypeDescription
id *stringMessage id.
images"1"Query. Keep remote images in the sanitized HTML.
curl
curl "https://tempmaily.co/api/v1/messages/$MESSAGE_ID" \
  -H "Authorization: Bearer $XTM_KEY"
Node (fetch)
const res = await fetch(`https://tempmaily.co/api/v1/messages/${messageId}`, {
  headers: { Authorization: `Bearer ${process.env.XTM_KEY}` },
});
const message = await res.json();
Response
200 OK
{
  "id": "e5f6a7b8-0000-4000-8000-000000000000",
  "fromAddress": "[email protected]",
  "fromName": "Ada Lovelace",
  "subject": "Your code",
  "bodyText": "Here is the login code: 123456",
  "bodyHtml": "<p>Here is the login code: <b>123456</b></p>",
  "snippet": "Here is the login code you requested…",
  "isRead": true,
  "hasAttachments": false,
  "sizeBytes": 2048,
  "receivedAt": "2026-07-09T12:01:00.000Z"
}
DELETE/api/v1/messages/{id}Delete a message

Permanently removes the message, its stored attachments, and the freed quota. Returns 204 No Content.

Path parameters
NameTypeDescription
id *stringMessage id.
curl
curl -X DELETE "https://tempmaily.co/api/v1/messages/$MESSAGE_ID" \
  -H "Authorization: Bearer $XTM_KEY"
Node (fetch)
await fetch(`https://tempmaily.co/api/v1/messages/${messageId}`, {
  method: "DELETE",
  headers: { Authorization: `Bearer ${process.env.XTM_KEY}` },
});
Response
204 No Content
POST/api/v1/webhooksRegister a webhook

Registers an outbound webhook. url must be a public https URL. The signing secret is returned exactly once and never echoed by the list endpoint. Up to 5 webhooks per account (409 webhook_limit at the cap).

Body (application/json)
NameTypeDescription
url *stringPublic https delivery endpoint.
curl
curl -X POST "https://tempmaily.co/api/v1/webhooks" \
  -H "Authorization: Bearer $XTM_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://hooks.example.com/xtm"}'
Node (fetch)
const res = await fetch("https://tempmaily.co/api/v1/webhooks", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.XTM_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ url: "https://hooks.example.com/xtm" }),
});
const { secret } = await res.json(); // store it — never shown again
Response
201 Created
{
  "id": "b1f2c3d4-0000-4000-8000-000000000000",
  "url": "https://hooks.example.com/xtm",
  "secret": "s3cr3t-shown-once-store-it",
  "enabled": true
}
GET/api/v1/webhooksList webhooks

Returns the caller's webhooks, newest first. The secret is never included.

curl
curl "https://tempmaily.co/api/v1/webhooks" \
  -H "Authorization: Bearer $XTM_KEY"
Node (fetch)
const res = await fetch("https://tempmaily.co/api/v1/webhooks", {
  headers: { Authorization: `Bearer ${process.env.XTM_KEY}` },
});
const { webhooks } = await res.json();
Response
200 OK
{
  "webhooks": [
    {
      "id": "b1f2c3d4-0000-4000-8000-000000000000",
      "url": "https://hooks.example.com/xtm",
      "enabled": true,
      "createdAt": "2026-07-09T12:00:00.000Z"
    }
  ]
}
DELETE/api/v1/webhooksDelete a webhook

Removes a webhook by id. A webhook you don't own returns 404. Returns 204 No Content.

Body (application/json)
NameTypeDescription
webhookId *stringId of the webhook to remove.
curl
curl -X DELETE "https://tempmaily.co/api/v1/webhooks" \
  -H "Authorization: Bearer $XTM_KEY" \
  -H "Content-Type: application/json" \
  -d '{"webhookId":"b1f2c3d4-0000-4000-8000-000000000000"}'
Node (fetch)
await fetch("https://tempmaily.co/api/v1/webhooks", {
  method: "DELETE",
  headers: {
    Authorization: `Bearer ${process.env.XTM_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ webhookId }),
});
Response
204 No Content
Webhooks

Receiving mail events

When a message arrives in an owned inbox, every enabled webhook receives a POST with a message.received payload. Delivery is best-effort: a 5-second timeout, no retries, and any non-2xx response is logged and dropped.

Delivery payload
POST <your-webhook-url>
X-Xtm-Signature: 9f86d081...   // hex(HMAC-SHA256(secret, rawBody))
Content-Type: application/json

{
  "event": "message.received",
  "inboxId": "a1b2c3d4-0000-4000-8000-000000000000",
  "message": {
    "id": "e5f6a7b8-0000-4000-8000-000000000000",
    "fromAddress": "[email protected]",
    "fromName": "Ada Lovelace",
    "subject": "Your code",
    "snippet": "Here is the login code you requested…",
    "receivedAt": "2026-07-09T12:01:00.000Z",
    "hasAttachments": false
  }
}
Signature verification

Each delivery carries X-Xtm-Signature, the hex-encoded HMAC-SHA256 of the raw request body keyed by the webhook secret. Recompute it over the raw bytes and compare in constant time; reject on mismatch.

Node verification
import { createHmac, timingSafeEqual } from "node:crypto";

// Verify an incoming TempMaily webhook. Compute the HMAC over the EXACT raw
// request body (not a re-serialized object) and compare in constant time.
function verify(rawBody, signatureHeader, secret) {
  const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(signatureHeader ?? "", "hex");
  return a.length === b.length && timingSafeEqual(a, b);
}

// Express example:
app.post("/xtm", express.raw({ type: "application/json" }), (req, res) => {
  const raw = req.body.toString("utf8");
  if (!verify(raw, req.get("X-Xtm-Signature"), process.env.XTM_WEBHOOK_SECRET)) {
    return res.sendStatus(401);
  }
  const { event, inboxId, message } = JSON.parse(raw);
  // event === "message.received"
  res.sendStatus(200);
});