message.send

Send text, rich text, a link preview or a poll into a conversation, or start a new one with a recipient.

POST /v1/commands/message.send

Sends a message. The payload is a union discriminated by kind; plain text omits kind entirely.

Target

Accepts a conversation target (reply into an existing thread) or a recipient target (start a new one).

A group is addressed as a conversation — kind: "conversation" with the group's conversationId — not as its own target kind. That is why the capability line below names three flags for two target kinds: a DM conversation and a group conversation are granted separately. See Capabilities.

Prop

Type

A recipient target sends plain text only, and it fails quietly rather than loudly. The endpoint forwards payload.text and discards every other key — so a recipient send carrying kind: "rich_text" and an effect returns 202 and delivers the bare text, with the effect silently dropped. You only get a 400 when recipientExternalId or text is missing entirely.

Two more limits are specific to this target and are enforced synchronously, so they come back as 422 rather than on the stream: the address must be an E.164 phone number (an email is recipient_not_allowed), and the text is trimmed and capped at 16,384 bytes rather than the 65,536 a conversation send allows (invalid_text).

To send rich content to someone new: send plain text first, wait for the conversation to appear on the stream, then send into it with a conversation target.

Routing path: a conversation target uses the agent-turn path and honours Idempotency-Key. A recipient target uses the admin path and does not.

Payload

Text

The default. No kind field.

Prop

Type

{ "text": "hello" }

rich_text

Text plus reply threading, a screen effect, or a subject line.

Prop

Type

{
  "kind": "rich_text",
  "text": "Congratulations",
  "effect": "confetti",
  "replyToMessageExternalId": "guid-1"
}

The payload is closed: rich_text accepts the five keys above and rejects any other. In particular there is no per-range styling field — no bold, italic or per-word animation — so the whole body arrives in one style.

Sends a URL that iMessage renders as a link preview.

Prop

Type

{ "kind": "rich_link", "url": "https://example.com/launch" }

poll

Prop

Type

{
  "kind": "poll",
  "question": "Lunch?",
  "options": ["Tacos", "Ramen", "Salad"]
}

Reading votes back is covered in Polls.

attachment

Prop

Type

The transferId is the id returned by POST /v1/media: stage the file there first, then put its transferId here to attach it — a command body never carries bytes. The filename's extension sets how it renders: a .vcf arrives as a contact card, a .jpg as an image. A send whose transferId has not landed yet settles failed on the response stream.

sticker

Not yet available

You can stage sticker bytes with POST /v1/media, but connectors do not yet advertise sticker sends, so this payload settles failed on the stream. Attachments send today.

Prop

Type

Request

curl $MAPIER_BASE_URL/v1/commands/message.send \
  -H "Authorization: Bearer $MAPIER_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-2481-shipped" \
  -d '{
    "target": {
      "kind": "conversation",
      "conversationId": "a0000000-0000-4000-8000-000000000001"
    },
    "payload": { "text": "Your order shipped." }
  }'

Starting a new conversation instead:

{
  "target": { "kind": "recipient", "recipientExternalId": "+14155550100" },
  "payload": { "text": "hi" }
}

Response

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

Then, on the response stream:

event: command — delivered
{
  "commandId": "cmd-1",
  "logicalActionId": "order-2481-shipped",
  "status": "succeeded",
  "errorCode": null
}
event: command — reply target too old
{
  "commandId": "cmd-1",
  "logicalActionId": "order-2481-shipped",
  "status": "failed",
  "errorCode": "stale_target"
}

Errors

StatusCodeCause
400recipient_send_requires_recipientExternalId_and_textRecipient target missing recipientExternalId or text
400invalid_conversationIdconversationId is not a UUID
404conversation_not_foundUnknown conversation, or another account's
422recipient_not_allowedRecipient target address is not an E.164 phone number
422invalid_textRecipient target text is empty or over 16,384 bytes
503no_live_connectorConversation target: no Mac holds a live lease
503connector_unavailableRecipient target: no Mac holds a live lease
503outbound_pausedRecipient target: sending is administratively paused

Settlement codes you will see for this command: stale_target (reply target aged out), invalid_target, gateway_rejected, send_returned_not_ok, outbound_echo_unconfirmed. See Error codes.

Capabilities

Requires command.message_send.conversation.v1, .group.v1 or .recipient.v1 depending on the target. Rich kinds additionally require command.message_send.content.v1 with the kind listed in its supportedKinds. See Capabilities.

On this page