Sending email
The relay accepts mail over REST (POST /v1/messages) or SMTP submission. Both paths run the same pipeline: tenant quotas and rates, suppression list, Guardian checks, DKIM signing, queueing, retries, and events.
POST /v1/messages
curl -X POST "$API/v1/messages" \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-8842-shipped" \
-d '{
"tenant_id": "TENANT_ID",
"from": "Shop <orders@shop.example.com>",
"to": ["customer@example.net"],
"cc": [],
"bcc": [],
"reply_to": "support@shop.example.com",
"subject": "Your order has shipped",
"text": "Your order 8842 is on its way.",
"html": "<p>Your order <b>8842</b> is on its way.</p>",
"headers": {"X-Campaign": "post-purchase"},
"attachments": [{
"filename": "invoice.pdf",
"content_type": "application/pdf",
"content": "<base64>"
}],
"tags": ["transactional", "shipping"],
"metadata": {"order_id": "8842"}
}'
| Field | Notes |
|---|---|
tenant_id |
Required with an account-wide key; implied by a tenant-bound key |
from |
Must be on a domain registered to the tenant and verified (the shared agents.mailgentic.ai domain can only be used by sending from an inbox, never as a free-form from) |
to, cc, bcc |
Arrays of addresses; total recipients capped by the tenant’s max_recipients_per_message |
text / html |
At least one required |
attachments |
Base64; up to 25 MiB total after decoding. Optional content_id and inline for embedded images |
tags |
Free-form strings; filterable in events, usable as a pause scope |
metadata |
Arbitrary JSON echoed in events and webhooks |
idempotency_key |
Alternative to the Idempotency-Key header |
Response
{ "message_id": "MESSAGE_ID", "queued": 1, "held": 0, "suppressed": 0, "duplicate": false }
202 Accepted is returned as soon as the message is durably queued.
queued— recipients placed in the delivery queueheld— recipients parked because an active pause covers them (they will go out on release)suppressed— recipients skipped because they are on the suppression listduplicate—truewhen the idempotency key was seen before; the originalmessage_idis returned and nothing new is sent
Idempotency
Any client that retries should set Idempotency-Key. Keys are scoped to the tenant and remembered long enough to cover realistic retry windows. A replay returns the original identity rather than sending a second copy.
Delivery status
GET /v1/messages/{id}
Returns { "message": {…}, "deliveries": [...] } — one delivery record per recipient:
{
"recipient": "customer@example.net",
"status": "delivered",
"attempts": 1,
"smtp_response": "250 2.0.0 OK",
"bounce_class": null,
"next_attempt_at": null,
"expires_at": "2026-10-07T10:00:00Z",
"delivered_at": "2026-10-04T10:00:03Z"
}
GET /v1/messages
Newest first. Filters: tenant_id, recipient, from, status, since, until, before (cursor), limit (default 50, max 200).
GET /v1/events
The account’s event stream, newest first. Filters: type, tenant_id, message_id, recipient, before_id, limit. Each event has an increasing numeric id, type, message_id, delivery_id, recipient, data and created_at.
| Event | Meaning |
|---|---|
accepted |
Submission accepted and queued |
held / released |
Parked by a pause / re-queued after release |
delivered |
Remote server accepted the message |
deferred |
Temporary failure; will retry until expiry |
bounced |
Permanent failure (data.bounce_class tells you why) |
blocked |
Refused by a receiving server’s policy |
complained |
Recipient reported spam (feedback loop) |
suppressed |
Recipient skipped because it is on the suppression list |
expired |
Retries exhausted |
cancelled |
Purged from a held queue |
unsubscribed |
Recipient used list-unsubscribe |
suspended / resumed |
A pause was created or lifted on a scope covering this mail |
anomaly_detected |
The Guardian flagged a pattern (see Shield and Guardian) |
auth_failed / rejected |
Submission refused (bad credential, suspended scope, policy) |
Events are also delivered by webhook.
Suppression list
Hard bounces and complaints add the recipient to the tenant’s suppression list automatically; later sends to that address are skipped and emit suppressed.
| Method | Path | Scope | Purpose |
|---|---|---|---|
GET |
/v1/suppressions |
read |
Search the list |
POST |
/v1/suppressions |
feedback |
Add an address manually |
DELETE |
/v1/suppressions/{id} |
admin |
Remove an entry |
POST |
/v1/complaints |
feedback |
Record a complaint you received out of band |
Sending from an inbox
Mail composed from an agent inbox (POST /v1/inboxes/{id}/messages, replies, forwards, drafts) goes through this same pipeline and additionally through the inbox’s Agent Shield policy. See Inboxes.
SMTP submission
Same pipeline, different door. See IMAP and SMTP for hosts, ports and credential rules.