Messages

Sending messages

One command, six payload shapes, and how to choose between them.

Everything you send — a plain sentence, a link card, a poll — goes through one command: POST /v1/commands/message.send.

The body is always two objects. target says where the message goes, payload says what it is.

{
  "target": {
    "kind": "conversation",
    "conversationId": "a0000000-0000-4000-8000-000000000001"
  },
  "payload": { "text": "Your table is ready." }
}

A call the API accepts returns 202 with an id you can correlate later:

202 Accepted
{ "commandId": "cmd-1", "status": "queued", "disposition": "queued" }

202 means queued, not delivered

The API does not validate payload at the boundary. A malformed payload — an unknown kind, an effect name that does not exist, a duplicated poll option — still returns 202, then settles failed on the response stream seconds later. The 202 tells you the command was durably queued and nothing more.

The boundary checks the envelope, not the contents. The body must be a JSON object of at most 65,536 bytes carrying a target object and a payload object. Those three conditions do give you a synchronous 400body_too_large, body_not_object and target_and_payload_required respectively — before anything is queued.

The six payload shapes

payload is a union discriminated by kind. Plain text is the exception: it carries no kind at all — only text.

kindWhat it sendsTargetStatus
omittedPlain textconversation or recipientAvailable
rich_textText plus an effect, a threaded reply or a subject lineconversationAvailable
rich_linkA URL that Messages renders as a preview cardconversationAvailable
pollA question with 2–12 optionsconversationAvailable
attachmentA file or voice memoconversationAvailable
stickerA sticker, optionally pinned to a messageconversationUnavailable — sticker sends not enabled yet

Both attachment and sticker reference their media by transferId, which you get from POST /v1/media. Attachment sends work today; sticker sends are not enabled on connectors yet. See the attachments page for the stage-and-send flow.

Two ways to address a send

message.send accepts two of the three target kinds, and the choice changes more than the address.

A conversation target replies into a thread you already know about. Its id comes from an inbound message event on the stream. This is the only target that accepts rich payloads, it honours Idempotency-Key, and it has a 30-second dispatch deadline.

A recipient target opens a new thread from an E.164 phone number — email addresses work elsewhere in the API but not here, and one comes back 422 recipient_not_allowed. It takes plain text only — the server reads payload.text, drops everything else without complaining, and caps the text at 16,384 bytes — and it ignores Idempotency-Key entirely, minting a fresh one per call. A retried recipient send delivers twice.

The usual pattern is to open with a plain recipient send, wait for the conversation to appear on the stream, then use its conversationId for everything after. See Addressing a conversation.

Pick a payload

On this page