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
| Need | Use this when |
|---|
| Create a chat | You have one or more email addresses or phone numbers |
| Get a chat | You have chat.guid and need chat details |
| Count chats | You need the number of currently visible chats |
| Mark read | A user opened the conversation and you want to clear unread state |
| Set typing | You want to show or hide the typing indicator |
| Share a contact card | You want to send the current account’s contact card |
| Set a background | You want to apply an image as the chat background |
| Subscribe to events | You need live changes for read state, archive state, or backgrounds |
Chat GUIDs
A chat GUID is the server identifier for a conversation:
| Chat type | GUID shape | Example |
|---|
| Direct chat | any;-;{recipient} | any;-;alice@example.com |
| Group chat | any;+;{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"
}
}
| Input | Rule |
|---|
addresses | At least one full email address or E.164 phone number |
options.message | Optional opening text; sent as part of the same call |
options.effect | Optional effect for the opening message; values are listed under message effects |
options.clientMessageId | Optional 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.
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.
| Stage | What happens |
|---|
Before setBackground(...) returns | The server waits until the background asset is uploaded and ready for distribution |
After setBackground(...) returns | The chat background change has been submitted, and iCloud distributes the asset |
| On other devices | The background appears after Messages receives the iCloud-distributed asset |
Common reasons the background UI may not appear:
| Situation | Result |
|---|
| The recipient’s network, iCloud, or Messages state is unhealthy | The background may appear late, often after reopening Messages |
| A group member has never spoken, interacted, or is treated by Apple as unknown / untrusted | Apple 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.type | Meaning |
|---|
chat.backgroundChanged | A custom background was set or replaced |
chat.backgroundRemoved | The custom background was removed |
chat.markedRead | The chat was marked read |
chat.archived | The chat was archived |
chat.unarchived | The 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
- Messages — send, read, edit, and unsend messages with
chat.guid
- Groups — manage group names, participants, group icons, and leaving
- Events — catch up on durable events after a disconnect
- Error Handling — handle
NotFoundError, ValidationError, and idempotent retries