Skip to content

Mail.td API

Developer email platform. One REST API. Same endpoints for free and Pro users.

Base URLhttps://api.mail.tdAuthAuthorization: Bearer td_xxx (get a token from the Pro Dashboard) {mailbox} — the email address, e.g. user@example.com (UUID also works)

In URL paths, accounts = mailboxes. One account = one email address.


Quick Start

bash
# 1. List available domains
curl https://api.mail.td/api/domains

# 2. Create a mailbox  (use one of the domains from step 1)
curl -X POST https://api.mail.td/api/accounts \
  -H "Authorization: Bearer td_xxx" \
  -H "Content-Type: application/json" \
  -d '{"address":"user@example.com","password":"secret"}'

# 3. Read its inbox
curl https://api.mail.td/api/accounts/user@example.com/messages \
  -H "Authorization: Bearer td_xxx"

user@example.com is a placeholder. Replace the domain with one returned by step 1 (e.g. mail.td, fexbox.org).

That's it. Three calls, a working inbox.


Endpoints

MethodPathDescription
GET/api/domainsList available domains
POST/api/accountsCreate a mailbox
GET/api/accounts/{mailbox}Get mailbox info
DELETE/api/accounts/{mailbox}Delete a mailbox
PUT/api/accounts/{mailbox}/reset-passwordReset mailbox password
GET/api/accounts/{mailbox}/messagesList messages
GET/api/accounts/{mailbox}/messages/{id}Read a message
PUT/api/accounts/{mailbox}/messages/{id}/readMark message as read
PUT/api/accounts/{mailbox}/messages/readMark many as read
DELETE/api/accounts/{mailbox}/messages/{id}Delete a message
GET/api/accounts/{mailbox}/messages/{id}/sourceDownload raw .eml
GET/api/accounts/{mailbox}/messages/{id}/attachments/{i}Download attachment
GET/api/user/me ProGet profile
GET/api/user/accounts ProList all your mailboxes
GET/api/user/domains ProList custom domains
POST/api/user/domains ProAdd a custom domain
POST/api/user/domains/{id}/verify ProVerify a custom domain
DELETE/api/user/domains/{id} ProDelete a custom domain
GET/api/user/tokens ProList API tokens
POST/api/user/tokens ProCreate an API token
DELETE/api/user/tokens/{id} ProRevoke an API token
GET/api/user/webhooks ProList webhooks
POST/api/user/webhooks ProCreate a webhook
DELETE/api/user/webhooks/{id} ProDelete a webhook
POST/api/user/webhooks/{id}/rotate ProRotate webhook secret
GET/api/user/webhooks/{id}/deliveries ProList webhook deliveries

Details

List available domains

GET /api/domains — no auth.

bash
curl https://api.mail.td/api/domains
json
{
  "domains": [
    { "id": "550e8400-...", "domain": "mail.td", "default": true, "sort_order": 0 },
    { "id": "6ba7b810-...", "domain": "fexbox.org", "default": false, "sort_order": 1 }
  ]
}

Cache this — domains rarely change.


Create a mailbox

POST /api/accounts

bash
curl -X POST https://api.mail.td/api/accounts \
  -H "Authorization: Bearer td_xxx" \
  -H "Content-Type: application/json" \
  -d '{"address":"user@example.com","password":"secret"}'

Body: address (required, local@domain), password (required, min 6 chars).

json
{ "id": "a1b2c3d4-...", "address": "user@example.com" }

201 — save the id; it's the {mailbox} in subsequent calls. The email address also works as {mailbox}.


Get mailbox info

GET /api/accounts/{mailbox}

bash
curl https://api.mail.td/api/accounts/user@example.com \
  -H "Authorization: Bearer td_xxx"
json
{
  "id": "a1b2c3d4-...",
  "address": "user@example.com",
  "role": "user",
  "quota": 41943040,
  "used": 1234567,
  "created_at": "2025-01-15T10:30:00Z"
}

quota and used are bytes.


Delete a mailbox

DELETE /api/accounts/{mailbox} — permanent, wipes all messages.

bash
curl -X DELETE https://api.mail.td/api/accounts/user@example.com \
  -H "Authorization: Bearer td_xxx"

204 No Content


Reset mailbox password

PUT /api/accounts/{mailbox}/reset-password — invalidates existing sessions.

bash
curl -X PUT https://api.mail.td/api/accounts/user@example.com/reset-password \
  -H "Authorization: Bearer td_xxx" \
  -H "Content-Type: application/json" \
  -d '{"password":"newpassword123"}'
json
{ "message": "password_reset" }

List messages

GET /api/accounts/{mailbox}/messages — paginated, 30 per page.

Query: page (default 1). Fewer than 30 results = last page.

bash
curl https://api.mail.td/api/accounts/user@example.com/messages?page=1 \
  -H "Authorization: Bearer td_xxx"
json
{
  "messages": [
    {
      "id": "f5e6d7c8-...",
      "sender": "noreply@example.com",
      "from": "Example <noreply@example.com>",
      "subject": "Verify your email",
      "preview_text": "Click the link below to verify...",
      "size": 4523,
      "is_read": false,
      "created_at": "2025-01-15T10:30:00Z"
    }
  ],
  "page": 1
}

Read a message

GET /api/accounts/{mailbox}/messages/{id}

bash
curl https://api.mail.td/api/accounts/user@example.com/messages/f5e6d7c8-... \
  -H "Authorization: Bearer td_xxx"
json
{
  "id": "f5e6d7c8-...",
  "sender": "noreply@example.com",
  "from": "Example <noreply@example.com>",
  "subject": "Verify your email",
  "address": "user@example.com",
  "size": 4523,
  "created_at": "2025-01-15T10:30:00Z",
  "text_body": "Click the link below...",
  "html_body": "<html>...</html>",
  "attachments": [
    { "index": 0, "filename": "document.pdf", "content_type": "application/pdf", "size": 12345 }
  ]
}

200, 404 if not found, 410 if expired.


Mark message as read

PUT /api/accounts/{mailbox}/messages/{id}/read — idempotent.

bash
curl -X PUT https://api.mail.td/api/accounts/user@example.com/messages/f5e6d7c8-.../read \
  -H "Authorization: Bearer td_xxx"

204 No Content


Mark many as read

PUT /api/accounts/{mailbox}/messages/read — pass ids (max 200) or all: true.

bash
curl -X PUT https://api.mail.td/api/accounts/user@example.com/messages/read \
  -H "Authorization: Bearer td_xxx" \
  -H "Content-Type: application/json" \
  -d '{"ids":["id1","id2","id3"]}'

# Or mark all:
# -d '{"all":true}'
json
{ "updated": 3 }

Delete a message

DELETE /api/accounts/{mailbox}/messages/{id} — soft delete, reclaims quota.

bash
curl -X DELETE https://api.mail.td/api/accounts/user@example.com/messages/f5e6d7c8-... \
  -H "Authorization: Bearer td_xxx"

204 No Content


Download raw .eml

GET /api/accounts/{mailbox}/messages/{id}/source

bash
curl -OJ https://api.mail.td/api/accounts/user@example.com/messages/f5e6d7c8-.../source \
  -H "Authorization: Bearer td_xxx"

200 · Content-Type: message/rfc822 · Content-Disposition: attachment; filename="message.eml"


Download attachment

GET /api/accounts/{mailbox}/messages/{id}/attachments/{i}{i} is the index from the message detail.

bash
curl -OJ https://api.mail.td/api/accounts/user@example.com/messages/f5e6d7c8-.../attachments/0 \
  -H "Authorization: Bearer td_xxx"

200 binary with original Content-Type and filename.


Pro management

Pro endpoints live under /api/user/*. They manage your account, custom domains, tokens, and webhooks — the email API stays the same.

Get profile

GET /api/user/me Pro

bash
curl https://api.mail.td/api/user/me \
  -H "Authorization: Bearer td_xxx"
json
{
  "id": "...",
  "email": "user@example.com",
  "plan": "pro",
  "status": "active",
  "max_domains": 3,
  "ops_used": 1234,
  "ops_limit": 100000,
  "domain_count": 1,
  "created_at": "2025-01-15T10:30:00Z"
}

ops_used / ops_limit — monthly operation quota.


List all your mailboxes

GET /api/user/accounts Pro

bash
curl https://api.mail.td/api/user/accounts \
  -H "Authorization: Bearer td_xxx"
json
{
  "accounts": [
    { "id": "a1b2c3d4-...", "address": "hello@mydomain.com", "quota": 52428800, "used": 1234567, "created_at": "..." }
  ]
}

Use each id as {mailbox} in the unified endpoints above.


List custom domains

GET /api/user/domains Pro

bash
curl https://api.mail.td/api/user/domains \
  -H "Authorization: Bearer td_xxx"
json
{
  "domains": [
    {
      "id": "d1e2f3a4-...",
      "domain": "yourdomain.com",
      "verify_status": "verified",
      "verify_token": "a1b2...",
      "verified_at": "2025-01-15T12:00:00Z",
      "mx_configured": true,
      "created_at": "2025-01-15T10:00:00Z"
    }
  ]
}

verify_status: pending or verified.


Add a custom domain

POST /api/user/domains Pro

bash
curl -X POST https://api.mail.td/api/user/domains \
  -H "Authorization: Bearer td_xxx" \
  -H "Content-Type: application/json" \
  -d '{"domain":"yourdomain.com"}'

Returns the DNS records you need to add:

json
{
  "id": "d1e2f3a4-...",
  "domain": "yourdomain.com",
  "verify_token": "a1b2...",
  "dns_records": [
    { "type": "TXT", "host": "_tempmail.yourdomain.com", "value": "tempmail-verify=a1b2..." },
    { "type": "MX",  "host": "yourdomain.com", "value": "smtp.mail.td", "priority": 10 }
  ]
}

Add both records at your DNS provider, then call Verify. DNS usually propagates in under 10 minutes.


Verify a custom domain

POST /api/user/domains/{id}/verify Pro

bash
curl -X POST https://api.mail.td/api/user/domains/d1e2f3a4-.../verify \
  -H "Authorization: Bearer td_xxx"
json
// Verified
{ "verify_status": "verified", "txt_record": true, "mx_record": true }

// Still pending — shows which records are still missing
{ "verify_status": "pending", "txt_record": true, "mx_record": false,
  "dns_records": [{ "type": "MX", "host": "yourdomain.com", "value": "smtp.mail.td", "priority": 10 }] }

410 domain_expired if the verification window expired — re-add the domain.


Delete a custom domain

DELETE /api/user/domains/{id} Pro — mailboxes on this domain stop receiving mail.

bash
curl -X DELETE https://api.mail.td/api/user/domains/d1e2f3a4-... \
  -H "Authorization: Bearer td_xxx"

204 No Content


List API tokens

GET /api/user/tokens Pro

bash
curl https://api.mail.td/api/user/tokens \
  -H "Authorization: Bearer td_xxx"
json
{
  "tokens": [
    { "id": "t1o2k3e4-...", "name": "Production", "last_used_at": "...", "created_at": "...", "revoked_at": null }
  ]
}

Create an API token

POST /api/user/tokens Pro

bash
curl -X POST https://api.mail.td/api/user/tokens \
  -H "Authorization: Bearer td_xxx" \
  -H "Content-Type: application/json" \
  -d '{"name":"Production Server"}'
json
{ "id": "t1o2k3e4-...", "name": "Production Server", "token": "td_aBcDeFgHiJ..." }

The token is shown only once. Store it securely.


Revoke an API token

DELETE /api/user/tokens/{id} Pro — takes effect immediately.

bash
curl -X DELETE https://api.mail.td/api/user/tokens/t1o2k3e4-... \
  -H "Authorization: Bearer td_xxx"

204 No Content


List webhooks

GET /api/user/webhooks Pro

bash
curl https://api.mail.td/api/user/webhooks \
  -H "Authorization: Bearer td_xxx"
json
{
  "webhooks": [
    {
      "id": "w1e2b3h4-...",
      "url": "https://yourapp.com/webhook",
      "events": ["email.received"],
      "status": "active",
      "failure_count": 0,
      "last_triggered_at": "2025-01-15T10:30:00Z",
      "created_at": "2025-01-15T10:00:00Z"
    }
  ]
}

Auto-disabled after 10 consecutive delivery failures. Delete + re-create to re-enable.


Create a webhook

POST /api/user/webhooks Pro

bash
curl -X POST https://api.mail.td/api/user/webhooks \
  -H "Authorization: Bearer td_xxx" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://yourapp.com/webhook","events":["email.received"]}'

Body: url (HTTPS only), events (currently just email.received).

json
{
  "id": "w1e2b3h4-...",
  "url": "https://yourapp.com/webhook",
  "events": ["email.received"],
  "secret": "whsec_aBcDeF...",
  "status": "active",
  "created_at": "..."
}

The secret is shown only once — save it, you'll need it to verify signatures.


Delete a webhook

DELETE /api/user/webhooks/{id} Pro

bash
curl -X DELETE https://api.mail.td/api/user/webhooks/w1e2b3h4-... \
  -H "Authorization: Bearer td_xxx"

204 No Content


Rotate webhook secret

POST /api/user/webhooks/{id}/rotate Pro — old secret invalidated immediately.

bash
curl -X POST https://api.mail.td/api/user/webhooks/w1e2b3h4-.../rotate \
  -H "Authorization: Bearer td_xxx"
json
{ "id": "w1e2b3h4-...", "secret": "whsec_NewSecret..." }

List webhook deliveries

GET /api/user/webhooks/{id}/deliveries Pro — recent delivery attempts, for debugging.

bash
curl https://api.mail.td/api/user/webhooks/w1e2b3h4-.../deliveries \
  -H "Authorization: Bearer td_xxx"
json
{
  "deliveries": [
    { "id": "...", "event_type": "email.received", "event_id": "evt_abc",
      "status_code": 200, "error": null, "attempt": 1, "duration_ms": 142, "created_at": "..." },
    { "id": "...", "event_type": "email.received", "event_id": "evt_def",
      "status_code": 0, "error": "connection timeout", "attempt": 3, "duration_ms": 30000, "created_at": "..." }
  ]
}

status_code: 0 means the connection itself failed (DNS, TLS, timeout).


Webhook payload

When an email arrives and you have a webhook configured, we POST this to your URL:

json
{
  "id": "evt_a1b2c3d4-...",
  "type": "email.received",
  "created_at": "2025-01-15T10:30:00Z",
  "data": {
    "email_id": "f5e6d7c8-...",
    "account_id": "a1b2c3d4-...",
    "address": "info@yourdomain.com",
    "from": "Example <noreply@example.com>",
    "sender": "noreply@example.com",
    "subject": "Verify your email",
    "preview_text": "Click the link below...",
    "size": 4523,
    "created_at": "2025-01-15T10:30:00Z"
  }
}

Each request carries three headers:

HeaderValue
X-Webhook-IDUnique event id (same as id above)
X-Webhook-TimestampUnix seconds
X-Webhook-Signaturesha256=<hex> over timestamp + "." + raw_body, keyed with your webhook secret

Respond 2xx within 10s to acknowledge. Non-2xx and timeouts are retried with exponential backoff; after 10 consecutive failures the webhook is disabled.

Verify the signature

javascript
const crypto = require('crypto');

function verify(rawBody, timestamp, signature, secret) {
  const expected = 'sha256=' +
    crypto.createHmac('sha256', secret).update(timestamp + '.' + rawBody).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
python
import hmac, hashlib

def verify(raw_body: bytes, timestamp: str, signature: str, secret: str) -> bool:
    expected = 'sha256=' + hmac.new(
        secret.encode(), timestamp.encode() + b'.' + raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)

Errors

All errors return JSON: { "error": "<code>", "message": "<human text>" }.

StatusWhen
200OK
201Resource created
204OK, no body
400Malformed request (e.g. invalid_address, invalid_domain, password_too_short, invalid_url, invalid_index)
401Missing / invalid / revoked token (invalid_token, invalid_or_expired_token)
403Quota or limit hit (account_limit_reached, domain_limit_reached, webhook_limit_reached)
404Resource doesn't exist, or you don't own it (not_found)
409Duplicate (address_taken, domain_already_exists)
410Expired message or domain verification (expired, domain_expired)
429Rate limited (rate_limit_exceeded) or monthly quota hit (ops_quota_exceeded)
5xxServer error — retry with backoff

Your API token can only access resources you created with it — others return 404.


Rate limits

PlanPer-secondCreate mailboxMonthly ops quota
Free4 req/s per user1 per 8s1,000
Pro10 req/s per user1 req/s100,000

Hitting 429 means wait and retry. Check GET /api/user/me for your current ops_used / ops_limit.

Tips: use webhooks instead of polling, cache the domain list, and back off exponentially after a 429.

javascript
async function fetchWithRetry(url, options, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    const res = await fetch(url, options);
    if (res.status !== 429) return res;
    await new Promise(r => setTimeout(r, 2 ** i * 1000));
  }
  throw new Error('rate limited');
}

SDKs

LanguagePackage
Pythonpip install mailtd
Node.jsnpm install mailtd
python
from mailtd import MailTD
client = MailTD("td_xxx")
mb = client.accounts.create("user@example.com", password="secret")
for m in client.messages.list(mb.id)[0]:
    print(client.messages.get(mb.id, m.id).text_body)
javascript
import { MailTD } from 'mailtd';
const client = new MailTD('td_xxx');
const mb = await client.accounts.create('user@example.com', { password: 'secret' });
const { messages } = await client.messages.list(mb.id);
for (const m of messages) {
  console.log((await client.messages.get(mb.id, m.id)).text_body);
}

See SDKs for all language clients.


LLM-friendly docs

A flat plain-text version of this page is served at /llms.txt for AI assistants and coding agents. More →

Mail.td API Documentation