Documentation
API reference

API reference — the mails.ai REST endpoints

12 min read

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 the cursor from the previous page.
  • Idempotency: send an Idempotency-Key header on POST /v1/messages and POST /v1/messages/batch. Replays within 24h return the original response with Idempotent-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 or agt_ 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_text and/or body_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; id may be a sent (msg_) or received (rcv_) message. Sets the In-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 new to (required), with optional subject / body override.
  • POST /v1/messages/{id}/cancel (manage) — cancel a still-scheduled message.
  • PATCH /v1/messages/{id} (manage) — reschedule a scheduled message to a new scheduled_at.
  • GET /v1/messages/{id}/raw (read) — the original message/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 required attestation(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.