curl --request POST \
--url https://api.inboxbase.ai/v1/identities/{handle}/send \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"to": "morgan@northwindrobotics.com",
"subject": "Quick intro — fleet rotation",
"text": "Hi Morgan, ...",
"noReplyEventAfter": "4h"
}
'{
"status": "queued",
"identity": "<string>",
"queued": 123,
"rejected": 123,
"results": [
{
"to": "jsmith@example.com",
"status": "queued",
"pendingId": "<string>",
"sendClass": "cold_first_contact",
"pinnedAccountId": "<string>",
"dispatchAt": 123,
"dispatchAtIso": "2023-11-07T05:31:56Z",
"convId": "<string>",
"reason": "do_not_contact"
}
]
}{
"error": "<string>",
"details": {}
}{
"error": "<string>",
"details": {}
}{
"error": "<string>",
"details": {}
}{
"error": "<string>",
"details": {}
}{
"error": "<string>",
"details": {}
}Send a message
Queue a new conversation or a reply. Idempotent on Idempotency-Key.
curl --request POST \
--url https://api.inboxbase.ai/v1/identities/{handle}/send \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"to": "morgan@northwindrobotics.com",
"subject": "Quick intro — fleet rotation",
"text": "Hi Morgan, ...",
"noReplyEventAfter": "4h"
}
'{
"status": "queued",
"identity": "<string>",
"queued": 123,
"rejected": 123,
"results": [
{
"to": "jsmith@example.com",
"status": "queued",
"pendingId": "<string>",
"sendClass": "cold_first_contact",
"pinnedAccountId": "<string>",
"dispatchAt": 123,
"dispatchAtIso": "2023-11-07T05:31:56Z",
"convId": "<string>",
"reason": "do_not_contact"
}
]
}{
"error": "<string>",
"details": {}
}{
"error": "<string>",
"details": {}
}{
"error": "<string>",
"details": {}
}{
"error": "<string>",
"details": {}
}{
"error": "<string>",
"details": {}
}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.
{
"to": "morgan@northwindrobotics.com",
"subject": "Quick intro",
"text": "...",
"labels": ["campaign:Q4-launch", "priority:hot"]
}
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.
{
"to": "morgan@northwindrobotics.com",
"subject": "Re: Fleet rotation",
"text": "Following up...",
"inReplyTo": "<original-message-id@northwindrobotics.com>",
"references": ["<original-message-id@northwindrobotics.com>"]
}
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.
Show child attributes
Show child attributes