chat-adapter-imessage) connects Chat SDK bots to iMessage. It’s built on spectrum-ts, so the same bot code reaches iMessage through any of three modes:
- Cloud (recommended) — connects to Spectrum Cloud with a project ID and secret. Runs anywhere, including serverless.
- Self-hosted — connects to your own
@photon-ai/advanced-imessagegRPC endpoint. - Local — reads the on-device Messages database and sends through Apple’s native APIs. macOS only.
Installation
chat is the Chat SDK; chat-adapter-imessage is the iMessage adapter for it.
Add the adapter
Register the adapter underadapters.imessage when you construct your bot, then handle messages with the usual Chat SDK callbacks.
- Cloud
- Self-hosted
- Local
Connects to Spectrum Cloud over gRPC. Get your project ID and secret from the dashboard.
Configuration
createiMessageAdapter(options) accepts the options below. Each has an environment-variable fallback, so you can configure the adapter entirely through the environment and pass no arguments.
| Option | Required | Description |
|---|---|---|
local | No | true for local, false for cloud/self-host. Defaults to local unless local: false, IMESSAGE_LOCAL=false, or remote credentials are provided. |
projectId | Cloud | Spectrum Cloud project ID. Falls back to IMESSAGE_PROJECT_ID. |
projectSecret | Cloud | Spectrum Cloud project secret. Falls back to IMESSAGE_PROJECT_SECRET. |
serverUrl | Self-host | gRPC host:port of your iMessage server. Falls back to IMESSAGE_SERVER_URL. |
apiKey | Self-host | Auth token for the self-hosted server. Falls back to IMESSAGE_API_KEY. |
clients | No | Explicit { address, token, phone }[] for multi-number self-host setups. |
phone | No | Routing/identity phone for legacy self-host (defaults to "shared"). Falls back to IMESSAGE_PHONE. |
webhookSecret | No | Per-webhook signing secret for verifying Spectrum Cloud deliveries. Required to receive webhooks. Falls back to IMESSAGE_WEBHOOK_SECRET. |
logger | No | Logger instance (defaults to ConsoleLogger("info")). |
Environment variables
Receiving messages
There are two ways to receive inbound messages:- Webhooks (recommended for serverless) — Spectrum Cloud delivers each message to an HTTPS endpoint as signed JSON. No long-lived connection or cron job. Cloud mode only.
- Gateway listener —
startGatewayListener()consumes spectrum-ts’s message stream in real time. Works in all modes; in serverless it needs a cron job to stay connected.
Webhooks
In cloud mode, Spectrum Cloud delivers inbound messages to your HTTPS endpoint as signed JSON — see the webhook docs. This is the simplest path for serverless: no cron, no persistent connection.Register the endpoint
In the Spectrum Cloud dashboard, register your endpoint URL (public HTTPS only) and copy the per-webhook signing secret — it is shown only once.
Configure the secret
Set
IMESSAGE_WEBHOOK_SECRET to that signing secret. The adapter verifies the X-Spectrum-Signature HMAC on every delivery and rejects unsigned, mismatched, or stale (>5 min) requests.X-Spectrum-Webhook-Id + message.id if you need exactly-once side effects.
A webhook delivery carries no live connection, but your bot can still respond to a DM: the adapter rebuilds the thread from its address and sends, reacts, edits, and shows typing over spectrum-ts — no gateway needed.
Gateway listener for serverless
The gateway listener keeps a live spectrum-ts stream open. In serverless, run it on a cron so it reconnects before each window expires.Feature support
“Remote” below means cloud or self-hosted mode — anything other thanlocal: true.
Feature support
Which Chat SDK capabilities the iMessage adapter implements, by mode.
Feature support
Which Chat SDK capabilities the iMessage adapter implements, by mode.
| Feature | Supported |
|---|---|
| Mentions | DMs only |
| DMs | Yes |
| File uploads | Yes (send) |
| Reactions (add) | Remote only |
| Reactions (remove) | No |
| Message editing | Remote only |
| Typing indicator | Remote only |
| Modals | Limited (remote only) |
| Message history | No |
| Thread/chat info | No |
| Cards | No |
| Streaming | No |
| Ephemeral messages | No |
| Webhooks | Yes (remote — Spectrum Cloud delivery) |
Modals
Remote mode supports limited modals by mapping Chat SDK’sopenModal() to iMessage native polls. Only Select children are supported — the first Select in the modal becomes a poll:
Modal.titlebecomes the poll question.Select.optionsbecome the poll choices (2–10 supported).- Votes trigger
onModalSubmitwith the selected option’svalue.
NotImplementedError.
Not supported:
Select.placeholder/label, TextInput, RadioSelect, Modal.submitLabel/closeLabel, more than one Select, and poll vote deselection.Tapback reactions
iMessage uses tapbacks instead of emoji reactions. The adapter maps standard emoji names to iMessage tapbacks.Tapback mapping
Emoji names accepted by addReaction and the iMessage tapback each maps to.
Tapback mapping
Emoji names accepted by addReaction and the iMessage tapback each maps to.
| Emoji name | Tapback |
|---|---|
love / heart | Love |
like / thumbs_up | Like |
dislike / thumbs_down | Dislike |
laugh | Laugh |
emphasize / exclamation | Emphasize |
question | Question |
Limitations
- DMs send cold; groups are session-bound. For a DM, the adapter rebuilds the thread from its address over gRPC, so it can send, react, edit, and show typing even into a thread it hasn’t seen this session — including a webhook delivery. A group has no by-id resolver, so addressing one requires it to have been received over the gateway/stream in the current session; cold sends to an unseen group throw
NotImplementedError. Local mode cannot create spaces at all — it only replies to received messages. - No message history.
fetchMessagesis not supported — spectrum-ts exposes no paginated history API. - No thread/chat info.
fetchThreadis not supported. - No reaction removal.
removeReactionis not supported. - Local mode supports sending and receiving, but not reactions, typing, editing, modals, history, or thread info.
- Formatting. iMessage is plain-text only; Markdown formatting is stripped when sending, preserving the text content.
- Platform. Local mode requires macOS. Cloud and self-host run anywhere.
- Cards. iMessage has no structured card layouts.
Troubleshooting
serverUrl is required when local is false
serverUrl is required when local is false
Provide cloud credentials (
IMESSAGE_PROJECT_ID + IMESSAGE_PROJECT_SECRET), or a self-host IMESSAGE_SERVER_URL + IMESSAGE_API_KEY.Self-host connection issues
Self-host connection issues
Confirm
IMESSAGE_SERVER_URL is a gRPC host:port (e.g. imessage.example.com:443), not an https:// URL. Verify the token matches your server’s credentials.Local mode not receiving messages
Local mode not receiving messages
Verify Full Disk Access is granted to your terminal or application, and that iMessage is signed in and working on the Mac.
NotImplementedError from fetchMessages / fetchThread / removeReaction
NotImplementedError from fetchMessages / fetchThread / removeReaction
These are not supported by spectrum-ts. See Limitations.