Rich links
Send a URL as a preview card, why you do not control how it looks, and why nothing can name the message afterwards.
The rich_link payload sends a single URL and asks Messages to render it as a
preview card — the tile with a title, an image and a domain, rather than a line
of blue text. An iMessage line — live or test — sends one as a message.send
whose payload has kind: "rich_link".
{
"kind": "rich_link",
"url": "https://example.com/orders/4821"
}That is the whole payload. kind and url, nothing else — a text, a
caption or any other key beside them is 422 validation_failed at that key.
URL constraints
Prop
Type
Three things are worth checking before you post:
- Absolute, with a scheme.
https://example.com/orders/4821works;example.com/orders/4821and/orders/4821do not. Onlyhttpandhttpsare accepted — nomailto:,tel:or app schemes. - 2048 characters, ceiling included. Long tracking parameters get there faster than you expect. Shorten the URL rather than trimming query strings you need.
- No control characters. Anything below
0x20, plus0x7f, is refused. If you are interpolating a URL out of user input, strip whitespace and newlines first.
You do not control the preview
This is the part that surprises people. You send a URL and only a URL. The recipient's phone fetches the destination itself and builds the card from what the page publishes — its Open Graph tags, its title, its image. There is no field for a title, a description, a thumbnail or a fallback.
Practically:
- A destination with good
og:title,og:descriptionandog:imagetags gets a full card. - A destination with none of them gets a thin card, often no more than the domain.
- A destination behind a login, a paywall or a bot-blocking WAF may render as little more than a URL — the phone is fetching the page as an anonymous client, not as your authenticated user.
If the card matters to you, the work is on the destination page, not in the payload. And test the exact URL you intend to send: a redirect to a different host can produce a different card than the final URL would.
Nothing reports back what the card looked like. A succeeded settlement means the line sent the
link, not that a preview rendered — there is no field on the response stream describing the card.
Its id
On a live line the Mac names the rich link it sent, as it names a text. The
command frame that settles the send carries Mapier's messageId and an
externalId, the line reports message.delivered (and message.read when the
phone sends one), and a later command can name the message: a tapback, a
sticker stuck to it, a reply, an edit or an unsend.
A rich link that settled with externalId: null keeps none, and no later
command can name it. That is every rich link sent before the line's Mac was
updated to name them, which also stays sent, and every rich link on a test
line.
Where a rich link can go
A rich link 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 card reaches every member. See which path a send takes.
Into any other thread it is refused before anything is written:
422 validation_failed at target.conversationId — or at the recipient key you
wrote — with reason: conversation_not_confirmed. Send the person a text
first; once they answer, the thread is confirmed and the card goes. A group
conversation the transport has not named yet is refused at
target.conversationId too. A rich link
to a line on any other channel is 404 unsupported_command with
reason: not_on_channel.
A reply is target.replyToMessageId, as on any send: a rich link can quote the
message the contact sent.
What the line may still refuse
What the line's device can send is not known before a rich link is sent. A
line whose device has not announced rich links — or replies on a rich link, for
a reply — is sent nothing: the command is accepted with a 202 and
then settles failed on the stream with capability_constraint_violation,
failureClass: capability. Watch the stream, and fall back to the URL as text
rather than retrying unchanged. See Capabilities.
In test mode the sandbox phone draws a rich link as the link beside a card naming its host, and fetches nothing. Live, the recipient's iPhone fetches the page and draws its title and picture — so what the card says is something to check on a real handset, not in the sandbox.
A worked example
A receipt link 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: order-4821-receipt-link" \
-d '{
"target": {
"kind": "conversation",
"conversationId": "a0000000-0000-4000-8000-000000000001"
},
"payload": {
"kind": "rich_link",
"url": "https://example.com/orders/4821"
}
}'Validate the URL client-side all the same: the API refuses a malformed one at
payload.url with 422 validation_failed, and a refusal caught before the
request is one fewer round trip.
Versus a URL in plain text
You can also put a URL inside an ordinary { text } payload, and it will still
be tappable. The difference is how it is sent: rich_link asks Messages to
compose the message as a link, whereas a text payload sends characters and
leaves the rendering entirely to the receiving client — and a text goes into
any thread, a confirmed one or not.
Use rich_link when the link is the message. Use plain text when the URL is
one part of a sentence — rich_link has no text field, so you cannot say
anything alongside it. If you want both, send two messages.
Related
Message effects
The 13 bubble and full-screen animations, who actually sees them — and the threads an effect can be sent into.
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.