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:
{ "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 400 —
body_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.
kind | What it sends | Target | Status |
|---|---|---|---|
| omitted | Plain text | conversation or recipient | Available |
rich_text | Text plus an effect, a threaded reply or a subject line | conversation | Available |
rich_link | A URL that Messages renders as a preview card | conversation | Available |
poll | A question with 2–12 options | conversation | Available |
attachment | A file or voice memo | conversation | Unavailable — needs an upload endpoint that does not exist |
sticker | A sticker, optionally pinned to a message | conversation | Unavailable — same reason |
The two unavailable kinds both require a transferId naming media already
staged in Mapier storage, and /v1 exposes no way to produce one. Their
schemas are documented on the attachments
page so you know what to expect when they
land, but you cannot use them today.
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 a phone number or email. It
takes plain text only — the server reads payload.text and drops everything
else without complaining — 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
Text and rich text
The workhorse: plain text, byte limits, threaded replies and subject lines.
Message effects
The 13 bubble and full-screen effects, and who actually sees them.
Rich links
Send a URL as a preview card you do not control.
Polls
Create a poll — and what you cannot do with it afterwards.
Attachments and stickers
Not yet sendable, but receiving attachments works today.