Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
ce9dc00
Add iMessage adapter package scaffold
underthestars-zhy Feb 26, 2026
17418b6
Add remote config support to iMessage adapter
underthestars-zhy Feb 26, 2026
077ac9b
Remove userName config from iMessage adapter
underthestars-zhy Feb 26, 2026
0f4347c
Store chat instance in iMessage adapter
underthestars-zhy Feb 26, 2026
62a85c3
Add iMessage adapter tests and types
underthestars-zhy Feb 27, 2026
31d7133
Implement postMessage for iMessage adapter
underthestars-zhy Feb 27, 2026
28cf6f1
Add remote mode connection and ready wait in iMessage adapter
underthestars-zhy Feb 28, 2026
2e4484b
Implement editMessage for iMessage remote adapter
underthestars-zhy Feb 28, 2026
3b13c2f
Add `parseMessage` implementation and tests for iMessage adapter
underthestars-zhy Feb 28, 2026
f9ae490
Implement `fetchThread` for iMessage remote mode
underthestars-zhy Feb 28, 2026
19e25cb
Implement addReaction/removeReaction for iMessage adapter
underthestars-zhy Feb 28, 2026
a4a9129
Implement startTyping for iMessage remote mode
underthestars-zhy Feb 28, 2026
605df2b
Add iMessage format converter and shared adapter dependency
underthestars-zhy Feb 28, 2026
b691e46
add meta
underthestars-zhy Feb 28, 2026
504e74d
Add iMessage adapter docs and README
underthestars-zhy Feb 28, 2026
f664c6f
Implement fetchMessages for iMessage adapter
underthestars-zhy Feb 28, 2026
f113029
Update imessage.mdx
underthestars-zhy Feb 28, 2026
37691f9
support file
underthestars-zhy Feb 28, 2026
5bca3ca
Update README to include iMessage adapter
underthestars-zhy Feb 28, 2026
4c55f14
Update index.mdx
underthestars-zhy Feb 28, 2026
dd8a456
Merge upstream/main into feat/imessage
underthestars-zhy Feb 28, 2026
e5a86ed
Fix Teams file upload support status in adapter docs
underthestars-zhy Feb 28, 2026
f4693bb
Add validation to iMessage `decodeThreadId` for wrong adapter
underthestars-zhy Feb 28, 2026
fee6bd0
Fix handleGatewayMessage to use processMessage and pass options
underthestars-zhy Feb 28, 2026
9ce26fb
Update README.md
underthestars-zhy Feb 28, 2026
da14fab
Update apps/docs/content/docs/adapters/index.mdx
underthestars-zhy Feb 28, 2026
9fb33da
Update apps/docs/content/docs/adapters/index.mdx
underthestars-zhy Feb 28, 2026
a367666
Fix gateway listener closing shared SDK in remote mode
underthestars-zhy Feb 28, 2026
3bcc3aa
Update imessage.mdx
underthestars-zhy Feb 28, 2026
a448148
Merge branch 'feat/imessage' of https://github.com/photon-hq/vercel-c…
underthestars-zhy Feb 28, 2026
8696487
Remove webhook forwarding from iMessage adapter
underthestars-zhy Feb 28, 2026
b08ad8d
Separate webhook token from remote API key in iMessage adapter
underthestars-zhy Feb 28, 2026
585c8c1
Remove webhook mode from iMessage adapter
underthestars-zhy Feb 28, 2026
706ad05
Add Poll element type and iMessage native poll support
underthestars-zhy Feb 28, 2026
3ad0480
Fix poll message handling in extractMessageContent
underthestars-zhy Feb 28, 2026
f7482e8
Fix iMessage DM detection to use chat GUID format
underthestars-zhy Feb 28, 2026
ed1561b
Add poll documentation
underthestars-zhy Feb 28, 2026
c6a1139
Fix Poll JSX syntax and add imessage-kit link in docs
underthestars-zhy Feb 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ See the [Getting Started guide](https://chat-sdk.dev/docs/getting-started) for a
| Telegram | `@chat-adapter/telegram` | Yes | Yes | Partial | No | Post+Edit | Yes |
| GitHub | `@chat-adapter/github` | Yes | Yes | No | No | No | No |
| Linear | `@chat-adapter/linear` | Yes | Yes | No | No | No | No |
| iMessage | `@chat-adapter/imessage` | DM-only | Remote-only | No | No | No | Yes |

## Features

Expand Down Expand Up @@ -84,6 +85,7 @@ See the [Getting Started guide](https://chat-sdk.dev/docs/getting-started) for a
| `@chat-adapter/linear` | [Linear adapter](https://chat-sdk.dev/docs/adapters/linear) |
| `@chat-adapter/state-redis` | [Redis state adapter](https://chat-sdk.dev/docs/state/redis) (production) |
| `@chat-adapter/state-ioredis` | [ioredis state adapter](https://chat-sdk.dev/docs/state/ioredis) (alternative) |
| `@chat-adapter/imessage` | [iMessage adapter](https://chat-sdk.dev/docs/adapters/imessage) |
| `@chat-adapter/state-memory` | [In-memory state adapter](https://chat-sdk.dev/docs/state/memory) (development) |

## AI coding agent support
Expand Down
241 changes: 241 additions & 0 deletions apps/docs/content/docs/adapters/imessage.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
---
title: iMessage
description: Configure the iMessage adapter with local (on-device) or remote (server-based) integration.
type: integration
prerequisites:
- /docs/getting-started
---

## Installation

```sh title="Terminal"
pnpm add @chat-adapter/imessage
```

## Usage

The adapter supports two modes: **local** (running directly on a Mac with iMessage) and **remote** (connecting to a [Photon](https://photon.codes) iMessage server). The mode is auto-detected from the `IMESSAGE_LOCAL` environment variable.

### Remote mode

Recommended for production. Connects to [Photon](https://photon.codes)'s managed iMessage service over HTTP and Socket.IO, so your bot can run on any platform.

```typescript title="lib/bot.ts" lineNumbers
import { Chat } from "chat";
import { createiMessageAdapter } from "@chat-adapter/imessage";

const bot = new Chat({
userName: "mybot",
adapters: {
imessage: createiMessageAdapter({
local: false,
}),
},
});

bot.onNewMention(async (thread, message) => {
await thread.post("Hello from iMessage!");
});
```

### Local mode

For development or self-hosted deployments using [imessage-kit](https://github.com/photon-hq/imessage-kit). Reads from the local iMessage database and sends via AppleScript. Must run on macOS with **Full Disk Access** granted.

```typescript title="lib/bot.ts" lineNumbers
import { Chat } from "chat";
import { createiMessageAdapter } from "@chat-adapter/imessage";

const bot = new Chat({
userName: "mybot",
adapters: {
imessage: createiMessageAdapter({
local: true,
}),
},
});

bot.onNewMention(async (thread, message) => {
await thread.post("Hello from iMessage!");
});
```

## Setup

### Remote mode

Remote mode connects to a [Photon](https://photon.codes) iMessage server, which handles the macOS-side integration on your behalf. You'll need an active Photon subscription to get your server credentials.

1. [Request access](https://photon.codes) from Photon to get your server credentials
2. Copy your **server URL** and **API key** from the Photon dashboard
3. Set `IMESSAGE_SERVER_URL` and `IMESSAGE_API_KEY` environment variables
4. Set `IMESSAGE_LOCAL=false`

### Local mode

Local mode requires the adapter to run directly on a macOS machine with iMessage. It uses `@photon-ai/imessage-kit` to read from the local `chat.db` database and send messages via AppleScript.

1. Grant **Full Disk Access** to your terminal or application in **System Settings → Privacy & Security → Full Disk Access**
2. Ensure iMessage is signed in and working on the Mac
3. No additional environment variables are required — local mode is the default

## Receiving messages

Call `startGatewayListener()` to listen for new messages in real-time. In remote mode, this uses Socket.IO push events. In local mode, it polls the iMessage database.

- In serverless environments, use a cron job to maintain the connection

## Gateway setup for serverless

### 1. Create Gateway route

```typescript title="app/api/imessage/gateway/route.ts"
import { after } from "next/server";
import { bot } from "@/lib/bot";

export const maxDuration = 800;

export async function GET(request: Request): Promise<Response> {
const cronSecret = process.env.CRON_SECRET;
if (!cronSecret) {
return new Response("CRON_SECRET not configured", { status: 500 });
}

const authHeader = request.headers.get("authorization");
if (authHeader !== `Bearer ${cronSecret}`) {
return new Response("Unauthorized", { status: 401 });
}

const durationMs = 600 * 1000;

return bot.adapters.imessage.startGatewayListener(
{ waitUntil: (task) => after(() => task) },
durationMs
);
}
```

### 2. Configure Vercel Cron

```json title="vercel.json"
{
"crons": [
{
"path": "/api/imessage/gateway",
"schedule": "*/9 * * * *"
}
]
}
```

This runs every 9 minutes, ensuring overlap with the 10-minute listener duration.

### 3. Add environment variables

Add `CRON_SECRET` to your Vercel project settings.

## Configuration

| Option | Required | Description |
|--------|----------|-------------|
| `local` | No | `true` for local mode, `false` for remote. Auto-detected from `IMESSAGE_LOCAL` (default: `true`) |
| `serverUrl` | Remote only | URL of the remote iMessage server. Auto-detected from `IMESSAGE_SERVER_URL` |
| `apiKey` | Remote only | API key for remote server authentication. Auto-detected from `IMESSAGE_API_KEY` |
| `logger` | No | Logger instance (defaults to `ConsoleLogger("info")`) |

## Environment variables

```bash title=".env.local"
IMESSAGE_LOCAL=false # Set to "false" for remote mode (default: true)
IMESSAGE_SERVER_URL=https://... # Required for remote mode
IMESSAGE_API_KEY=... # Required for remote mode
```

## Features

| Feature | Supported |
|---------|-----------|
| Mentions | DMs only |
| Reactions (add/remove) | Remote only |
| Polls | Remote only |
| Cards | No |
| Modals | No |
| Streaming | No |
| DMs | Yes |
| Ephemeral messages | No |
| File uploads | Yes |
| Typing indicator | Remote only |
| Message history | Yes |
| Message editing | Remote only |

## Polls

Remote mode supports native iMessage polls. Post a `Poll` to create a voting experience directly in the chat. Vote callbacks are delivered via `onAction`.

```tsx title="lib/bot.tsx" lineNumbers
import { Poll } from "chat";

bot.onNewMention(async (thread, message) => {
await thread.post(
<Poll
id="fav-color"
question="What is your favorite color?"
options={["Red", "Blue", "Green"]}
/>
);
});

bot.onAction("fav-color", async (event) => {
await event.thread.post(`${event.user.fullName} voted for ${event.value}!`);
});
```

<Callout type="info">
Polls are only available in remote mode. Attempting to post a poll in local mode will throw a `NotImplementedError`.
</Callout>

See the [Polls guide](/docs/polls) for more details.

## Tapback reactions

iMessage uses tapbacks instead of emoji reactions. The adapter maps standard emoji names to iMessage tapbacks:

| Emoji | Tapback |
|-------|---------|
| `love` / `heart` | ❤️ Love |
| `like` / `thumbs_up` | 👍 Like |
| `dislike` / `thumbs_down` | 👎 Dislike |
| `laugh` | 😂 Laugh |
| `emphasize` / `exclamation` | ‼️ Emphasize |
| `question` | ❓ Question |

## Limitations

- **Local mode**: Only supports sending/receiving messages, message history, and file uploads. Reactions, typing indicators, message editing, and thread fetching require remote mode.
- **Formatting**: iMessage is plain-text only. Markdown formatting (bold, italic, etc.) is stripped when sending messages, preserving only the text content.
- **Platform**: Local mode requires macOS. Remote mode can run on any platform — [Photon](https://photon.codes) manages the iMessage infrastructure for you.
- **Cards and modals**: iMessage has no support for structured card layouts or interactive modals.
- **Polls**: Only supported in remote mode. Local mode does not have poll support.

## Troubleshooting

### "serverUrl is required" error

- Set `IMESSAGE_SERVER_URL` or pass `serverUrl` in config when using remote mode
- This error occurs when `IMESSAGE_LOCAL=false` but no server URL is provided

### "apiKey is required" error

- Set `IMESSAGE_API_KEY` or pass `apiKey` in config when using remote mode

### Local mode not receiving messages

- Verify **Full Disk Access** is granted to your terminal or application
- Check that iMessage is signed in and working
- Messages are polled from the local database — there may be a short delay

### Remote mode connection issues

- Verify the server URL is correct and accessible
- Check that the API key matches your Photon iMessage service credentials
- Confirm your Photon subscription is active
69 changes: 35 additions & 34 deletions apps/docs/content/docs/adapters/index.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Overview
description: Platform-specific adapters for Slack, Teams, Google Chat, Discord, Telegram, GitHub, and Linear.
description: Platform-specific adapters for Slack, Teams, Google Chat, iMessage, Discord, Telegram, GitHub, and Linear.
type: overview
prerequisites:
- /docs/getting-started
Expand All @@ -12,48 +12,48 @@ Adapters handle webhook verification, message parsing, and API calls for each pl

### Messaging

| Feature | [Slack](/docs/adapters/slack) | [Teams](/docs/adapters/teams) | [Google Chat](/docs/adapters/gchat) | [Discord](/docs/adapters/discord) | [Telegram](/docs/adapters/telegram) | [GitHub](/docs/adapters/github) | [Linear](/docs/adapters/linear) |
|---------|-------|-------|-------------|---------|---------|--------|--------|
| Post message | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Edit message | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Delete message | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| File uploads | ✅ | ✅ | ❌ | ✅ | ⚠️ Single file | ❌ | ❌ |
| Streaming | ✅ Native | ⚠️ Post+Edit | ⚠️ Post+Edit | ⚠️ Post+Edit | ⚠️ Post+Edit | ❌ | ❌ |
| Feature | [Slack](/docs/adapters/slack) | [Teams](/docs/adapters/teams) | [Google Chat](/docs/adapters/gchat) | [iMessage](/docs/adapters/imessage) | [Discord](/docs/adapters/discord) | [Telegram](/docs/adapters/telegram) | [GitHub](/docs/adapters/github) | [Linear](/docs/adapters/linear) |
|---------|-------|-------|-------------|----------|---------|----------|--------|--------|
| Post message | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Edit message | ✅ | ✅ | ✅ | ⚠️ Remote only | ✅ | ✅ | ✅ | ✅ |
| Delete message | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ |
| File uploads | ✅ | ✅ | ❌ | ✅ | ✅ | ⚠️ Single file | ❌ | ❌ |
| Streaming | ✅ Native | ⚠️ Post+Edit | ⚠️ Post+Edit | ❌ | ⚠️ Post+Edit | ⚠️ Post+Edit | ❌ | ❌ |

### Rich content

| Feature | Slack | Teams | Google Chat | Discord | Telegram | GitHub | Linear |
|---------|-------|-------|-------------|---------|----------|--------|--------|
| Card format | Block Kit | Adaptive Cards | Google Chat Cards | Embeds | Markdown + inline keyboard buttons | GFM Markdown | Markdown |
| Buttons | ✅ | ✅ | ✅ | ✅ | ⚠️ Inline keyboard callbacks | ❌ | ❌ |
| Link buttons | ✅ | ✅ | ✅ | ✅ | ⚠️ Inline keyboard URLs | ❌ | ❌ |
| Select menus | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
| Fields | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Images in cards | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ |
| Modals | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Feature | Slack | Teams | Google Chat | iMessage | Discord | Telegram | GitHub | Linear |
|---------|-------|-------|-------------|----------|---------|----------|--------|--------|
| Card format | Block Kit | Adaptive Cards | Google Chat Cards | Plain text | Embeds | Markdown + inline keyboard buttons | GFM Markdown | Markdown |
| Buttons | ✅ | ✅ | ✅ | ❌ | ✅ | ⚠️ Inline keyboard callbacks | ❌ | ❌ |
| Link buttons | ✅ | ✅ | ✅ | ❌ | ✅ | ⚠️ Inline keyboard URLs | ❌ | ❌ |
| Select menus | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Fields | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ |
| Images in cards | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ |
| Modals | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |

### Conversations

| Feature | Slack | Teams | Google Chat | Discord | Telegram | GitHub | Linear |
|---------|-------|-------|-------------|---------|----------|--------|--------|
| Mentions | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Add reactions | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Remove reactions | ✅ | ❌ | ✅ | ✅ | ✅ | ⚠️ | ⚠️ |
| Typing indicator | ❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ |
| DMs | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| Ephemeral messages | ✅ Native | ❌ | ✅ Native | ❌ | ❌ | ❌ | ❌ |
| Feature | Slack | Teams | Google Chat | iMessage | Discord | Telegram | GitHub | Linear |
|---------|-------|-------|-------------|----------|---------|----------|--------|--------|
| Mentions | ✅ | ✅ | ✅ | ⚠️ DMs only | ✅ | ✅ | ✅ | ✅ |
| Add reactions | ✅ | ❌ | ✅ | ⚠️ Remote only | ✅ | ✅ | ✅ | ✅ |
| Remove reactions | ✅ | ❌ | ✅ | ⚠️ Remote only | ✅ | ✅ | ⚠️ | ⚠️ |
| Typing indicator | ❌ | ✅ | ❌ | ⚠️ Remote only | ✅ | ✅ | ❌ | ❌ |
| DMs | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| Ephemeral messages | ✅ Native | ❌ | ✅ Native | ❌ | ❌ | ❌ | ❌ | ❌ |

### Message history

| Feature | Slack | Teams | Google Chat | Discord | Telegram | GitHub | Linear |
|---------|-------|-------|-------------|---------|----------|--------|--------|
| Fetch messages | ✅ | ✅ | ✅ | ✅ | ⚠️ Cached | ✅ | ✅ |
| Fetch single message | ✅ | ❌ | ❌ | ❌ | ⚠️ Cached | ❌ | ❌ |
| Fetch thread info | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Fetch channel messages | ✅ | ✅ | ✅ | ✅ | ⚠️ Cached | ✅ | ❌ |
| List threads | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ |
| Fetch channel info | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
| Post channel message | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| Feature | Slack | Teams | Google Chat | iMessage | Discord | Telegram | GitHub | Linear |
|---------|-------|-------|-------------|----------|---------|----------|--------|--------|
| Fetch messages | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️ Cached | ✅ | ✅ |
| Fetch single message | ✅ | ❌ | ❌ | ❌ | ❌ | ⚠️ Cached | ❌ | ❌ |
| Fetch thread info | ✅ | ✅ | ✅ | ⚠️ Remote only | ✅ | ✅ | ✅ | ✅ |
| Fetch channel messages | ✅ | ✅ | ✅ | ❌ | ✅ | ⚠️ Cached | ✅ | ❌ |
| List threads | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ |
| Fetch channel info | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ |
| Post channel message | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ |

<Callout type="info">
⚠️ indicates partial support — the feature works with limitations. See individual adapter pages for details.
Expand All @@ -66,6 +66,7 @@ Adapters handle webhook verification, message parsing, and API calls for each pl
| [Slack](/docs/adapters/slack) | `@chat-adapter/slack` |
| [Microsoft Teams](/docs/adapters/teams) | `@chat-adapter/teams` |
| [Google Chat](/docs/adapters/gchat) | `@chat-adapter/gchat` |
| [iMessage](/docs/adapters/imessage) | `@chat-adapter/imessage` |
| [Discord](/docs/adapters/discord) | `@chat-adapter/discord` |
| [Telegram](/docs/adapters/telegram) | `@chat-adapter/telegram` |
| [GitHub](/docs/adapters/github) | `@chat-adapter/github` |
Expand Down
1 change: 1 addition & 0 deletions apps/docs/content/docs/adapters/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"slack",
"teams",
"gchat",
"imessage",
"discord",
"telegram",
"github",
Expand Down
1 change: 1 addition & 0 deletions apps/docs/content/docs/api/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"message",
"postable-message",
"cards",
"polls",
"markdown",
"modals"
]
Expand Down
Loading