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.
npm install @photon-ai/advanced-imessage-kit
# or
bun add @photon-ai/advanced-imessage-kitimport { 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();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"
}chatGuid is the unique identifier for a conversation. The format is service;-;address:
- iMessage DM:
iMessage;-;+1234567890oriMessage;-;[email protected] - SMS DM:
SMS;-;+1234567890 - Group chat:
iMessage;+;chat123456789 - Auto-detect:
any;-;+1234567890(SDK automatically detects the service type)
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 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 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
Examples: message-send.ts | message-unsend.ts | message-reaction.ts | message-search.ts
// 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
// 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();await sdk.messages.unsendMessage({
messageGuid: "message-guid-to-unsend",
partIndex: 0, // Optional
});Example: message-unsend.ts
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
// Trigger message notification
await sdk.messages.notifyMessage("message-guid");
// Get embedded media
const media = await sdk.messages.getEmbeddedMedia("message-guid");Examples: chat-fetch.ts | chat-group.ts | message-typing.ts
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();const chat = await sdk.chats.getChat("chat-guid", {
with: ["participants", "lastMessage"],
});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"
});// Mark as read/unread
await sdk.chats.markChatRead("chat-guid");
await sdk.chats.markChatUnread("chat-guid");
// Delete chat
await sdk.chats.deleteChat("chat-guid");// Show "typing..."
await sdk.chats.startTyping("chat-guid");
// Stop showing
await sdk.chats.stopTyping("chat-guid");Example: message-typing.ts
const messages = await sdk.chats.getChatMessages("chat-guid", {
limit: 100,
offset: 0,
sort: "DESC",
before: Date.now(),
after: Date.now() - 86400000,
});// 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");// 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
Examples: message-attachment.ts | message-audio.ts | message-reply-sticker.ts | attachment-download.ts
const message = await sdk.attachments.sendAttachment({
chatGuid: "iMessage;-;+1234567890",
filePath: "/path/to/file.jpg",
fileName: "custom-name.jpg", // Optional
});const message = await sdk.attachments.sendAttachment({
chatGuid: "iMessage;-;+1234567890",
filePath: "/path/to/audio.m4a",
isAudioMessage: true,
});Example: message-audio.ts
await sdk.attachments.sendSticker({
chatGuid: "iMessage;-;+1234567890",
filePath: "/path/to/sticker.png",
selectedMessageGuid: "message-to-stick-on", // Optional
});Example: message-reply-sticker.ts
// Get attachment details
const attachment = await sdk.attachments.getAttachment("attachment-guid");
// Get total count
const count = await sdk.attachments.getAttachmentCount();// 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
Example: contact-list.ts
const contacts = await sdk.contacts.getContacts();// Get by phone or email
const card = await sdk.contacts.getContactCard("+1234567890");Examples: service-check.ts | handle-query.ts
A Handle represents a messaging address (phone number or email).
// 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 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
const focusStatus = await sdk.handles.getHandleFocusStatus("handle-guid");Examples: message-stats.ts | server-info.ts
const info = await sdk.server.getServerInfo();
// {
// os_version: "14.0",
// server_version: "1.0.0",
// private_api: true,
// helper_connected: true,
// detected_icloud: "[email protected]",
// ...
// }const stats = await sdk.server.getMessageStats();
// {
// total: 12345,
// sent: 5000,
// received: 7345,
// last24h: 50,
// last7d: 300,
// last30d: 1000,
// }// All media stats
const mediaStats = await sdk.server.getMediaStatistics();
// Per-chat media stats
const chatMediaStats = await sdk.server.getMediaStatisticsByChat();const logs = await sdk.server.getServerLogs(100); // Get last 100 logs// Get alerts
const alerts = await sdk.server.getAlerts();
// Mark alerts as read
await sdk.server.markAlertAsRead(["alert-id-1", "alert-id-2"]);Example: server-info.ts
Examples: poll-create.ts | poll-add-option.ts
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
await sdk.polls.addOption({
chatGuid: "iMessage;-;+1234567890",
pollMessageGuid: "poll-message-guid",
optionText: "New Option D",
});Example: poll-add-option.ts
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/lib/poll-utils";
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.
Example: findmy-friends.ts
// Get friends' locations
const friends = await sdk.icloud.getFindMyFriends();
// Refresh location data
await sdk.icloud.refreshFindMyFriends();Example: findmy-friends.ts
Examples: listen-simple.ts | listen-advanced.ts | auto-reply-hey.ts
The SDK receives real-time events from the server via Socket.IO.
sdk.on("ready", () => {
console.log("SDK connected and ready");
});
sdk.on("disconnect", () => {
console.log("Disconnected");
});
sdk.on("error", (error) => {
console.error("Error:", error);
});// 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 read status changed
sdk.on("chat-read-status-changed", ({ chatGuid, read }) => {
console.log(`Chat ${chatGuid} marked as ${read ? "read" : "unread"}`);
});sdk.on("typing-indicator", ({ display, guid }) => {
console.log(`${guid} ${display ? "is typing" : "stopped typing"}`);
});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");
});sdk.on("new-findmy-location", (location) => {
console.log(`${location.handle} location updated:`, location.coordinates);
});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");// Graceful shutdown
process.on("SIGINT", async () => {
await sdk.close();
process.exit(0);
});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();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);
}
}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 sendsRun any example with Bun:
bun run examples/<filename>.ts| 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 |
| File | Description |
|---|---|
| message-reply.ts | Reply to messages |
| message-unsend.ts | Unsend messages |
| message-reaction.ts | Send Tapbacks |
| message-effects.ts | Message effects |
| message-search.ts | Search messages |
| File | Description |
|---|---|
| chat-fetch.ts | Get chat list |
| chat-group.ts | Manage groups |
| message-typing.ts | Typing indicators |
| File | Description |
|---|---|
| contact-list.ts | Get contacts |
| service-check.ts | Check iMessage availability |
| message-service-check.ts | Monitor message service types |
| File | Description |
|---|---|
| attachment-download.ts | Download attachments |
| message-reply-sticker.ts | Send stickers |
| File | Description |
|---|---|
| poll-create.ts | Create polls |
| poll-add-option.ts | Add poll options |
| 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 |
Download llms.txt for language model context:
MIT License
