Messages

Multipart messages

Text and photos as one message — a caption over a photo, an album, or text and photos interleaved — and why a link that fails sends nothing at all.

A multipart is one message made of several parts — text parts and photo parts, in the order you list them. The recipient gets one notification and one message in the thread: a caption above a photo, an album of up to ten photos, or text and photos interleaved. An iMessage line — live or test — sends one as a message.send whose payload has kind: "multipart".

{
  "kind": "multipart",
  "parts": [
    { "kind": "text", "text": "The three we talked about, in the order you asked:" },
    { "kind": "media", "url": "https://example.com/listings/4821/front.jpg" },
    { "kind": "media", "url": "https://example.com/listings/4821/kitchen.jpg" },
    { "kind": "media", "url": "https://example.com/listings/4821/garden.jpg" }
  ]
}

Each photo is a link. Before the message goes out, the platform fetches every link, in order, and hands each file to the line; only then is the message sent. If any one link fails, nothing is sent and the refusal names the part — a partial message is never sent.

Fields

Prop

Type

effect and subject sit beside parts, exactly as they sit beside text on a plain send. A reply is target.replyToMessageId, as on any send.

Refused before anything is written

Each of these is 422 validation_failed at the field named, and nothing is queued:

  • Fewer than 2 or more than 20 parts, more than 10 media parts, or the same link twice — at payload.parts.
  • A blank text part — at payload.parts[i].text.
  • A link that is not https, cannot be fetched, is too large, or is not a photo, video or PDF — at payload.parts[i].url. Audio is not a part: iMessage sends an audio file as a message of its own.
  • A thread the transport has not confirmed — at target.conversationId, or at the recipient key you wrote, with reason: conversation_not_confirmed.

A multipart to a line on any other channel is 404 unsupported_command with reason: not_on_channel.

Every link is fetched before anything is sent

The fetch happens inside the request, in the order of the parts, and a link that fails refuses the whole send at that part's url. So the links have to be reachable from Mapier's servers when you post — a public https address, not one behind a login or on your own network — and a large album takes longer to answer than a text does. A 202 means every file reached the line.

Where a multipart can go

A multipart travels on the line's conversation path and nowhere else — the path a thread takes once the transport has confirmed it is a conversation with that one contact, which happens the first time the contact writes into it. See which path a send takes.

Into any other thread it is refused before anything is written, at target.conversationId or the recipient key with reason: conversation_not_confirmed. Send the person a text first; once they answer, the thread is confirmed and the album goes. A group conversation is refused at target.conversationId too.

What the Mac may still refuse

What the Mac behind the line can send is not known before a multipart is sent. A Mac whose helper has not announced multipart sends is sent nothing: the command is accepted with a 202 and then settles failed on the stream with capability_constraint_violation, failureClass: capability — never several separate messages in its place. Watch the stream, and fall back to a text and a rich link rather than retrying unchanged. See Capabilities.

In test mode the sandbox phone performs a multipart of text parts and draws it as one bubble holding its text; a multipart holding a photo settles failed with unsupported_capability, because the sandbox fetches nothing. Live, each part draws as its own bubble and the photos stack into an album. Test the album against a real handset once, not against the sandbox.

A worked example

An album with a caption, into a conversation the contact opened:

curl -X POST https://api.mapier.ai/v1/commands/message.send \
  -H "Authorization: Bearer $MAPIER_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: listing-4821-album" \
  -d '{
    "target": {
      "kind": "conversation",
      "conversationId": "a0000000-0000-4000-8000-000000000001"
    },
    "payload": {
      "kind": "multipart",
      "parts": [
        { "kind": "text", "text": "The three we talked about, in the order you asked:" },
        { "kind": "media", "url": "https://example.com/listings/4821/front.jpg" },
        { "kind": "media", "url": "https://example.com/listings/4821/kitchen.jpg" },
        { "kind": "media", "url": "https://example.com/listings/4821/garden.jpg" }
      ]
    }
  }'

Checking the bounds client-side saves a round trip; the API checks them again at payload.parts before it fetches anything.

On this page