Developers
TapMail API
Create temporary addresses, read the emails they receive and download attachments from your own code. It's made for automated tests: your script signs up with a fresh address, reads the confirmation email and moves on. Every Premium plan includes 1,000 requests a month, and you can add more.
Getting started
- Buy any Premium plan.
- Open API in your dashboard and create a key.
- Send requests to the base URL below, with your key in the
X-API-Keyheader.
Base URL
https://tap-mail.com/api/v1
Requests and responses are JSON. The paths and field names match temp-mail.io's API, so code written for it usually only needs the new base URL and key.
cURL
# 1. Create an address that lasts 1 hour
curl -X POST https://tap-mail.com/api/v1/emails \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"ttl": 3600}'
# 2. Read the emails it received
curl https://tap-mail.com/api/v1/emails/ADDRESS/messages \
-H "X-API-Key: YOUR_API_KEY"JavaScript (Node 18+): wait for a sign-up email
const API = "https://tap-mail.com/api/v1";
const headers = { "X-API-Key": process.env.TAPMAIL_API_KEY, "Content-Type": "application/json" };
async function call(method, path, body) {
const res = await fetch(API + path, { method, headers, body: body && JSON.stringify(body) });
const json = await res.json();
if (!res.ok) throw new Error(`${json.error.code}: ${json.error.detail}`);
return json;
}
// A new address for this test run, deleted automatically after 10 minutes
const { email } = await call("POST", "/emails", { ttl: 600 });
await signUpOnMySite(email); // your own code
// Check every 5 seconds, for up to 1 minute
let message;
for (let i = 0; i < 12 && !message; i++) {
await new Promise((r) => setTimeout(r, 5000));
[message] = (await call("GET", `/emails/${email}/messages`)).messages;
}
const link = message.body_text.match(/https:\/\/\S+/)[0];
await call("DELETE", `/emails/${email}`); // or let it expirePython
import os, time, requests
API = "https://tap-mail.com/api/v1"
headers = {"X-API-Key": os.environ["TAPMAIL_API_KEY"]}
email = requests.post(f"{API}/emails", json={"ttl": 600}, headers=headers).json()["email"]
sign_up_on_my_site(email) # your own code
for _ in range(12):
time.sleep(5)
messages = requests.get(f"{API}/emails/{email}/messages", headers=headers).json()["messages"]
if messages:
print(messages[0]["subject"])
breakAuthentication
Every request needs your key in the X-API-Key header. Keys start with tm_. You can have up to 5, for example one per project, and delete any of them in your dashboard; a deleted key stops working immediately.
We only show a key once, when you create it, and we only store a scrambled version of it, so we can't show it again. Keep it secret: don't put it in code that runs in a browser or in a public repository. If one leaks, delete it and create a new one.
Limits and usage
- Monthly requests. Every call uses 1 request, except
GET /rate_limit. Your allowance starts again every month on the day your plan started. Your plan's requests are used first, then any extra requests you bought. - Speed. Up to 10 requests per second per key. To wait for an email, check every few seconds rather than in a tight loop.
- Addresses expire. An address made through the API is deleted with its emails after 1 day, or after the
ttlyou choose (1 minute to 30 days). API addresses are never permanent. - 10 addresses at a time. API addresses count toward the same 10 as your dashboard addresses. Expired and deleted addresses free their place straight away.
Every answer tells you where you stand, in these headers:
X-Ratelimit-Limit: 5000 # requests this month X-Ratelimit-Used: 1204 X-Ratelimit-Remaining: 3796 X-Ratelimit-Reset: 1793404800 # when they start again (Unix time, seconds)
GET/rate_limit
Response
{ "limit": 5000, "used": 1204, "remaining": 3796, "reset": 1793404800 }Addresses
GET/domains
public) and the domains you connected yourself (custom).Response
{
"domains": [
{ "name": "tap-mail.com", "type": "public" },
{ "name": "mail.your-shop.com", "type": "custom" }
]
}POST/emails
{} for a random one.email- Optional. The name to use, like "john" or "john@tap-mail.com". Random if left out.
domain- Optional. One of GET /domains. A random public domain if left out.
domain_type- Optional. "public" or "custom": a random domain of that kind.
ttl- Optional. Seconds until the address and its emails are deleted: 60 to 2592000 (30 days). 86400 (1 day) if left out.
Response
{
"email": "k7q2m9@tap-mail.com",
"ttl": 86400,
"expires_at": "2026-10-27T09:40:00.000Z"
}DELETE/emails/:email
Response
{}GET/emails/:email/messages
Response
{
"messages": [ /* emails, newest first, like GET /messages/:id */ ]
}Emails
GET/messages/:id
spam is true and warning says why: phishing-link, phishing-sender or spam.Response
{
"id": "6720c1f2a9b3e4d5f6a7b8c9",
"from": "Acme <hello@acme.com>",
"to": "k7q2m9@tap-mail.com",
"cc": [],
"subject": "Confirm your email",
"body_text": "Click the link to confirm: https://acme.com/confirm/abc",
"body_html": "<p>Click the link to confirm: ...</p>",
"created_at": "2026-10-26T09:41:12.000Z",
"attachments": [
{
"id": "6720c1f2a9b3e4d5f6a7b8c9-0",
"name": "invoice.pdf",
"size": 48213,
"content_type": "application/pdf"
}
],
"spam": false,
"warning": null
}DELETE/messages/:id
Response
{}GET/messages/:id/source_code
Response
{ "data": "Received: from mail.acme.com ...\r\nSubject: Confirm your email\r\n..." }Attachments
GET/attachments/:id
attachments.Response
The file itself, with its own Content-Type.
Errors
Errors always have the same shape, with an HTTP status to match:
{
"error": {
"type": "request_error",
"code": "validation_error",
"detail": "ttl must be a whole number of seconds from 60 to 2592000 (30 days)."
},
"meta": { "request_id": "3f1c2a9e-8b7d-4c6e-9a5f-1d2e3c4b5a69" }
}The request_id is also in the X-Request-Id header. If you contact us about a failed request, include it.
| Status | Code | What it means |
|---|---|---|
| 400 | validation_error | A field is missing or wrong. detail says which one. |
| 401 | api_key_empty | No X-API-Key header was sent. |
| 401 | api_key_invalid | The key doesn't exist, or was deleted. |
| 403 | plan_expired | Your Premium plan has ended. Renew it to use the API again. |
| 403 | mailbox_limit | You already have 10 addresses. Delete one, or wait for one to expire. |
| 404 | not_found | The address, email, attachment or endpoint doesn't exist, or isn't yours. |
| 409 | address_taken | Someone already uses that address. Pick another name. |
| 429 | rate_limited | Too many requests per second, or this month's requests are used up. |
| 500 | internal_error | Something went wrong on our side. Try again, and quote the request_id if it keeps happening. |