im.messages sends, reads, mutates, and subscribes to messages.
Get a chat.guid before calling message APIs. chat.guid is the server’s chat identifier. It is not an email address or phone number. If you only have a recipient address, create or resolve the chat first with im.chats.create(...).
| Scenario | Argument shape |
|---|---|
| Send, read, edit, or unsend in one chat | First argument is chat.guid |
| List messages in one chat | listInChat(chat.guid, options) |
| Subscribe to one chat’s message events | subscribeEvents({ chat: chat.guid }) |
| List recent messages across chats | listRecent(options), no chat.guid |
What You Can Do
| Need | Use |
|---|---|
| Send plain text | im.messages.sendText(chat.guid, text) |
| Add a message effect | effect: MessageEffect.* |
| Format text | formatting: [...] |
| Send an attachment | Upload with im.attachments.upload(...), then call im.messages.sendAttachment(...) |
| Send a card that opens your iMessage extension | im.messages.sendCustomizedMiniApp(chat.guid, message) |
| Reply to a message | replyTo on sendText(...), sendAttachment(...), or sendMultipart(...) |
| Send multipart content | im.messages.sendMultipart(...) |
| Add or remove a reaction | im.messages.setReaction(...) |
| Place a sticker | im.messages.placeSticker(...) |
| Edit a message | im.messages.edit(...) |
| Unsend a message | im.messages.unsend(...) |
| Notify anyway | im.messages.notifySilenced(...) |
| Get or list messages | get(...), listRecent(...), listInChat(...) |
| Subscribe to message events | im.messages.subscribeEvents(...) |
Send Text
sendText(...) returns Message. text is trimmed by the server and must not be empty after trimming.
Common Message fields:

Message Effects
Message effects apply to the whole outgoing message: confetti, fireworks, slam, invisible ink, and similar iMessage effects.| Constant | Effect |
|---|---|
MessageEffect.confetti | Confetti |
MessageEffect.fireworks | Fireworks |
MessageEffect.balloons | Balloons |
MessageEffect.heart | Heart |
MessageEffect.lasers | Lasers |
MessageEffect.celebration | Celebration |
MessageEffect.sparkles | Sparkles |
MessageEffect.spotlight | Spotlight |
MessageEffect.echo | Echo |
MessageEffect.slam | Slam |
MessageEffect.loud | Loud |
MessageEffect.gentle | Gentle |
MessageEffect.invisible | Invisible ink |
Text Formatting
formatting applies bold, italic, underline, strikethrough, or animated text effects to a range of text.
type | Effect | Shape |
|---|---|---|
"bold" | Bold | { type: "bold", start, length } |
"italic" | Italic | { type: "italic", start, length } |
"underline" | Underline | { type: "underline", start, length } |
"strikethrough" | Strikethrough | { type: "strikethrough", start, length } |
"effect" | Animated text effect | { type: "effect", start, length, effect } |
start and length use UTF-16 code units. In plain ASCII text, offsets match what you see on screen. With emoji or other non-BMP characters, one visible character may use two code units.
| Constant | Effect |
|---|---|
TextEffect.big | Big |
TextEffect.small | Small |
TextEffect.shake | Shake |
TextEffect.nod | Nod |
TextEffect.explode | Explode |
TextEffect.ripple | Ripple |
TextEffect.bloom | Bloom |
TextEffect.jitter | Jitter |

Send Attachments
Sending an attachment has two steps:- Upload file bytes with
im.attachments.upload(...)to get an attachment GUID. - Pass
uploaded.attachment.guidtosendAttachment(...).
sendAttachment(...) is a server attachment GUID, not a local file path. Upload behavior, file extensions, Live Photos, and downloads are covered in attachments.

Audio Messages
To use Apple’s audio-message bubble UI, setisAudioMessage: true:
| Option | Meaning |
|---|---|
isAudioMessage: true | Shows the audio-message UI, such as a play button and waveform |
| Omitted | Sends as a regular attachment |

Send Mini App Cards
sendCustomizedMiniApp(...) sends a card that, when tapped, opens your iMessage extension and hands it url. Use it to launch your own app with a structured payload; for plain link previews, just put the URL in sendText(...).
You need a published iMessage extension on the App Store before you can call this. appName, teamId, and extensionBundleId identify that extension so Messages.app can route the tap to it on the recipient’s device. appStoreId is optional — when set, recipients without the extension installed see an App Store install prompt.
For most cards we recommend an image preview with an overlaid title:
| Field | Meaning |
|---|---|
appName | Display name of your app. Recipients without your extension installed see this on an App Store install prompt. |
appStoreId | Optional numeric App Store id, e.g. 1234567890 from apps.apple.com/app/id1234567890. When set, must be a positive integer. |
teamId | 10-character uppercase alphanumeric Apple Team ID. Find it in App Store Connect → Membership. |
extensionBundleId | Bundle identifier of the iMessage extension target inside your app. |
url | Absolute URL delivered to your extension when the recipient taps the card. |
layout | What the card looks like in the conversation, covered in Card Layout below. |
This call does not accept
replyTo, message effects, or subject. The only option you can pass in the final argument is clientMessageId, used as an idempotency key for job retries.Card Layout
layout mirrors Apple’s MSMessageTemplateLayout — slot names match the Apple field names, so Apple’s documentation applies directly.
| Slot | Where it renders |
|---|---|
caption | Top-left, bold. The most prominent text slot. |
subcaption | Below caption, on the left. |
trailingCaption | Top-right. |
trailingSubcaption | Below trailingCaption, on the right. |
image | JPEG preview image filling the card. |
imageTitle | Overlay text above the image. |
imageSubtitle | Overlay text below imageTitle. |
summary | Fallback text for notifications, lock screens, and other surfaces that cannot render the full card. |
- At least one of
caption,subcaption,trailingCaption,trailingSubcaption, orimagemust be set.summaryalone is not enough — it only appears on fallback surfaces. imageandimageTitlemust be set together. Setting one without the other is rejected.imageSubtitlerequiresimage.
Reply to a Message
To reply to a message, pass the target message GUID asreplyTo:
replyTo works with text, attachment, and multipart sends:
| Method | Reply field |
|---|---|
sendText(...) | options.replyTo |
sendAttachment(...) | options.replyTo |
sendMultipart(...) | options.replyTo |

Send Multipart Messages
sendMultipart(...) sends text, mentions, and attachments as one logical message. Recipients see related bubbles instead of separate sends.
| Input | Rule |
|---|---|
parts | Must not be empty |
| Text part | Pass text; it is trimmed and must not be empty after trimming |
| Attachment part | Pass attachmentGuid; do not pass a local file path |
mentionedAddress | Text parts only |
formatting | Applies only to the current text part |

sendMultipart(...) call.

Reactions and Stickers
Reactions
setReaction(...) adds or removes a tapback / emoji reaction. The fourth argument is true to add and false to remove.
kind | Meaning |
|---|---|
"love" | Heart |
"like" | Thumbs up |
"dislike" | Thumbs down |
"laugh" | Laugh |
"emphasize" | Emphasis |
"question" | Question mark |
"emoji" | Custom emoji; also pass emoji |

Stickers
A sticker is an image placed on top of a message. Upload the sticker image first, then callplaceSticker(...).
Sticker placement is not screen pixels. Think of the target message bubble as a small coordinate space: x: 0.5, y: 0.5 is roughly the center. Larger x moves right; smaller x moves left. Larger y moves down; smaller y moves up. Start near 0.5, 0.5 and make small adjustments. Do not pass pixel-like values such as 120 or 90.
| Field | Meaning |
|---|---|
x | Horizontal position. 0.5 is roughly centered; larger moves right, smaller moves left |
y | Vertical position. 0.5 is roughly centered; larger moves down, smaller moves up |
scale | Optional scale factor |
rotation | Optional rotation. Use small values such as -0.08 or 0.08 for a slight tilt; do not pass 8 |
width | Display width. Pass it explicitly to keep large source images from covering the message |

partIndex. partIndex is zero-based. When omitted, the first bubble is targeted.
placeSticker(...) supports the same partIndex option.
Edit and Unsend
edit(...) changes a sent message and returns the updated Message.
unsend(...) retracts a sent message and returns void.
| Operation | Apple window | Returns |
|---|---|---|
edit(...) | Within 15 minutes of sending | Message |
unsend(...) | Within 2 minutes of sending | void |
| Option | Used by | Meaning |
|---|---|---|
backwardCompatText | edit(...) | Fallback text for clients that cannot display message edits |
partIndex | edit(...), unsend(...) | Target bubble index for multipart messages; zero-based |
clientMessageId | edit(...), unsend(...) | Idempotency key for job retries |
Notify Anyway
notifySilenced(...) triggers Apple’s “Notify Anyway” action after a recipient has Focus silence enabled.
void. To check Focus state before sending, use im.addresses.isFocusSilenced(address).
Get and List Messages
Get One Message
When you have a message GUID, callget(...):
NotFoundError.
List Recent Messages
listRecent(...) lists recent messages across chats:
listInChat(...) lists messages in one chat:
nextPageToken as the next request’s pageToken:
| Filter | Meaning |
|---|---|
after | Only messages created after this time |
before | Only messages created before this time |
isFromMe | true for sent messages only, false for received messages only |
isRead | true for read messages only, false for unread messages only |
pageSize | Number of messages per page; range 1..100 |
pageToken | nextPageToken from the previous response |
Embedded Media
Digital Touch and handwritten-message media are not exposed as regular attachments. UsegetEmbeddedMedia(...) to read that media.
| Case | Result |
|---|---|
| Message is Digital Touch or handwritten media | Returns { data, mimeType } |
| Chat or message does not exist | Throws NotFoundError |
| Message is not an embedded-media type | Throws ValidationError, with error.code === ErrorCode.operationNotSupported |
Message Events
subscribeEvents(...) streams live message changes. To scope it to one chat, pass { chat: chat.guid }:
event.type | Extra fields | Meaning |
|---|---|---|
message.received | message | A message was received or became visible |
message.edited | messageGuid, content, editedAt | A message was edited |
message.read | messageGuid, readAt | A message was marked read |
message.unsent | messageGuid, retractedAt | A message was retracted |
message.reactionAdded | messageGuid, reaction, targetPartIndex? | A reaction was added |
message.reactionRemoved | messageGuid, reaction, targetPartIndex? | A reaction was removed |
message.stickerPlaced | messageGuid, sticker?, placement?, targetPartIndex? | A sticker was placed on a message |
Next Steps
- Attachments — upload files, get attachment GUIDs, and send attachment messages
- Chats — create chats and get
chat.guid - Events — handle live events and recovery
- Error Handling — handle errors, retries, and idempotent writes