Skip to content

iMessage ​

Poki uses BlueBubbles to receive iMessage tasks and return replies. Install it only if you want this channel. The normal ./bin/setup build does not download a gateway.

Install ​

Use macOS on Apple Silicon or Intel, Python 3.11 or newer, and the macOS curl and ditto tools. The channel itself also needs Node.js 24+ and the Poki Agent prerequisites.

From the repository root, run:

sh
./bin/setup-channels

Choose imessage to install, or press Enter for none. Choosing none makes no network requests and writes no files. The explicit equivalents are ./bin/setup-channels --channel imessage and ./bin/setup-channels --channel none.

The installer asks for the local server URL, the single allowed sender, the Poki device name or ID, the exact direct chat GUID, and a server password. Password input is hidden. Choose a password now and set the same one in BlueBubbles when completing its setup. For a self-chat, the GUID can be any;-;<sender> or iMessage;-;<sender>; use the exact value for that conversation. See the existing channel guide for sender scoping and self-chat behavior.

The command downloads the architecture-specific ZIP from the official 1.9.12 release, verifies the pinned SHA-256 digest before extraction, and checks the app version. It installs ~/Applications/BlueBubbles.app and creates ~/.config/poki-agent/imessage/:

  • imessage.json: sender, direct chat GUID, device and ChatGPT authentication selection.
  • connection.json: local server URL and password. Keep this file private.
  • state/: created by the channel later for its cursor, reply ledger and task records.

The configuration directory has mode 700; its two JSON files have mode 600. The installer does not print the password, start BlueBubbles, sign in, grant permissions, start a worker or send messages. Download and verification failures remove the temporary archive and leave no app or channel configuration. Installation failures remove only the new app and configuration created by that attempt. Existing app or configuration destinations cause an error and are left unchanged.

Choose unused destinations when testing or keeping an existing installation:

sh
./bin/setup-channels --channel imessage \
  --app-dir /path/to/clean/Applications \
  --config-dir /path/to/clean/imessage

For offline installation, add --archive /path/to/BlueBubbles-1.9.12-arm64-mac.zip (Apple Silicon) or the matching Intel BlueBubbles-1.9.12-mac.zip. This still requires the pinned digest. There is no option to skip verification or use a moving latest release. Release provenance and digests are recorded in the installer; the archive itself is not committed to Git.

For unattended installation, use --answers /path/to/private-answers.json instead of interactive input. Create it with mode 600 and these fields: url, password, sender, device, and optional chatGuid. Use the same formats as the prompts. Keep it outside the repository and remove it after successful installation. Do not put passwords in command arguments, shell history, screenshots or logs. The installer copies the password into the private connection file; it does not change BlueBubbles' own settings.

Complete the steps that require you ​

  1. Open Messages → Settings → iMessage, then sign in to your Apple Account. Complete Apple verification yourself. The installer never reads your Apple credentials.
  2. Open the installed BlueBubbles.app. In its Intro → Permissions setup, follow the links to System Settings → Privacy & Security → Full Disk Access and enable this copy of BlueBubbles. Follow the Accessibility prompt if you use features that need it. When macOS asks for Messages automation permission, allow BlueBubbles to control Messages.
  3. In BlueBubbles' Connection step, set the password you chose in the installer and click the save icon. Use the same local server port as the saved Poki URL. If you change the port or password later, update the private connection.json to match. Follow BlueBubbles' remaining onboarding steps, including notification setup if requested, using its official setup guide.
  4. Keep the server reachable locally. Poki's polling channel does not require exposing a public URL, enabling Private API or disabling SIP. Do not change those settings just to send ordinary text through Poki.

The official 1.9.12 release is signed and notarized. If macOS blocks it, inspect the system warning and consult the release notes; this installer does not remove quarantine attributes or bypass Gatekeeper.

Check, then start ​

Install the agent dependencies and complete its model login as described in the agent guide. For a custom destination, include --config-dir /path/to/clean/imessage in both commands below.

sh
node bin/poki-imessage.mjs --check

This loads the password privately, authenticates and reads only the configured conversation. It sends no reply and starts no phone task. A successful check confirms the gateway connection and scoped read, not phone automation or message delivery.

Once you have prepared Poki's MCP service, the selected device and model access, start the channel explicitly:

sh
node bin/poki-imessage.mjs

Only new messages after the first startup become tasks. Keep this process running. Do not run two workers for the same conversation or delete its state directory to restart it. Stop the worker with Ctrl-C before moving or removing its private configuration. The launcher uses the configuration directory for state; it does not use the checkout's agent/.runtime.

Verification scope ​

The install path was exercised with the official Apple Silicon release into a clean directory, with version and file permissions checked. Failure and opt-out paths use the same command. The private-config launcher was checked against a local fake gateway without sending messages. The Intel asset is pinned from the official release metadata but has not been installed on an Intel Mac. Apple Account login, macOS permissions and a real message exchange remain steps for the user; installing an app does not prove those steps work.

Channel behavior ​

Install and start BlueBubbles Server. In its settings, enable the local HTTP server and set a server password. On the same Mac, its default URL is http://127.0.0.1:1234. BlueBubbles owns Messages permissions. Poki Agent does not read the Messages database or run a native message helper.

Set the server URL and password in the worker's environment. Use HTTPS when the server is on another machine. Keep credentials out of configuration files, shell history, and source control. For example, read the password without displaying it in zsh:

sh
export BLUEBUBBLES_URL=http://127.0.0.1:1234
read -rs 'BLUEBUBBLES_PASSWORD?BlueBubbles password: '
export BLUEBUBBLES_PASSWORD

Create ~/.config/poki-agent/imessage.json as a private JSON file. Set sender to the exact iMessage phone number, including country code, or email address. Use one direct sender. Create the parent directory first with mkdir -p ~/.config/poki-agent.

json
{
  "sender": "allowed@example.test",
  "device": "<device UUID>",
  "model": "<model available to your account>",
  "auth": "chatgpt"
}

Restrict its permissions. Check authentication and the scoped read before starting the worker from the repository root:

sh
chmod 600 ~/.config/poki-agent/imessage.json
node agent/imessage.mjs --config ~/.config/poki-agent/imessage.json --check
node agent/imessage.mjs --config ~/.config/poki-agent/imessage.json

--check sends no replies and starts no phone task. The first start records existing message IDs and skips history. It queries only the configured direct chat and sender, excludes outgoing messages and reactions, and never lists all conversations. Messages arriving after this baseline become tasks.

For a self-chat, set chatGuid in the private configuration to the exact direct GUID reported by BlueBubbles for that sender. Both iMessage;-;<sender> and any;-;<sender> are supported. Without chatGuid, the worker uses iMessage;-;<sender>. Verify this value with a query restricted to the intended conversation before starting. A self-chat must produce a received copy with isFromMe=false; the sent copy remains excluded.

Replies are plain text, with no visible prefix or invisible marker. Before each send, the worker records the exact text in a private reply ledger scoped to the server and direct chat. Each entry suppresses one matching received copy and expires after 24 hours if unused. A consumed entry stays tied to its received message ID until the cursor acknowledges it, so restarting before acknowledgement cannot turn that copy into a task. After consumption, a user can send identical text as a new task. The former bridge's 🤖 Codex: replies are still ignored.

The ledger survives restart and uses file mode 600. Keep it with the channel cursor. A definite HTTP client rejection removes the entry; an uncertain send keeps it until consumption or expiry. If identical user text arrives before the synchronized reply, content-only matching treats the first copy as the reply. Set context in the private configuration to pass initial task context to every task. Stop the former bridge before starting Poki Agent so only one backend handles the chat.

Each iMessage task immediately sends a local confirmation based on the user's own words, before constructing the phone task or making a model request. Model text is not sent as a second opening. The confirmation goes through the same persistent reply ledger as every reply. The worker stays running and polls iMessage every 200 ms plus request time. The opening-sent log records receive-to-send duration without message contents. Task records separately measure model setup, MCP setup/status and model request timing.

After the confirmation, iMessage sends only the final result, without intermediate progress or status prefixes. Send /stop to stop a task. Other messages while a task runs become task corrections. The cursor is saved before dispatch, so interrupted tasks are not replayed after restart. A failed or uncertain reply stops the worker without an automatic resend. Check the chat before restarting.

The default private state directory is agent/.runtime/bluebubbles. Use --state <directory> to change it. Keep one worker per channel configuration. The old SQLite cursor and --fake-db/--outbox flags are not used.

For an isolated MCP and worker lifecycle, run node agent/scripts/self-chat.mjs --config <private JSON file> --state <private directory> from the repository root. Add --check for a read-only startup check. The supervisor records device-call notifications from this worker. Stopping an idle worker closes its services without acquiring or cleaning up a device. Physical-device runs must still use the shared device lock.