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 — atpayload.parts[i].url. Audio is not a part: iMessage sends an audio file as a message of its own. - A thread the transport has neither confirmed nor reported as a group chat —
at
target.conversationId, or at the recipient key you wrote, withreason: 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, and the path a group chat the transport has reported takes, where the message reaches every member. 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 the
transport has not named yet is refused at target.conversationId too.
What the line may still refuse
What the line's device can send is not known before a multipart is sent. A
line whose device 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, it arrives as one message whose parts draw as separate
bubbles, the photos stacked 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" \
-H "x-mapier-version: 2026-10-15" \
-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.
Related
Stickers
Stick one emoji, or an image you link to, onto a message — placed, scaled and rotated — or send it as a bubble of its own, and what a line that cannot draw one answers.
Location
Ask a contact to share their location with a request card in Messages, and receive what they share as events you opt into.