Skip to main content
im.messages covers everything related to individual messages - sending plain text, rich multipart messages, reacting, editing, unsending, paginated listing, and real-time event subscriptions.

Sending

The SDK has three sending tiers. Use the simplest tier that covers your use case.

Tier 1 - plain text

await im.messages.send(chat, "Hello!");

Tier 2 - with options

import { MessageEffect } from "@photon-ai/advanced-imessage";

await im.messages.send(chat, "Hello!", {
  effect: MessageEffect.confetti,
  replyTo: someMessageGuid,
  subject: "Hey",
  attachment: attachmentGuid,
  audioMessage: false,
});
SendOptions reference
OptionTypeDescription
effectMessageEffectVisual send effect (slam, confetti, fireworks, …)
replyToMessageGuid | { guid: MessageGuid; partIndex?: number }Reply to a specific message or part
subjectstringMessage subject line
attachmentAttachmentGuidPre-uploaded attachment to include
audioMessagebooleanSend attachment as an audio message
sticker{ attachment: AttachmentGuid; target: MessageGuid; placement?: StickerPlacement }Send as a sticker on another message
formattingTextFormatInput[]Bold, italic, underline, or text effects
service"iMessage" | "SMS" | "RCS"Override service
clientMessageIdstringIdempotency key
ddScanbooleanEnable data detectors scan
richLinkbooleanExpand URLs to rich link previews

Tier 3 - MessageBuilder

Use the builder when you need mentions, per-part formatting, or attachments mixed with text.
import { MessageBuilder, MessageEffect } from "@photon-ai/advanced-imessage";

await im.messages.sendComposed(
  chat,
  MessageBuilder.multipart()
    .addText("Hey ")
    .addMention("@Alice", "alice@icloud.com")
    .addText("!")
    .withEffect(MessageEffect.slam)
    .asReplyTo(someGuid)
    .build(),
);
All three methods return a SendReceipt:
interface SendReceipt {
  guid: MessageGuid;
  clientMessageId?: string;
}

Available effects

import { MessageEffect, TextEffect } from "@photon-ai/advanced-imessage";

MessageEffect.slam; // impact send effect
MessageEffect.confetti; // confetti burst
MessageEffect.fireworks; // fireworks
MessageEffect.balloons; // balloons
// ...

TextEffect.big; // big text animation
TextEffect.small; // small text animation
// ...

Reactions

import { Reaction } from "@photon-ai/advanced-imessage";

// Tapback
await im.messages.react(chat, messageGuid, Reaction.love);
await im.messages.react(chat, messageGuid, Reaction.like, { partIndex: 1 });

// Emoji reaction (iOS 17+)
await im.messages.reactEmoji(chat, messageGuid, "🔥");

// Remove a tapback
await im.messages.unreact(chat, messageGuid, Reaction.love);
All reaction methods return CommandReceipt:
interface CommandReceipt {
  guid: MessageGuid;
}

Edit & unsend

// Edit text (iOS 16+)
await im.messages.edit(chat, messageGuid, "Corrected text", {
  backwardCompatText: "Edited: Corrected text",
  partIndex: 0,
});

// Retract a message (iOS 16+)
await im.messages.unsend(chat, messageGuid, { partIndex: 0 });

Querying messages

Get a single message

const message = await im.messages.get(guid);

List with auto-pagination

list() returns a Paginated<Message> that is both awaitable and for awaitable - no pagination API to learn.
// First page
const page = await im.messages.list({ chatGuid: chat, limit: 25 });
page.data; // Message[]
page.meta; // { total, offset, limit }

// Stream all messages
for await (const message of im.messages.list({ chatGuid: chat })) {
  console.log(message.text);
}

// Buffer all, with a safety cap
const all = await im.messages.list({ chatGuid: chat }).toArray({ limit: 500 });
MessageListOptions reference
OptionTypeDescription
chatGuidChatGuidFilter to one conversation
beforeDateOnly messages before this timestamp
afterDateOnly messages after this timestamp
sortSortDirection"ascending" or "descending"
limitnumberPage size
offsetnumberStarting offset
withChatsbooleanInclude related chat objects
withAttachmentsbooleanInclude attachment metadata

Message statistics

const stats = await im.messages.stats();
stats.total; // number
stats.sent; // number
stats.received; // number

Real-time events

subscribe() returns a TypedEventStream<MessageEvent>. Pass an event type string to narrow the type automatically.
// All message events
for await (const event of im.messages.subscribe()) {
  event.type; // "message.sent" | "message.received" | "message.updated" | "message.sendError"
}

// Only incoming messages - TS knows the exact shape
for await (const event of im.messages.subscribe("message.received")) {
  console.log(event.message.text);
  console.log(event.chatGuid);
}

Event types

TypeExtra fields
message.sentmessage, chatGuid, clientMessageId?
message.receivedmessage, chatGuid
message.updatedmessage, chatGuid, updateType (edited | unsent | notified | reaction)
message.sendErrorchatGuid, errorCode, errorMessage, clientMessageId?

Stream operators

TypedEventStream supports .filter(), .map(), and .take():
const incoming = im.messages
  .subscribe("message.received")
  .filter((e) => e.message.text !== undefined)
  .map((e) => ({ from: e.message.sender?.address, text: e.message.text! }));

for await (const { from, text } of incoming) {
  console.log(`[${from}] ${text}`);
}
Streams implement Symbol.asyncDispose:
await using stream = im.messages.subscribe("message.received");
for await (const event of stream) {
  ...
}

Miscellaneous

// Acknowledge a silenced message notification
await im.messages.notifySilenced(chat, messageGuid);

// Extract embedded media from a rich message (link preview images, etc.)
const items = await im.messages.getEmbeddedMedia(chat, messageGuid);
// items: Array<{ data: Uint8Array; mimeType: string }>