Skip to main content

Reactions (Tapbacks)

iMessage Tapbacks let users react to a specific message with one of several short reactions — a heart, a thumbs-up, an emoji, or a sticker — instead of sending a follow-up message.

Inbound reactions (Mac → CRM) are forwarded via the standard POST /conversations/messages/inbound call with a structured reaction block in the bluesendr-metadata:v1 footer embedded in the message body.

Outbound reactions (CRM → Mac) ride inside the existing POST /crm/webhook request — a reaction block in the metadata footer tells the Mac to apply a Tapback to a specific message. See Outbound: CRM → Mac below.

Three kinds of reaction​

KindWhat it isAvailable since
classicThe original 6 Tapbacks: love, like, dislike, laugh, emphasize, question.macOS 10.12+
emojiAny emoji glyph as a reaction.macOS 15+
stickerA sticker (Memoji, Genmoji, or pack sticker) as a reaction.macOS 15+

Inbound: Mac → CRM​

Reactions arrive via POST /conversations/messages/inbound. The metadata footer embedded in the message body carries an extra reaction object:

-- bluesendr-metadata:v1 --
{
"is_group": true,
"chat_id": 680,
"recipients": ["+32456832391", "+32483119453"],
"sender": "+32456832391",
"message_guid": "8F2C6D14-A09E-4133-B84E-72F0E5C9B6A1",
...,
"reaction": {
"action": "add",
"kind": "classic",
"target_message_guid": "1A562E89-FB3A-4F24-8CCE-054FDEC1A9D7",
"type": "like"
}
}
-- end bluesendr-metadata --

The message body itself carries the iMessage system-locale fallback text (e.g. Liked "Test" in English, or Vond "Test" leuk on a Dutch Mac). This is fine as a human-readable display fallback for CRMs that don't render reactions structurally — but never key reaction logic off the body text. The structured reaction object is locale- independent and authoritative.

reaction object​

FieldAlways presentNotes
kindyes"classic" | "emoji" | "sticker". Branch on this before reading kind-specific fields below.
actionyes"add" | "remove". (Sticker reactions currently only emit "add" — Apple has not introduced a remove path.)
target_message_guidyesGUID of the message reacted to. Use this for pairing.
target_message_guid_rawyesUnstripped value, kept for debugging. Do not use for pairing.
typekind=classic onlyOne of love, like, dislike, laugh, emphasize, question.
emojikind=emoji onlyThe emoji glyph as a Unicode string (e.g. "😂"). Populated on both add and remove.
stickerkind=sticker onlyObject: see Sticker reactions.

Pairing add / remove​

A user can add a Tapback and later remove it. CRMs should pair adds with their corresponding removes by:

(target_message_guid, sender, kind, type-or-emoji-or-sticker.guid)

For classic, the type field disambiguates which of the 6 Tapbacks. For emoji, pair on kind="emoji" only — the Mac may or may not populate emoji on remove rows depending on its macOS version, so don't key off the glyph.

Sender vs contact resolution​

metadata.sender is the reactor's handle, not the original message author. The inbound contactId is resolved using the same priority as plain group messages — see Sender vs contact resolution in groups. Use contactId for contact lookup, metadata.sender for "Bob reacted to your message" attribution.

Inbound example: classic add​

POST /conversations/messages/inbound
{
"type": "SMS",
"contactId": "<crm contact id>",
"locationId": "<crm location id>",
"message": "Liked \"Test\"\n\n-- bluesendr-metadata:v1 --\n{\"chat_id\":680,\"is_group\":true,\"message_guid\":\"8F2C6D14-A09E-4133-B84E-72F0E5C9B6A1\",\"reaction\":{\"action\":\"add\",\"kind\":\"classic\",\"target_message_guid\":\"1A562E89-FB3A-4F24-8CCE-054FDEC1A9D7\",\"type\":\"like\"},\"recipients\":[\"+32456832391\",\"+32483119453\"],\"sender\":\"+32456832391\",\"service_name\":\"iMessage\"}\n-- end bluesendr-metadata --"
}

Inbound example: classic remove​

POST /conversations/messages/inbound
{
"type": "SMS",
"contactId": "<crm contact id>",
"locationId": "<crm location id>",
"message": "Removed a like from \"Test\"\n\n-- bluesendr-metadata:v1 --\n{\"chat_id\":680,\"is_group\":true,\"message_guid\":\"E0476BBA-9F2C-4A8D-B0E5-3CD8164F291B\",\"reaction\":{\"action\":\"remove\",\"kind\":\"classic\",\"target_message_guid\":\"1A562E89-FB3A-4F24-8CCE-054FDEC1A9D7\",\"type\":\"like\"},\"recipients\":[\"+32456832391\",\"+32483119453\"],\"sender\":\"+32456832391\",\"service_name\":\"iMessage\"}\n-- end bluesendr-metadata --"
}

The CRM should pair this with the previous example to update the reaction state on the target message.

Inbound example: emoji​

reaction block
{
"action": "add",
"kind": "emoji",
"emoji": "😂",
"target_message_guid": "1A562E89-FB3A-4F24-8CCE-054FDEC1A9D7",
"target_message_guid_raw": "p:0/1A562E89-FB3A-4F24-8CCE-054FDEC1A9D7"
}

Body fallback: Reacted 😂 to "Test".

Sticker reactions​

Inbound sticker reactions also include the rendered sticker as an attachment URL in the attachments[] array on the POST /conversations/messages/inbound payload:

reaction block
{
"action": "add",
"kind": "sticker",
"sticker": {
"guid": "E6D84270-1AF3-4815-ACB1-79D58E91646E",
"mime_type": "image/heic",
"name": "E6D84270-1AF3-4815-ACB1-79D58E91646E.heic",
"short_description": "Sheep waiting in stable, Sheep stable"
},
"target_message_guid": "0AF18B70-3378-43A9-A3FB-AFF3CF1167F9",
"target_message_guid_raw": "bp:0AF18B70-3378-43A9-A3FB-AFF3CF1167F9"
}
FieldAlways presentNotes
guidyesLocal sticker identifier on the originating Mac. Not portable — do not echo back as a reference for outbound.
mime_typebest-effortE.g. "image/heic", "image/png". Best-effort from the source.
namebest-effortFilename.
short_descriptionbest-effortHuman-readable description (often the alt-text for Memoji / Genmoji stickers).

The attachments[] array on the same inbound payload carries a URL to the rendered sticker image, suitable for inline rendering. BlueSender converts non-web-friendly formats (HEIC stickers, animated PNG stickers) to widely-renderable formats (PNG, animated GIF) before upload, so CRMs don't have to handle native iMessage formats.

The structured reaction.sticker block remains the authoritative discriminator; attachments[] is the renderable sibling.

Localization​

The body text is iMessage-generated and follows the system locale of the originating Mac. Examples:

EnglishDutch
Liked "ABCDEFG"Vond "ABCDEFG" leuk
Loved "ABCDEFG"Gaf een hartje aan 'ABCDEFG'
Removed a like from "ABCDEFG"Verwijderde een vind-ik-leuk uit 'ABCDEFG'
Reacted 😂 to "X"Reageerde met 😂 op "X"
Sent a sticker to "X"(locale-equivalent)

Use the structured reaction object for logic; use the body only for fallback display.

Outbound: CRM → Mac​

CRM-originated reactions ship inside the existing POST /crm/webhook request — there is no separate endpoint to call. The Mac detects a reaction block in the bluesendr-metadata:v1 footer of the message body and applies the Tapback, returning a message_status ack on the same channel a regular send-message would use.

Wire contract​

POST /crm/webhook
{
"locationId": "<crm location id>",
"contactId": "<crm contact id>",
"phone": "<recipient E.164>",
"messageId": "<your correlation id, becomes hlMessageId on the ack>",
"message": "-- bluesendr-metadata:v1 --\n{\"chat_id\":649,\"reaction\":{\"action\":\"add\",\"kind\":\"classic\",\"target_message_guid\":\"1A562E89-FB3A-4F24-8CCE-054FDEC1A9D7\",\"type\":\"like\"}}\n-- end bluesendr-metadata --"
}

Top-level fields are identical to a regular send-message webhook — phone, contactId, and messageId are still required for routing and ack correlation. The message field can be the metadata block on its own, or you can prefix any display text (e.g. Liked "ABC") that the CRM should show in its conversation log — anything before the -- bluesendr-metadata:v1 -- line is purely for CRM display and is not included on the Tapback itself.

The base64-wrapped sentinel form [[BSMD:v1:<base64-of-the-JSON>]] is also accepted by the Mac parser if you'd rather not embed the delimited block inline.

reaction block fields​

Same shape as the inbound reaction object — see reaction object above for the full table. The minimum required fields per kind:

KindRequired fields
classickind, action, target_message_guid, type
emojikind, action, target_message_guid, emoji
stickerkind, action, target_message_guid, plus a sticker block with either bytes (base64-encoded image) or url (HTTPS image URL — fetched on demand by the Mac). Optional: sticker.name — a human-readable label surfaced in logs and macOS VoiceOver only. Does not cause a lookup against the user's installed stickers; the Mac always renders the image from the supplied bytes / url. If both bytes and url are provided, bytes wins (no extra network round-trip). If neither is provided, the Mac acks INVALID_KIND_FIELD_COMBO.

target_message_guid is the GUID delivered to the CRM via:

  • the inbound reaction.target_message_guid field on a previous message_received (when reacting to a message that was itself a reaction's target), or
  • the inbound message_guid field on a previous message_received (when reacting to a plain message a customer just sent — including in 1:1 chats), or
  • the outbound message's own GUID when the CRM is reacting to one of its own sends.

The chat_id field on the metadata footer is required so the Mac knows which conversation to open; it can be reused verbatim from the inbound metadata of a prior message in the same chat.

Acks​

Acks reuse the standard message_status channel keyed by hlMessageId (= the messageId you sent in the request). Status values:

  • delivered — the requested end-state is now true on the Mac.
  • failed with one of the codes below.

Idempotency​

Outbound reaction requests are idempotent and direction-honoring: the requested action is treated as authoritative, and a request whose desired end-state is already true is acked delivered as a no-op.

RequestReaction currently present?Ack
addyesdelivered (no-op)
addnodelivered once applied (or failed)
removeyesdelivered once applied (or failed)
removenodelivered (no-op)

Practical consequences for CRMs:

  • Safe to retry. Resending an add or remove after a transient network error or stale local state will not flip the reaction to the wrong direction.
  • action is authoritative. Pair add/remove purely on (target_message_guid, kind, type-or-emoji) — a delivered ack means the desired end-state holds.
  • Apple's single reaction slot still applies. A user can only have one active reaction per message at a time, regardless of kind. Sending add 😂 against a message that currently shows ❤️ from the same user replaces the heart, exactly as it would on a phone.
  • Scope is per-(kind, type-or-emoji). add love and add like on the same target are independent — adding a like to a message that currently carries a love from the same user is a state change, not a no-op.

Constraints and error codes by kind​

KindStatusFailure codes
classicFully supported. add and remove honor direction (see Idempotency above).INVALID_FRAME, UI_AUTOMATION_DISABLED, plus generic dispatch codes from the Mac-side dispatch table.
emojiFully supported on macOS 26+. Recently-used glyphs land instantly via the inline tapback palette; any other emoji falls through to the full system emoji picker (slower path, same outcome).EMOJI_NOT_IN_RECENTS when neither the inline palette nor the system picker fallback can land the glyph (legacy name; treat as "couldn't deliver — fall back"). May fire pre- or post-dispatch (see Error codes). Plus the codes above.
stickerFully supported. Provide the image as inline base64 in reaction.sticker.bytes or as an HTTPS URL in reaction.sticker.url (the Mac fetches it).STICKER_DOWNLOAD_FAILED when a url fetch hits a network failure or non-2xx HTTP status. STICKER_ENCODE_FAILED when the supplied bytes (inline or downloaded) are not a decodable image. STICKER_NOT_FEASIBLE for a build-time kill-switch or when the post-gesture chat-state probe didn't observe a new sticker reaction within the budget. INVALID_KIND_FIELD_COMBO when neither bytes nor url is provided. Treat dispatch failures the same way as other reaction dispatch failures — retry briefly or fall back to a classic Tapback. Plus the codes above.
other / unknown—NOT_IMPLEMENTED.

Outbound example: classic add​

POST /crm/webhook
{
"locationId": "loc_abc",
"contactId": "ct_123",
"phone": "+15555550100",
"messageId": "react-001",
"message": "-- bluesendr-metadata:v1 --\n{\"chat_id\":649,\"reaction\":{\"action\":\"add\",\"kind\":\"classic\",\"target_message_guid\":\"1A562E89-FB3A-4F24-8CCE-054FDEC1A9D7\",\"type\":\"like\"}}\n-- end bluesendr-metadata --"
}

Outbound example: emoji add​

reaction block
{
"action": "add",
"kind": "emoji",
"emoji": "😂",
"target_message_guid": "1A562E89-FB3A-4F24-8CCE-054FDEC1A9D7"
}

Outbound example: classic remove​

reaction block
{
"action": "remove",
"kind": "classic",
"target_message_guid": "1A562E89-FB3A-4F24-8CCE-054FDEC1A9D7",
"type": "like"
}

The CRM should pair this with the previous add request on (target_message_guid, kind, type). Mac will ack message_status: delivered once the requested end-state holds — see Idempotency.

Outbound example: sticker add​

reaction block
{
"action": "add",
"kind": "sticker",
"target_message_guid": "1A562E89-FB3A-4F24-8CCE-054FDEC1A9D7",
"sticker": {
"bytes": "<base64-encoded image>",
"name": "Party Popper"
}
}

Sticker reactions are add-only — Apple's iMessage doesn't model removing a sticker reaction, so the Mac will reject action: "remove" with INVALID_KIND_FIELD_COMBO.

The sticker.bytes field carries the exact image to apply as the reaction (HEIC / PNG / JPEG; the Mac re-encodes as needed). Same size considerations as the standalone-sticker bytes field — see Stickers → sticker block fields.

As an alternative, sticker.url accepts an HTTPS image URL. The Mac fetches it on demand, then runs the same dispatch path as the inline bytes form. Non-2xx HTTP responses ack STICKER_DOWNLOAD_FAILED; URLs that return non-image bodies ack STICKER_ENCODE_FAILED. If both bytes and url are provided, bytes wins and no network call is made.

reaction block (URL form)
{
"action": "add",
"kind": "sticker",
"target_message_guid": "1A562E89-FB3A-4F24-8CCE-054FDEC1A9D7",
"sticker": {
"url": "https://cdn.example.com/stickers/party-popper.png",
"name": "Party Popper"
}
}

Pasting into a CRM message composer​

If you're triggering reactions manually from the CRM's iMessage composer (or copy-pasting a test payload during integration), paste the literal multi-line body below — the CRM will forward it to BlueSender as a regular outbound message and the Mac will detect the metadata block and apply the requested Tapback.

Replace chat_id with the chat the target message belongs to (carry it over from a recent inbound message's metadata) and target_message_guid with the GUID of the message you want to react to. Optionally prefix any display text before the -- bluesendr-metadata:v1 -- line — that text shows up in the CRM conversation log but is not included on the Tapback.

Classic add — like
-- bluesendr-metadata:v1 --
{
"chat_id": 660,
"reaction": {
"action": "add",
"kind": "classic",
"target_message_guid": "7EE3FD68-C2D6-4066-9623-2598202C8A42",
"type": "like"
}
}
-- end bluesendr-metadata --
Classic remove — dislike
-- bluesendr-metadata:v1 --
{
"chat_id": 660,
"reaction": {
"action": "remove",
"kind": "classic",
"target_message_guid": "7EE3FD68-C2D6-4066-9623-2598202C8A42",
"type": "dislike"
}
}
-- end bluesendr-metadata --
Emoji add
-- bluesendr-metadata:v1 --
{
"chat_id": 660,
"reaction": {
"action": "add",
"kind": "emoji",
"emoji": "😂",
"target_message_guid": "7EE3FD68-C2D6-4066-9623-2598202C8A42"
}
}
-- end bluesendr-metadata --

The remaining classic type values (love, like, dislike, laugh, emphasize, question) follow the same shape — swap the type field. For emoji, swap the emoji field; the Mac uses the inline tapback palette for recently-used glyphs and falls through to the full system emoji picker for everything else, so any emoji should land on macOS 26+. The rare edge cases ack EMOJI_NOT_IN_RECENTS.

CRM responsibilities checklist​

  • Strip the metadata footer from displayed message bodies.
  • Branch on reaction.kind before reading kind-specific fields.
  • Pair add and remove by (target_message_guid, sender, kind, type-or-glyph-or-sticker.guid).
  • Treat the structured reaction object as authoritative — never key off the body text (it's localized).
  • Display metadata.sender as the reactor, not the original message author.
  • Ignore unknown fields and unknown kind values rather than rejecting the payload — the contract may add new fields or kinds over time.
  • Fall back gracefully on dispatch-failure codes when sending outbound reactions — EMOJI_NOT_IN_RECENTS (neither the inline tapback palette nor the full system emoji picker could land the requested glyph; rare on macOS 26+), STICKER_DOWNLOAD_FAILED (URL fetch hit a network error or non-2xx status), STICKER_ENCODE_FAILED (supplied bytes or downloaded content wasn't a decodable image), and STICKER_NOT_FEASIBLE (real dispatch failure or kill-switched build).