API reference — the mails.ai REST endpoints
The mails.ai API is REST over HTTPS with JSON request and response bodies. The base URL is https://api.mails.ai and all endpoints live under the /v1 prefix. Every request authenticates with Authorization: Bearer mk_live_… (see Authentication) and every error follows one envelope.
Conventions
- IDs are prefixed ULIDs —
msg_,rcv_,evt_,agt_,thrd_,dft_,whe_,key_,sup_. They sort by creation time. - Lists are cursor-paginated: pass
limit(1–100, default 25) and thecursorfrom the previous page. - Idempotency: send an
Idempotency-Keyheader onPOST /v1/messagesandPOST /v1/messages/batch. Replays within 24h return the original response withIdempotent-Replay: true. - Request IDs: every response carries an
X-Request-Idheader (also in the error body) — include it when contacting support.
Messages
POST /v1/messages — send
Requires the send scope. Sends one message immediately, or schedules it if you pass a future scheduled_at. Request fields:
agent(required) — agent name oragt_id to send from.to(required) — a recipient string, or an array of up to 50.cc,bcc— arrays of up to 50 each.subject(required) — up to 998 characters.body_textand/orbody_html— at least one is required.reply_to— override the reply-to address.in_reply_to_message_id,references— thread a message manually.attachments— up to 10, each{ filename, content_base64, content_type }; 25 MB total decoded.metadata— up to 64 string key/value pairs, echoed back on events.scheduled_at— ISO-8601; a future time schedules the send.tags— up to 10{ name, value }pairs for your own filtering.
curl https://api.mails.ai/v1/messages \
-H "Authorization: Bearer $MAILS_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-2741-confirmation" \
-d '{
"agent": "sarah",
"to": "user@example.com",
"subject": "Your order shipped",
"body_text": "Tracking: 1Z999AA10123456784",
"metadata": { "order_id": "2741" }
}'Returns 201 with the sent message:
{
"id": "msg_01J9X2K7Q8ABCDEF...",
"agent_id": "agt_01J9X2K7Q8...",
"thread_id": "thrd_01J9X2K7Q8...",
"to": ["user@example.com"],
"subject": "Your order shipped",
"routing_pool": "clean",
"classifier_score": 0.03,
"status": "sent",
"cost_usd": 0.0001,
"tags": [],
"created_at": "2026-06-19T17:25:11.004Z"
}status is sent now, or scheduled with a scheduled_at field if you scheduled it. Common errors: agent_not_found (404), agent_paused (422), recipient_suppressed (422), workspace_not_approved (403), a *_limit_exceeded (429), or upstream_error (502) if the mail provider rejects the send.
POST /v1/messages/batch — batch send
Requires send. Takes an array of up to 100 message objects (the same fields as a single send). Per-message failures do not fail the batch — each result is either the sent message or an inline error, in order. A single Idempotency-Key covers the whole batch.
{
"data": [
{ "id": "msg_01J9...", "status": "sent", "to": ["a@example.com"], "...": "..." },
{ "error": { "type": "invalid_request_error", "code": "recipient_suppressed",
"message": "Recipient is suppressed.", "param": "to" } }
],
"batch_size": 2
}GET /v1/messages & GET /v1/messages/{id}
Requires read. List sent messages (filter by agent_id, thread_id; cursor-paginated), or fetch one by id with its full bodies, delivery timestamps (delivered_at, bounced_at, complained_at), metadata, and tags.
Reply, forward, cancel, raw
POST /v1/messages/{id}/reply(send) — reply in-thread;idmay be a sent (msg_) or received (rcv_) message. Sets theIn-Reply-To/References/Re:headers for you. Body:body_text/body_html,reply_all,cc,bcc,scheduled_at.POST /v1/messages/{id}/forward(send) — forward to newto(required), with optionalsubject/ body override.POST /v1/messages/{id}/cancel(manage) — cancel a still-scheduledmessage.PATCH /v1/messages/{id}(manage) — reschedule a scheduled message to a newscheduled_at.GET /v1/messages/{id}/raw(read) — the originalmessage/rfc822.eml.
Received mail
GET /v1/messages/received & /{id}
Requires read. Inbound mail is read-only over the API — messages arrive through the parsing pipeline and surface here (and as message.received events). The list gives excerpts and parse status; the single-message endpoint adds the full body_text, body_html, and a raw_url.
{
"id": "rcv_01J9X2K7Q8...",
"agent_id": "agt_01J9...",
"thread_id": "thrd_01J9...",
"from": { "address": "user@example.com", "name": "Jordan Lee" },
"to": "sarah@acme.mails.ai",
"subject": "Re: Your order shipped",
"is_thread_reply": true,
"in_reply_to_message_id": "msg_01J9...",
"body_text_excerpt": "Thanks — got it. One question…",
"spf_pass": true,
"dkim_pass": true,
"parse_status": "parsed",
"received_at": "2026-06-19T18:02:44.000Z"
}Events & streaming
Requires read. GET /v1/events lists typed events (filter by event_type, agent_id, since). GET /v1/events/streamis a Server-Sent Events live tail. Full detail — event types, payloads, reconnection — is in the events & streaming guide.
Webhooks
POST /v1/webhooks (manage) registers an HTTPS endpoint for events. Body: url (required, https://, SSRF-checked), event_types (default ["*"]), description. The response includes a signing_secret (whsec_…) shown once. Manage with GET / PATCH / DELETE /v1/webhooks/{id}, fire a synthetic event with POST /v1/webhooks/{id}/test, and inspect the delivery log at GET /v1/webhooks/{id}/deliveries. Signature verification is covered in the webhooks guide.
Agents
POST /v1/agents (manage) creates a sending identity. Only name is required ([a-z0-9._-], ≤32). Optional: domain, allowlist_domains, blocklist_domains, daily_send_limit, hourly_send_limit, classify_inbound.
{
"id": "agt_01J9X2K7Q8...",
"name": "sarah",
"email": "sarah@acme.mails.ai",
"domain": "acme.mails.ai",
"workspace_id": "wsk_01J9...",
"status": "active",
"created_at": "2026-06-19T17:24:08.512Z"
}GET /v1/agents lists; GET / PATCH / DELETE /v1/agents/{id} fetch, update (status active/paused, limits, allow/block lists, classify_inbound), or archive.
API keys
POST /v1/api-keys (manage) mints a key — body { name?, agent_id?, scopes, mode, expires_at? }; the plaintext key is returned once. GET /v1/api-keys lists (prefix only, never the plaintext); DELETE /v1/api-keys/{id} revokes. See Authentication.
Suppression
GET /v1/suppression(read) — with?address=, looks up whether an address is suppressed and why; without, lists your allowlist.POST /v1/suppression/allow(manage) — allow an address back, with a requiredattestation(20–2000 chars) that you have consent.DELETE /v1/suppression/allow/{id}(manage) — revoke an allowlist entry.
Also available
Beyond the core surface above: GET /v1/me (the calling key’s identity), GET /v1/reputation (per-agent scores), GET /v1/threads and /{id} (conversation grouping), POST /v1/drafts and /{id}/send (staged + scheduled sends), GET /v1/billing/usage (current-period meters), and GET /v1/logs (workspace audit log). Each follows the same auth, pagination, and error conventions above.