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

  1. Buy any Premium plan.
  2. Open API in your dashboard and create a key.
  3. Send requests to the base URL below, with your key in the X-API-Key header.

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 expire

Python

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"])
        break

Authentication

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 ttl you 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

Your usage this month. Free: it doesn't use a request.

Response

{ "limit": 5000, "used": 1204, "remaining": 3796, "reset": 1793404800 }

Addresses

GET/domains

The domains you can create addresses on: ours (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

Creates an address. Send an empty body {} 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

Deletes an address and all its emails now, instead of waiting for it to expire. Works on your dashboard addresses too.

Response

{}

GET/emails/:email/messages

The emails an address received, newest first: up to 200. Older ones can still be read by their id.

Response

{
  "messages": [ /* emails, newest first, like GET /messages/:id */ ]
}

Emails

GET/messages/:id

One email. Reading it marks it as read in your dashboard. If phishing protection found something suspicious, 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

Deletes one email.

Response

{}

GET/messages/:id/source_code

The email exactly as it arrived, headers and all (the .eml file). Only kept for addresses created through the API.

Response

{ "data": "Received: from mail.acme.com ...\r\nSubject: Confirm your email\r\n..." }

Attachments

GET/attachments/:id

Downloads an attachment. Its id is listed in the email's 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.

StatusCodeWhat it means
400validation_errorA field is missing or wrong. detail says which one.
401api_key_emptyNo X-API-Key header was sent.
401api_key_invalidThe key doesn't exist, or was deleted.
403plan_expiredYour Premium plan has ended. Renew it to use the API again.
403mailbox_limitYou already have 10 addresses. Delete one, or wait for one to expire.
404not_foundThe address, email, attachment or endpoint doesn't exist, or isn't yours.
409address_takenSomeone already uses that address. Pick another name.
429rate_limitedToo many requests per second, or this month's requests are used up.
500internal_errorSomething went wrong on our side. Try again, and quote the request_id if it keeps happening.