Appearance
Mail.td API
Developer email platform. One REST API. Same endpoints for free and Pro users.
Base URL — https://api.mail.tdAuth — Authorization: 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.comis 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
| Method | Path | Description |
|---|---|---|
| GET | /api/domains | List available domains |
| POST | /api/accounts | Create a mailbox |
| GET | /api/accounts/{mailbox} | Get mailbox info |
| DELETE | /api/accounts/{mailbox} | Delete a mailbox |
| PUT | /api/accounts/{mailbox}/reset-password | Reset mailbox password |
| GET | /api/accounts/{mailbox}/messages | List messages |
| GET | /api/accounts/{mailbox}/messages/{id} | Read a message |
| PUT | /api/accounts/{mailbox}/messages/{id}/read | Mark message as read |
| PUT | /api/accounts/{mailbox}/messages/read | Mark many as read |
| DELETE | /api/accounts/{mailbox}/messages/{id} | Delete a message |
| GET | /api/accounts/{mailbox}/messages/{id}/source | Download raw .eml |
| GET | /api/accounts/{mailbox}/messages/{id}/attachments/{i} | Download attachment |
| GET | /api/user/me Pro | Get profile |
| GET | /api/user/accounts Pro | List all your mailboxes |
| GET | /api/user/domains Pro | List custom domains |
| POST | /api/user/domains Pro | Add a custom domain |
| POST | /api/user/domains/{id}/verify Pro | Verify a custom domain |
| DELETE | /api/user/domains/{id} Pro | Delete a custom domain |
| GET | /api/user/tokens Pro | List API tokens |
| POST | /api/user/tokens Pro | Create an API token |
| DELETE | /api/user/tokens/{id} Pro | Revoke an API token |
| GET | /api/user/webhooks Pro | List webhooks |
| POST | /api/user/webhooks Pro | Create a webhook |
| DELETE | /api/user/webhooks/{id} Pro | Delete a webhook |
| POST | /api/user/webhooks/{id}/rotate Pro | Rotate webhook secret |
| GET | /api/user/webhooks/{id}/deliveries Pro | List webhook deliveries |
Details
List available domains
GET /api/domains — no auth.
bash
curl https://api.mail.td/api/domainsjson
{
"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:
| Header | Value |
|---|---|
X-Webhook-ID | Unique event id (same as id above) |
X-Webhook-Timestamp | Unix seconds |
X-Webhook-Signature | sha256=<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>" }.
| Status | When |
|---|---|
200 | OK |
201 | Resource created |
204 | OK, no body |
400 | Malformed request (e.g. invalid_address, invalid_domain, password_too_short, invalid_url, invalid_index) |
401 | Missing / invalid / revoked token (invalid_token, invalid_or_expired_token) |
403 | Quota or limit hit (account_limit_reached, domain_limit_reached, webhook_limit_reached) |
404 | Resource doesn't exist, or you don't own it (not_found) |
409 | Duplicate (address_taken, domain_already_exists) |
410 | Expired message or domain verification (expired, domain_expired) |
429 | Rate limited (rate_limit_exceeded) or monthly quota hit (ops_quota_exceeded) |
5xx | Server error — retry with backoff |
Your API token can only access resources you created with it — others return 404.
Rate limits
| Plan | Per-second | Create mailbox | Monthly ops quota |
|---|---|---|---|
| Free | 4 req/s per user | 1 per 8s | 1,000 |
| Pro | 10 req/s per user | 1 req/s | 100,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
| Language | Package |
|---|---|
| Python | pip install mailtd |
| Node.js | npm 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 →