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.
https://tempmaily.co/api/v1The full machine-readable spec is at /openapi.json (OpenAPI 3.1).
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: Bearer xtm_your_api_key_hereRequests that fail authentication return a JSON error envelope { "error": "<code>" } with one of these statuses:
| Status | error | When |
|---|---|---|
| 401 | invalid_key | Missing, malformed, unknown, or revoked key. |
| 402 | premium_required | The key's account has no active premium subscription. |
| 429 | rate_limited | Exceeded 300 requests per 60 seconds for the key. |
Rate limit: 300 requests / 60s per key. Over budget returns 429 rate_limited.
Reference
In the examples, $XTM_KEY is your API key. Fields marked * are required.
/api/v1/inboxesCreate an inboxCreates 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.
| Name | Type | Description |
|---|---|---|
| domainId | string | Service-pool domain id. Defaults to the free domain. |
| customDomainId | string | A verified custom domain you own. Takes precedence over domainId. |
| expiry | "1d" | "7d" | "30d" | "never" | Inbox lifetime. Defaults to 1d; never = permanent. |
curl -X POST "https://tempmaily.co/api/v1/inboxes" \
-H "Authorization: Bearer $XTM_KEY" \
-H "Content-Type: application/json" \
-d '{"expiry":"7d"}'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();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
}/api/v1/inboxesList inboxesReturns the caller's inboxes, newest first.
curl "https://tempmaily.co/api/v1/inboxes" \
-H "Authorization: Bearer $XTM_KEY"const res = await fetch("https://tempmaily.co/api/v1/inboxes", {
headers: { Authorization: `Bearer ${process.env.XTM_KEY}` },
});
const { inboxes } = await res.json();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
}
]
}/api/v1/inboxes/{id}/messagesList messages in an inboxReturns message summaries (no body) for a caller-owned inbox, newest first. An inbox you don't own returns 404.
| Name | Type | Description |
|---|---|---|
| id * | string | Inbox id. |
curl "https://tempmaily.co/api/v1/inboxes/$INBOX_ID/messages" \
-H "Authorization: Bearer $XTM_KEY"const res = await fetch(`https://tempmaily.co/api/v1/inboxes/${inboxId}/messages`, {
headers: { Authorization: `Bearer ${process.env.XTM_KEY}` },
});
const { messages } = await res.json();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"
}
]
}/api/v1/messages/{id}Get a messageReturns a full message including sanitized bodyHtml, and marks it read. Remote images are stripped unless ?images=1. Render bodyHtml inside a sandboxed iframe.
| Name | Type | Description |
|---|---|---|
| id * | string | Message id. |
| images | "1" | Query. Keep remote images in the sanitized HTML. |
curl "https://tempmaily.co/api/v1/messages/$MESSAGE_ID" \
-H "Authorization: Bearer $XTM_KEY"const res = await fetch(`https://tempmaily.co/api/v1/messages/${messageId}`, {
headers: { Authorization: `Bearer ${process.env.XTM_KEY}` },
});
const message = await res.json();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"
}/api/v1/messages/{id}Delete a messagePermanently removes the message, its stored attachments, and the freed quota. Returns 204 No Content.
| Name | Type | Description |
|---|---|---|
| id * | string | Message id. |
curl -X DELETE "https://tempmaily.co/api/v1/messages/$MESSAGE_ID" \
-H "Authorization: Bearer $XTM_KEY"await fetch(`https://tempmaily.co/api/v1/messages/${messageId}`, {
method: "DELETE",
headers: { Authorization: `Bearer ${process.env.XTM_KEY}` },
});204 No Content/api/v1/webhooksRegister a webhookRegisters 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).
| Name | Type | Description |
|---|---|---|
| url * | string | Public https delivery endpoint. |
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"}'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 again201 Created
{
"id": "b1f2c3d4-0000-4000-8000-000000000000",
"url": "https://hooks.example.com/xtm",
"secret": "s3cr3t-shown-once-store-it",
"enabled": true
}/api/v1/webhooksList webhooksReturns the caller's webhooks, newest first. The secret is never included.
curl "https://tempmaily.co/api/v1/webhooks" \
-H "Authorization: Bearer $XTM_KEY"const res = await fetch("https://tempmaily.co/api/v1/webhooks", {
headers: { Authorization: `Bearer ${process.env.XTM_KEY}` },
});
const { webhooks } = await res.json();200 OK
{
"webhooks": [
{
"id": "b1f2c3d4-0000-4000-8000-000000000000",
"url": "https://hooks.example.com/xtm",
"enabled": true,
"createdAt": "2026-07-09T12:00:00.000Z"
}
]
}/api/v1/webhooksDelete a webhookRemoves a webhook by id. A webhook you don't own returns 404. Returns 204 No Content.
| Name | Type | Description |
|---|---|---|
| webhookId * | string | Id of the webhook to remove. |
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"}'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 }),
});204 No ContentReceiving 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.
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
}
}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.
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);
});