Skip to content

photon-hq/advanced-imessage-kit

Repository files navigation

Banner

Advanced iMessage Kit

A powerful TypeScript SDK for iMessage with real-time messaging support

TypeScript License Discord

Advanced iMessage Kit is a full-featured iMessage SDK for reading, sending, and automating iMessage conversations on macOS. Perfect for building AI agents, automation tools, and chat applications.


Features

Feature Description Method Example
Send Messages Send text messages to any contact messages.sendMessage() message-send.ts
Reply to Messages Reply inline to a specific message messages.sendMessage() message-reply.ts
Message Effects Send with effects (confetti, fireworks, etc.) messages.sendMessage() message-effects.ts
Unsend Messages Retract a sent message messages.unsendMessage() message-unsend.ts
Edit Messages Edit a sent message messages.editMessage() message-edit.ts
Send Tapbacks React with ❤️ 👍 👎 😂 ‼️ messages.sendReaction() message-reaction.ts
Query Messages Search and filter message history messages.getMessages() message-search.ts
Message History View messages, reactions, polls, stickers chats.getChatMessages() message-history.ts
Send Attachments Send images, files, documents attachments.sendAttachment() message-attachment.ts
Send Audio Messages Send voice messages attachments.sendAttachment() message-audio.ts
Send Stickers Send sticker as standalone message attachments.sendSticker() message-sticker.ts
Reply Stickers Attach sticker to a message bubble attachments.sendSticker() message-reply-sticker.ts
Download Attachments Download received files and media attachments.downloadAttachment() attachment-download.ts
Get Chats List all conversations chats.getChats() chat-fetch.ts
Manage Group Chats Add/remove members, rename groups chats.addParticipant() chat-group.ts
Typing Indicators Show "typing..." status chats.startTyping() message-typing.ts
Get Contacts Fetch device contacts contacts.getContacts() contact-list.ts
Share Contact Card Share your contact info in chat contacts.shareContactCard() message-contact-card.ts
Check iMessage Availability Verify if contact uses iMessage handles.getHandleAvailability() service-check.ts
Server Info Get server status and config server.getServerInfo() server-info.ts
Message Statistics Get message counts and analytics server.getMessageStats() message-stats.ts
Create Polls Create interactive polls in chat polls.create() poll-create.ts
Vote on Polls Vote or unvote on poll options polls.vote() poll-vote.ts
Add Poll Options Add options to existing polls polls.addOption() poll-add-option.ts
Find My Friends (WIP) Get friends' locations icloud.getFindMyFriends() findmy-friends.ts
Real-time Events Listen for new messages, typing, etc. sdk.on() listen-simple.ts
Auto Reply Build automated reply bots sdk.on() auto-reply-hey.ts

Quick Start

Installation

npm install @photon-ai/advanced-imessage-kit
# or
bun add @photon-ai/advanced-imessage-kit

Basic Usage

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

const sdk = SDK({
  serverUrl: "http://localhost:1234",
});

await sdk.connect();

sdk.on("new-message", (message) => {
  console.log("New message:", message.text);
});

await sdk.messages.sendMessage({
  chatGuid: "iMessage;-;+1234567890",
  message: "Hello World!",
});

await sdk.close();

Configuration

interface ClientConfig {
  serverUrl?: string; // Server URL, defaults to "http://localhost:1234"
  apiKey?: string; // API key (if server requires authentication)
  logLevel?: "debug" | "info" | "warn" | "error"; // Log level, defaults to "info"
}

Core Concepts

chatGuid Format

chatGuid is the unique identifier for a conversation. The format is service;-;address:

  • iMessage DM: iMessage;-;+1234567890 or iMessage;-;email@example.com
  • SMS DM: SMS;-;+1234567890
  • Group chat: iMessage;+;chat123456789
  • Auto-detect: any;-;+1234567890 (SDK automatically detects the service type)

How to Get IDs

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│  Phone / Email  │────▶│  Build chatGuid │────▶│  Send Message   │
│  +1234567890    │     │ any;-;+123...   │     │  sendMessage()  │
└─────────────────┘     └─────────────────┘     └─────────────────┘

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│   getChats()    │────▶│  Get chat.guid  │────▶│  Use for other  │
│   List chats    │     │                 │     │  operations     │
└─────────────────┘     └─────────────────┘     └─────────────────┘

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│  sendMessage()  │────▶│ Get message.guid│────▶│  edit/unsend    │
│   Send message  │     │                 │     │  sendReaction   │
└─────────────────┘     └─────────────────┘     └─────────────────┘

Messages

Examples: message-send.ts | message-unsend.ts | message-edit.ts | message-reaction.ts | message-search.ts

Send Messages

// Send a text message
const message = await sdk.messages.sendMessage({
  chatGuid: "iMessage;-;+1234567890",
  message: "Hello!",
});

// With subject and effect
await sdk.messages.sendMessage({
  chatGuid: "iMessage;-;+1234567890",
  message: "Happy Birthday!",
  subject: "Wishes",
  effectId: "com.apple.messages.effect.CKConfettiEffect",
});

// Reply to a message
await sdk.messages.sendMessage({
  chatGuid: "iMessage;-;+1234567890",
  message: "This is a reply",
  selectedMessageGuid: "original-message-guid",
});

Message Effects:

Effect effectId
Confetti com.apple.messages.effect.CKConfettiEffect
Fireworks com.apple.messages.effect.CKFireworksEffect
Balloons com.apple.messages.effect.CKBalloonEffect
Hearts com.apple.messages.effect.CKHeartEffect
Lasers com.apple.messages.effect.CKHappyBirthdayEffect
Shooting Star com.apple.messages.effect.CKShootingStarEffect
Sparkles com.apple.messages.effect.CKSparklesEffect
Echo com.apple.messages.effect.CKEchoEffect
Spotlight com.apple.messages.effect.CKSpotlightEffect
Gentle com.apple.MobileSMS.expressivesend.gentle
Loud com.apple.MobileSMS.expressivesend.loud
Slam com.apple.MobileSMS.expressivesend.impact
Invisible Ink com.apple.MobileSMS.expressivesend.invisibleink

Example: message-effects.ts

Query Messages

// Get a single message
const message = await sdk.messages.getMessage("message-guid");

// Query messages
const messages = await sdk.messages.getMessages({
  chatGuid: "iMessage;-;+1234567890",
  limit: 50,
  offset: 0,
  sort: "DESC", // DESC = newest first, ASC = oldest first
  before: Date.now(),
  after: Date.now() - 86400000, // Last 24 hours
});

// Search messages
const results = await sdk.messages.searchMessages({
  query: "keyword",
  chatGuid: "iMessage;-;+1234567890", // Optional
  limit: 20,
});

// Get counts
const total = await sdk.messages.getMessageCount();
const sent = await sdk.messages.getSentMessageCount();
const updated = await sdk.messages.getUpdatedMessageCount();

Unsend Messages

await sdk.messages.unsendMessage({
  messageGuid: "message-guid-to-unsend",
  partIndex: 0, // Optional
});

Example: message-unsend.ts

Edit Messages

const editedMessage = await sdk.messages.editMessage({
  messageGuid: "message-guid-to-edit",
  editedMessage: "New text content",
  backwardsCompatibilityMessage: "New text content", // Optional, defaults to editedMessage
  partIndex: 0, // Optional, defaults to 0
});

console.log(`Edited: ${editedMessage.guid}`);
console.log(`New text: ${editedMessage.text}`);
console.log(`Date edited: ${editedMessage.dateEdited}`);

Example: message-edit.ts

Send Tapbacks

await sdk.messages.sendReaction({
  chatGuid: "iMessage;-;+1234567890",
  messageGuid: "target-message-guid",
  reaction: "love", // love, like, dislike, laugh, emphasize, question
  partIndex: 0, // Optional
});

// Remove a Tapback (prefix with -)
await sdk.messages.sendReaction({
  chatGuid: "iMessage;-;+1234567890",
  messageGuid: "target-message-guid",
  reaction: "-love", // -love, -like, -dislike, -laugh, -emphasize, -question
});

Example: message-reaction.ts

Other Message Operations

// Trigger message notification
await sdk.messages.notifyMessage("message-guid");

// Get embedded media
const media = await sdk.messages.getEmbeddedMedia("message-guid");

Chats

Examples: chat-fetch.ts | chat-group.ts | message-typing.ts

Get Chats

const chats = await sdk.chats.getChats({
  withLastMessage: true, // Include last message
  withArchived: false, // Include archived chats
  offset: 0,
  limit: 50,
});

// Get chat count
const count = await sdk.chats.getChatCount();

Get Single Chat

const chat = await sdk.chats.getChat("chat-guid", {
  with: ["participants", "lastMessage"],
});

Create Chat

const newChat = await sdk.chats.createChat({
  addresses: ["+1234567890", "+0987654321"],
  message: "Hello everyone!", // Optional initial message
  service: "iMessage", // "iMessage" or "SMS"
  method: "private-api", // "apple-script" or "private-api"
});

Chat Status

// Mark as read/unread
await sdk.chats.markChatRead("chat-guid");
await sdk.chats.markChatUnread("chat-guid");

// Delete chat
await sdk.chats.deleteChat("chat-guid");

Typing Indicators

// Show "typing..."
await sdk.chats.startTyping("chat-guid");

// Stop showing
await sdk.chats.stopTyping("chat-guid");

Example: message-typing.ts

Get Chat Messages

const messages = await sdk.chats.getChatMessages("chat-guid", {
  limit: 100,
  offset: 0,
  sort: "DESC",
  before: Date.now(),
  after: Date.now() - 86400000,
});

Manage Group Chats

// Rename group
await sdk.chats.updateChat("chat-guid", {
  displayName: "New Group Name",
});

// Add participant
await sdk.chats.addParticipant("chat-guid", "+1234567890");

// Remove participant
await sdk.chats.removeParticipant("chat-guid", "+1234567890");

// Leave group
await sdk.chats.leaveChat("chat-guid");

Group Icon

// Set group icon
await sdk.chats.setGroupIcon("chat-guid", "/path/to/image.jpg");

// Get group icon
const iconBuffer = await sdk.chats.getGroupIcon("chat-guid");

// Remove group icon
await sdk.chats.removeGroupIcon("chat-guid");

Example: chat-group.ts


Attachments

Examples: message-attachment.ts | message-audio.ts | message-reply-sticker.ts | attachment-download.ts

Send Attachments

const message = await sdk.attachments.sendAttachment({
  chatGuid: "iMessage;-;+1234567890",
  filePath: "/path/to/file.jpg",
  fileName: "custom-name.jpg", // Optional
});

Send Audio Messages

const message = await sdk.attachments.sendAttachment({
  chatGuid: "iMessage;-;+1234567890",
  filePath: "/path/to/audio.m4a",
  isAudioMessage: true,
});

Example: message-audio.ts

Send Stickers

Stickers can be sent in two ways:

Standalone Sticker - Sends as its own message (like sending an image, but with sticker styling):

await sdk.attachments.sendSticker({
  chatGuid: "iMessage;-;+1234567890",
  filePath: "/path/to/sticker.png",
});

Example: message-sticker.ts

Reply Sticker (Tapback Sticker) - Attaches to an existing message bubble:

await sdk.attachments.sendSticker({
  chatGuid: "iMessage;-;+1234567890",
  filePath: "/path/to/sticker.png",
  selectedMessageGuid: "target-message-guid", // Required for reply sticker
  stickerX: 0.5, // Position X (0-1), default: 0.5
  stickerY: 0.5, // Position Y (0-1), default: 0.5
  stickerScale: 0.75, // Scale (0-1), default: 0.75
  stickerRotation: 0, // Rotation in radians, default: 0
  stickerWidth: 300, // Width in pixels, default: 300
});

Example: message-reply-sticker.ts

Get Attachment Info

// Get attachment details
const attachment = await sdk.attachments.getAttachment("attachment-guid");

// Get total count
const count = await sdk.attachments.getAttachmentCount();

Download Attachments

// Download attachment
const buffer = await sdk.attachments.downloadAttachment("attachment-guid", {
  original: true, // Download original file
  force: false, // Force re-download
  width: 800, // Image width (for thumbnails)
  height: 600, // Image height
  quality: 80, // Image quality
});

// Download Live Photo video
const liveBuffer = await sdk.attachments.downloadAttachmentLive(
  "attachment-guid"
);

// Get blurhash (for placeholders)
const blurhash = await sdk.attachments.getAttachmentBlurhash("attachment-guid");

Example: attachment-download.ts


Contacts

Example: contact-list.ts

Get Contacts

const contacts = await sdk.contacts.getContacts();

Get Contact Card

// Get contact card by phone or email
const card = await sdk.contacts.getContactCard("+1234567890");
// {
//   firstName: "John",
//   lastName: "Doe",
//   emails: ["john@example.com"],
//   phones: ["+1234567890"],
//   ...
// }

Share Contact Card

Share your contact card with a chat:

// chatGuid is the chat identifier (e.g. the `guid` field you get from chat APIs/events)
// Check whether the SDK recommends sharing your contact card in this chat.
//
// Returns:
// - true: sharing is recommended (typically when the other side shared theirs and you haven't shared yours yet)
// - false: NOT recommended (e.g. you've already shared, OR the other side hasn't shared theirs yet)
const shouldShare = await sdk.contacts.shouldShareContact("chat-guid");

if (shouldShare) {
  // Share your contact card (iMessage "Share Name and Photo")
  await sdk.contacts.shareContactCard("chat-guid");
}

Example: message-contact-card.ts


Handles

Examples: service-check.ts | handle-query.ts

A Handle represents a messaging address (phone number or email).

Query Handles

// Query handles
const result = await sdk.handles.queryHandles({
  address: "+1234567890", // Optional, filter by address
  with: ["chats"], // Optional, include related chats
  offset: 0,
  limit: 50,
});

// Get single handle
const handle = await sdk.handles.getHandle("handle-guid");

// Get total count
const count = await sdk.handles.getHandleCount();

Check Service Availability

Check if a phone/email supports iMessage or FaceTime:

// First parameter is the address (phone or email), not handle guid
const hasIMessage = await sdk.handles.getHandleAvailability(
  "+1234567890",
  "imessage"
);
const hasFaceTime = await sdk.handles.getHandleAvailability(
  "+1234567890",
  "facetime"
);

// Choose service based on availability
const chatGuid = hasIMessage ? `iMessage;-;+1234567890` : `SMS;-;+1234567890`;

Example: service-check.ts

Get Focus Status

const focusStatus = await sdk.handles.getHandleFocusStatus("handle-guid");

Server

Examples: message-stats.ts | server-info.ts

Get Server Info

const info = await sdk.server.getServerInfo();
// {
//   os_version: "14.0",
//   server_version: "1.0.0",
//   private_api: true,
//   helper_connected: true,
//   detected_icloud: "user@icloud.com",
//   ...
// }

Message Statistics

const stats = await sdk.server.getMessageStats();
// {
//   total: 12345,
//   sent: 5000,
//   received: 7345,
//   last24h: 50,
//   last7d: 300,
//   last30d: 1000,
// }

Media Statistics

// All media stats
const mediaStats = await sdk.server.getMediaStatistics();

// Per-chat media stats
const chatMediaStats = await sdk.server.getMediaStatisticsByChat();

Server Logs

const logs = await sdk.server.getServerLogs(100); // Get last 100 logs

Polls

Examples: poll-create.ts | poll-vote.ts | poll-unvote.ts | poll-add-option.ts

Create Polls

const pollMessage = await sdk.polls.create({
  chatGuid: "iMessage;-;+1234567890",
  title: "What should we do?", // Optional
  options: ["Option A", "Option B", "Option C"],
});

console.log("Poll GUID:", pollMessage.guid);

Example: poll-create.ts

Add Poll Options

await sdk.polls.addOption({
  chatGuid: "iMessage;-;+1234567890",
  pollMessageGuid: "poll-message-guid",
  optionText: "New Option D",
});

Example: poll-add-option.ts

Vote on Polls

// Vote on a poll option
await sdk.polls.vote({
  chatGuid: "iMessage;-;+1234567890",
  pollMessageGuid: "poll-message-guid",
  optionIdentifier: "option-uuid", // UUID of the option to vote for
});

// Remove your vote
await sdk.polls.unvote({
  chatGuid: "iMessage;-;+1234567890",
  pollMessageGuid: "poll-message-guid",
  optionIdentifier: "option-uuid",
});

Examples: poll-vote.ts | poll-unvote.ts

Parse Poll Messages

Use the poll-utils helper functions to parse and display poll messages:

import {
  isPollMessage,
  isPollVote,
  parsePollDefinition,
  parsePollVotes,
  getPollSummary,
  getOptionTextById,
} from "@photon-ai/advanced-imessage-kit";

sdk.on("new-message", (message) => {
  if (isPollMessage(message)) {
    if (isPollVote(message)) {
      // Parse vote data
      const voteData = parsePollVotes(message);
      console.log("Votes:", voteData?.votes);

      // Get option text for each vote
      voteData?.votes.forEach((vote) => {
        const optionText = getOptionTextById(vote.voteOptionIdentifier);
        console.log(`${vote.participantHandle} voted for "${optionText}"`);
      });
    } else {
      // Parse poll definition
      const pollData = parsePollDefinition(message);
      console.log("Poll title:", pollData?.title);
      console.log("Options:", pollData?.options);
    }

    // Or get a formatted summary
    console.log(getPollSummary(message));
  }
});

Note: Poll definitions are automatically cached when received. When a vote arrives, the SDK looks up the corresponding option text from the cache. If you receive a vote for a poll that was created before the SDK started, the option text won't be available and will show the UUID instead.


iCloud (Work in Progress)

Example: findmy-friends.ts

Find My Friends

// Get friends' locations
const friends = await sdk.icloud.getFindMyFriends();

// Refresh location data
await sdk.icloud.refreshFindMyFriends();

Example: findmy-friends.ts


Real-time Events

Examples: listen-simple.ts | listen-advanced.ts | auto-reply-hey.ts

The SDK receives real-time events from the server via Socket.IO.

Connection Events

sdk.on("ready", () => {
  console.log("SDK connected and ready");
});

sdk.on("disconnect", () => {
  console.log("Disconnected");
});

sdk.on("error", (error) => {
  console.error("Error:", error);
});

Message Events

// New message
sdk.on("new-message", (message) => {
  console.log("New message:", message.text);
  console.log("From:", message.handle?.address);
  console.log("From me:", message.isFromMe);
});

// Message status update (delivered, read, etc.)
sdk.on("updated-message", (message) => {
  if (message.dateRead) console.log("Message read");
  else if (message.dateDelivered) console.log("Message delivered");
});

// Send failed
sdk.on("message-send-error", (data) => {
  console.error("Send failed:", data);
});

Chat Events

// Chat read status changed
sdk.on("chat-read-status-changed", ({ chatGuid, read }) => {
  console.log(`Chat ${chatGuid} marked as ${read ? "read" : "unread"}`);
});

Typing Indicators

sdk.on("typing-indicator", ({ display, guid }) => {
  console.log(`${guid} ${display ? "is typing" : "stopped typing"}`);
});

Group Events

sdk.on("group-name-change", (message) => {
  console.log("Group renamed to:", message.groupTitle);
});

sdk.on("participant-added", (message) => {
  console.log("Someone joined the group");
});

sdk.on("participant-removed", (message) => {
  console.log("Someone was removed from the group");
});

sdk.on("participant-left", (message) => {
  console.log("Someone left the group");
});

sdk.on("group-icon-changed", (message) => {
  console.log("Group icon changed");
});

sdk.on("group-icon-removed", (message) => {
  console.log("Group icon removed");
});

Find My Friends Events (WIP)

sdk.on("new-findmy-location", (location) => {
  console.log(`${location.handle} location updated:`, location.coordinates);
});

Remove Event Listeners

const handler = (message) => console.log(message);
sdk.on("new-message", handler);

// Remove specific listener
sdk.off("new-message", handler);

// Remove all listeners
sdk.removeAllListeners("new-message");

Best Practices

Resource Management

// Graceful shutdown
process.on("SIGINT", async () => {
  await sdk.close();
  process.exit(0);
});

Message Deduplication

The SDK includes built-in message deduplication to prevent processing duplicates during network instability:

// Clear processed messages (prevent memory leaks)
sdk.clearProcessedMessages(1000);

// Get processed message count
const count = sdk.getProcessedMessageCount();

Error Handling

try {
  await sdk.messages.sendMessage({
    chatGuid: "invalid-guid",
    message: "test",
  });
} catch (error) {
  if (error.response?.status === 404) {
    console.error("Chat not found");
  } else {
    console.error("Send failed:", error.message);
  }
}

Auto-create Chats

When sending to a contact you've never messaged before, the SDK automatically creates the chat:

// Works even without existing chat history
await sdk.messages.sendMessage({
  chatGuid: "any;-;+1234567890",
  message: "Hi, this is John",
});
// SDK detects the chat doesn't exist, creates it, then sends

Examples

Run any example with Bun:

bun run examples/<filename>.ts

Getting Started

File Description
listen-simple.ts Listen with formatted output
listen-advanced.ts Listen with full JSON and startup info
message-send.ts Send text messages
message-attachment.ts Send attachments
message-audio.ts Send audio messages

Message Operations

File Description
message-reply.ts Reply to messages
message-unsend.ts Unsend messages
message-edit.ts Edit messages
message-reaction.ts Send Tapbacks
message-effects.ts Message effects
message-search.ts Search messages
message-history.ts Message history

Chats & Groups

File Description
chat-fetch.ts Get chat list
chat-group.ts Manage groups
message-typing.ts Typing indicators

Contacts & Services

File Description
contact-list.ts Get contacts
message-contact-card.ts Share contact card
service-check.ts Check iMessage availability
message-service-check.ts Monitor message service types

Attachments & Media

File Description
attachment-download.ts Download attachments
message-sticker.ts Send standalone stickers
message-reply-sticker.ts Send reply stickers

Polls

File Description
poll-create.ts Create polls
poll-vote.ts Vote on polls
poll-unvote.ts Unvote on polls
poll-add-option.ts Add poll options

Server & Advanced

File Description
server-info.ts Server info and logs
message-stats.ts Message statistics
findmy-friends.ts Find My Friends (WIP)
auto-reply-hey.ts Auto reply bot

LLMs

Download llms.txt for language model context:


License

MIT License

Author

@Artist-MOBAI