Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.photon.codes/docs/llms.txt

Use this file to discover all available pages before exploring further.

im.chats creates and manages iMessage conversations. A chat can be a direct conversation or a group conversation. Most chat methods identify the conversation by chat.guid. The exceptions are create(...), which takes email addresses or phone numbers and returns chat.guid, and count(...), which does not take a chat argument. For group names, participants, group icons, and leaving a group, use groups.

What You Can Do

NeedUse this when
Create a chatYou have one or more email addresses or phone numbers
Get a chatYou have chat.guid and need chat details
Count chatsYou need the number of currently visible chats
Mark readA user opened the conversation and you want to clear unread state
Set typingYou want to show or hide the typing indicator
Share a contact cardYou want to send the current account’s contact card
Set a backgroundYou want to apply an image as the chat background
Subscribe to eventsYou need live changes for read state, archive state, or backgrounds

Chat GUIDs

A chat GUID is the server identifier for a conversation:
Chat typeGUID shapeExample
Direct chatany;-;{recipient}any;-;alice@example.com
Group chatany;+;{group-id}any;+;group-id
In normal code, do not hand-write GUIDs. Use the chat.guid returned by im.chats.create(...), im.chats.get(...), message results, or event payloads.

Create a Chat

One address creates a direct chat. Two or more addresses create a group chat:
const { chat } = await im.chats.create(["alice@example.com"]);

console.log(chat.guid);
const { chat: group } = await im.chats.create(["alice@example.com", "bob@example.com"]);

console.log(group.guid);
Returns CreateChatResult:
{
  "chat": {                         // Created or resolved Chat
    "guid": "any;-;alice@example.com",
    "displayName": "Alice",
    "isGroup": false
  },
  "initialMessage": {                // Present only when options.message is sent
    "guid": "message-guid"
  }
}
InputRule
addressesAt least one full email address or E.164 phone number
options.messageOptional opening text; sent as part of the same call
options.effectOptional effect for the opening message; values are listed under message effects
options.clientMessageIdOptional idempotency key for retrying the same logical create operation
The server normalizes addresses and rejects short codes, service numbers, invalid email addresses, and duplicate recipients. effect only applies when options.message is present. It is not a chat property. For more complex sends, such as formatting or replies, create the chat first and then use the messages API.
clientMessageId is only needed when your job system might retry the same write after a crash or timeout. Most direct calls can omit it. See error handling for details.

Get a Chat

const chat = await im.chats.get("any;-;alice@example.com");

console.log(chat.displayName, chat.unreadCount, chat.isGroup);
Returns Chat:
{
  "guid": "any;-;alice@example.com",  // Chat GUID used by chat and message APIs
  "displayName": "Alice",            // Display name
  "isGroup": false,                  // Whether this is a group chat
  "participants": [                  // Chat participants
    {
      "address": "alice@example.com",
      "service": "iMessage"
    }
  ],
  "service": "iMessage",             // Chat service
  "isArchived": false,               // Whether the chat is archived
  "isFiltered": false,               // Whether the system filtered this chat
  "unreadCount": 0,                  // Unread count; may be absent
  "lastMessage": {}                  // Latest message when available
}
get(...) throws NotFoundError when the chat does not exist or cannot be resolved.

Count Chats

By default, archived chats are excluded:
const active = await im.chats.count();
Include archived chats with includeArchived: true:
const total = await im.chats.count({ includeArchived: true });
Returns number.

Read and Typing State

Mark Read

await im.chats.markRead(chat.guid);
Returns void. Calling it again when the chat has no unread messages is safe.

Typing Indicator

Show typing:
await im.chats.setTyping(chat.guid, true);
Stop typing:
await im.chats.setTyping(chat.guid, false);
Returns void. Typing is temporary UI state. It is not written to the durable event log and cannot be caught up after a disconnect.

Contact Card

await im.chats.shareContactInfo(chat.guid);
Returns void. The contact card comes from the Mac running the service. The SDK cannot choose which fields are included.

Chat Backgrounds

Chat backgrounds use raw image bytes. They are not attachment GUIDs, and you do not upload them through im.attachments first.

Set a Background

await im.chats.setBackground(chat.guid, await readFile("background.jpg"));
Returns void. data must not be empty. The server detects the image type from the bytes. JPEG, PNG, HEIC, and HEIF are supported. After setBackground(...) succeeds, the background usually syncs to other participants’ devices within 30s. The image is uploaded to iCloud and then distributed to the conversation. This is not a hard SLA: network state, iCloud state, and the Messages client state all affect when the background appears.
StageWhat happens
Before setBackground(...) returnsThe server waits until the background asset is uploaded and ready for distribution
After setBackground(...) returnsThe chat background change has been submitted, and iCloud distributes the asset
On other devicesThe background appears after Messages receives the iCloud-distributed asset
Common reasons the background UI may not appear:
SituationResult
The recipient’s network, iCloud, or Messages state is unhealthyThe background may appear late, often after reopening Messages
A group member has never spoken, interacted, or is treated by Apple as unknown / untrustedApple may not show the background UI to that member
The last case is a Messages display rule, not an SDK option. If one group member never sees the background, it is usually more effective for that member to send a message, mark the sender as known, or reopen Messages than to call setBackground(...) repeatedly.

Check Background State

const hasCustom = await im.chats.hasBackground(chat.guid);
Returns boolean.

Remove a Background

await im.chats.removeBackground(chat.guid);
Returns void. Removing a background is safe to repeat, even when no custom background is set.

Chat Events

subscribeEvents(...) returns TypedEventStream<ChatEvent>. Use the stream to observe changes made by other people, other devices, or another part of your system. Immediately after your code calls a write method, use that method’s return value or completion status.

Scope

Only one chat:
const stream = im.chats.subscribeEvents({
  chat: chat.guid,
});
All visible chat events:
const stream = im.chats.subscribeEvents();

Event Shape

Every chat event has the same outer fields:
{
  "type":       "chat.markedRead",       // Event type
  "chatGuid":   "any;-;alice@example.com",
  "sequence":   123,                     // Event sequence for ordering and catch-up
  "isFromMe":   true,                    // Triggered by the current account
  "occurredAt": "2026-01-01T12:00:00Z",  // Event time
  "actor": {                             // Participant that triggered the event; may be absent
    "address": "alice@example.com",
    "service": "iMessage"
  }
}

Event Types

event.typeMeaning
chat.backgroundChangedA custom background was set or replaced
chat.backgroundRemovedThe custom background was removed
chat.markedReadThe chat was marked read
chat.archivedThe chat was archived
chat.unarchivedThe chat was unarchived

Handle Events

for await (const event of im.chats.subscribeEvents({ chat: chat.guid })) {
  switch (event.type) {
    case "chat.backgroundChanged":
    case "chat.backgroundRemoved":
    case "chat.markedRead":
    case "chat.archived":
    case "chat.unarchived":
      console.log(event.type, event.sequence);
      break;
  }
}
When you subscribe to all visible chats, use event.chatGuid to identify the chat:
for await (const event of im.chats.subscribeEvents()) {
  console.log(event.chatGuid, event.type);
}
If the stream disconnects, use events to catch up on missed durable events, then continue consuming the live stream.

Next Steps

  1. Messages — send, read, edit, and unsend messages with chat.guid
  2. Groups — manage group names, participants, group icons, and leaving
  3. Events — catch up on durable events after a disconnect
  4. Error Handling — handle NotFoundError, ValidationError, and idempotent retries