Messages are scoped to an agent. The same routes accept either the master key (acting on any agent) or the agent's own per-agent key (scoped to itself).
POST/agents/:id/messages/send{
"to": ["alice@example.com"],
"cc": ["bob@example.com"],
"bcc": ["audit@example.com"],
"subject": "Welcome to the beta",
"text": "Plain text body.",
"html": "<p>HTML body.</p>",
"attachments": [
{
"filename": "report.pdf",
"contentType": "application/pdf",
"data": "JVBERi0xLjQKJ..."
}
]
}
| Field | Type | Required | Description |
|---|---|---|---|
to | string | string | Yes | One or more valid email addresses. |
cc | string | string | No | Carbon-copy recipients. |
bcc | string | string | No | Blind-carbon-copy recipients (never appear in the composed MIME). |
subject | string | Yes | 1–998 chars. |
text | string | Conditional | Required if html is omitted. Min 1 char. |
html | string | Conditional | Required if text is omitted. Min 1 char. |
attachments | object | No | Up to 10 entries. Each: filename (1–255), contentType (1–255), data (base64-encoded, ≤ ~5 MiB raw). |
Limits:
to+cc+bcc ≤ 50 recipients.From is set to the agent's own address; user-supplied From is ignored.202 Accepted (status sent or partial){
"id": "0f7d4b73-7a7f-4f6b-8a18-2a4e5e6c9c0a",
"status": "sent",
"message_id_header": "<uuid@agents.yourdomain.com>",
"recipients": [
{ "recipient": "alice@example.com", "status": "sent" }
]
}
502 Bad Gateway (status rejected){
"id": "0f7d4b73-7a7f-4f6b-8a18-2a4e5e6c9c0a",
"status": "rejected",
"message_id_header": "<uuid@agents.yourdomain.com>",
"error": "E_SENDER_NOT_VERIFIED",
"recipients": [
{ "recipient": "alice@example.com", "status": "rejected", "error": "E_SENDER_NOT_VERIFIED" }
]
}
Aggregate status | Meaning |
|---|---|
sent | Every envelope recipient succeeded. |
partial | Some recipients succeeded, others failed. The Worker continues attempting later recipients after a single failure. The message.sent webhook still fires. |
rejected | Every recipient failed. No webhook fires. |
The per-recipient outcome is also persisted to the per-agent SQLite message_recipients table so future retries can target only the failed subset.
400 — Zod validation failed (invalid email, missing subject, no body, too many recipients, oversized attachment, bad base64, body too large, MIME exceeds 25 MiB).401 — missing or bad bearer.403 — per-agent key for the wrong agent.404 — agent unknown or tombstoned.GET/agents/:id/messages| Parameter | Type | Default | Description |
|---|---|---|---|
limit | integer | 50 | Clamped to 1..100. |
offset | integer | 0 | Pagination offset. |
200 OK{
"messages": [
{
"id": "uuid",
"direction": "inbound",
"from_addr": "alice@example.com",
"to_addr": "abc123def456@agents.yourdomain.com",
"subject": "Hi",
"status": "received",
"raw_size": 1234,
"created_at": 1730000000,
"thread_id": "uuid"
}
],
"total": 1,
"limit": 50,
"offset": 0
}
The list endpoint deliberately omits body_text / body_html / message_id_header for compactness. Fetch the full record via GET /agents/:id/threads/:threadId.
401, 403, 404 as above.| Field | Type | Description |
|---|---|---|
id | string (uuid) | Per-agent message id. |
direction | "inbound" | "outbound" | |
from_addr | string | Envelope sender. |
to_addr | string | Stored recipient (for outbound, the joined envelope recipients). |
subject | string | null | |
status | string | received, sent, partial, rejected, pending. |
raw_size | integer | null | UTF-8 byte size of the raw MIME (outbound) or the parsed email (inbound). |
created_at | integer | Unix timestamp (seconds). |
thread_id | string | null | The conversation this message belongs to. |
message_id_header | string | null | RFC 5322 Message-ID (only on the per-thread GET /threads/:id). |
body_text | string | null | Plain text body (only on the per-thread GET /threads/:id). |
body_html | string | null | HTML body (only on the per-thread GET /threads/:id). |