Send a message
Queue a new conversation or a reply. Idempotent on Idempotency-Key.
pendingId you can correlate with the downstream
email.sent webhook (or email.send_failed_permanently on a hard fail).
The body has two shapes — new conversation or reply — that are
mutually exclusive. New conversations need to and subject; replies
need convId and we resolve recipient, subject (with Re:), threading
headers, and the previously-pinned mailbox from the existing thread.
text and html can both be set; recipients see the multipart message
their client prefers. Most production senders set both.
Send classification
The response includessendClass, which tells you how we routed:
-
cold_first_contact— first message ever from this identity to this recipient. We pace these with the identity’s drip schedule and honor the working-hours window. The first available mailbox to claim the row owns the recipient from then on; subsequent sends use the same mailbox. -
cold_followup— second-or-later cold message to a recipient who hasn’t replied in the last 3 outbounds. Same drip + working hours as a first contact, but pinned to a known mailbox. -
warm— replying to a recipient who responded within their last 3 outbounds. Bypasses drip pacing and the working-hours window — responsive recipients get answered as fast as the dispatcher fires.
pinnedAccountId is the backing mailbox id (only meaningful for
cold_followup and warm; null on cold_first_contact until a
mailbox claims the row).
Labels
Label the conversation at queue time by passinglabels: [...].
Idempotent on the conv (re-labeling an already-labeled conv is a
no-op), so sequencer flows can pass the same campaign label on every
step.
GET /identities/:id/labels/stats.
Free-form strings; we recommend a key:value convention so labels
filter cleanly on prefix.
No-reply nudges
PassnoReplyEventAfter (duration string "1d" / "4h" / "30m" or a
raw number of milliseconds) to schedule an email.no_reply webhook +
event when no inbound lands on the conversation within that window.
Default: 1 day. Floor: 60 seconds.
Idempotency
PassIdempotency-Key to make retries safe. Same key with the same
body returns the original response (header Idempotent-Replayed: true); same key with a different body returns 409. Keys live for 24
hours, scoped per-org.
For sequencers the right key is <leadId>:<step> — if your worker
crashes after we accepted the send but before your DB recorded it, the
retry returns the same response and you don’t double-send.
Stitching to external threads
If the conversation started outside 12m (an imported CRM history, forwarded mail), passinReplyTo and references on the new-conv
shape. We thread the new send onto the external chain.
Status codes
202 Accepted— at least one row was queued. The body’sresults[]array tells you per-recipient outcome.429 Too Many Requests— every recipient on the request was rejected (do_not_contact,no_eligible_pool,inactive,suspended). Inspectresults[i].reasonto discriminate.409 Conflict—Idempotency-Keyreused with a different request body.
What we don’t support yet
- Scheduled sends. No
sendAt. We pace cold sends via the identity scheduling config (working hours, drip interval); for one-shot future sends, run a scheduler in your own infra and call at fire time. - Attachments. Not supported.
- Custom headers. No way to set
List-Unsubscribe/List-Unsubscribe-Posttoday. High-priority because of bulk-sender requirements; we’ll ship it.
Authorizations
Bearer authentication header of the form Bearer <token>, where <token> is your auth token.
Headers
Up to 255 characters. Same key with same body returns the original response (with Idempotent-Replayed: true); same key with a different body returns 409. Keys live 24 hours, scoped per-org.
255Path Parameters
Identity handle, URL-encoded.
"alice.acme@inboxbase.ai"
Body
- Option 1
- Option 2
Recipient address. Pass an array to send to multiple recipients; each becomes its own conversation.
Subject line.
1Plain-text body. At least one of text / html is required.
HTML body.
Message-ID of an external message you're stitching to.
Cumulative References chain for stitching to an external thread.
A duration string ("4h", "3 days", "90 minutes", "1.5h") or a raw number interpreted as milliseconds. Floor 1 minute.
"4h"
Conversation tags applied at queue time (e.g. ["campaign:Q4-launch", "priority:hot"]). Idempotent — a tag already on the conv is a no-op. Pivot label-rollup analytics off these via /identities/:id/labels/stats.
241 - 120Response
At least one recipient accepted.
Queue-first send response. The dispatcher delivers each row asynchronously; correlate pendingId with the downstream email.sent webhook to know when delivery actually fired.
queued if at least one recipient queued; rejected if the whole batch was rejected.
queued, rejected Identity handle echoed back.
Count of recipients successfully queued.
Count of recipients rejected.