Skip to main content
@photon-ai/imessage-kit is an MIT-licensed, macOS-only SDK that talks directly to the local Messages database and AppleScript bridge. Use it when you want to read, send, and automate iMessage on a Mac you control — automation tools, AI agents, chat-first apps.
This SDK is for talking to iMessage locally on a Mac you control. If you want remote/hosted iMessage delivery, threaded replies, tapbacks, edits / unsends, and live typing indicators, use Spectrum for a unified API across platforms, or @photon-ai/advanced-imessage when you need low-level iMessage control.

Requirements

  • OS: macOS only (reads the Messages SQLite database directly).
  • Runtime: Node.js ≥ 20 or Bun ≥ 1.0.
  • Permission: the process must have Full Disk Access granted. Open System Settings → Privacy & Security → Full Disk Access and add your terminal or IDE (Terminal, iTerm2, Warp, VS Code, Cursor…).

Install

On Bun the SDK has zero runtime dependencies. On Node it uses better-sqlite3 as an optional peer dependency.

Quick start

The constructor takes an optional — override the default Messages database path, concurrent-send limit, debug logging, webhooks, and plugins.

Sending

sdk.send(to, content) auto-detects whether to is a recipient (phone number / email) or a chatId (group or DM).

Text

Images and files

Groups

Group chatIds are returned by listChats():

Batch

Concurrency is controlled by maxConcurrentSends from the SDK config.

Querying messages

Returns a . Each entry is a .

Unread messages

getUnreadMessages() groups results by sender:

Listing chats

Returns an array of .

Real-time watching

The watcher polls the Messages database and fires per registered callback — see for every available hook.

Auto-reply with the message chain

sdk.message(msg) returns a — a fluent builder for filter-then-respond patterns:
Chain guards to skip tapbacks, filter by chat kind, match on predicates, and choose a response:

Attachment helpers

Each attachment is an .

Common media formats

Messages carries a wide range of formats through without transcoding. The SDK’s isImage / isVideo / isAudio helpers classify by the stored MIME type — there’s no allowlist.

Scheduling

MessageScheduler

The constructor takes (sdk, config?, events?) — three positional args. Config is ; one-shot task shape is and recurring is . The scheduler loop is internal — no start() call needed.

Reminders — natural-language scheduling

All scheduling calls take an optional . Supported expressions:
  • Duration: "5 minutes", "2 hours", "1 day", "30 seconds", "1 week"
  • Time: "5pm", "5:30pm", "17:30"
  • Day + time: "tomorrow 9am", "friday 2pm"

Plugins

See for the full hook surface. Hooks take positional args — onBeforeSend(to, content), onAfterSend(to, result) — not a single object. definePlugin() is a helper that just returns its argument for type inference.

Error handling

Every thrown error is an . The factory functions (PlatformError, DatabaseError, SendError, WebhookError, ConfigError) produce IMessageError instances tagged with the corresponding code — switch on error.code rather than instanceof for each subtype.