Migrating from BlueBubbles to the Official iMessage Plugin
This guide helps users convert old BlueBubbles configurations to the official iMessage plugin, covering key mapping, group allowlist gates, and cutover verification. It is essential for anyone currently using BlueBubbles with OpenClaw.
Read this when
- Planning a move from BlueBubbles to the official iMessage plugin
- Translating BlueBubbles config keys to iMessage equivalents
- Verifying imsg before enabling the iMessage plugin
BlueBubbles is no longer supported. iMessage access through OpenClaw now goes exclusively through the official @openclaw/imessage plugin, which talks to steipete/imsg using JSON-RPC and exposes the same private API capabilities BlueBubbles relied on (react, edit, unsend, reply, sendWithEffect, native polls, group management, attachments). A single CLI binary takes the place of the BlueBubbles server, client app, and webhook setup: there is no REST endpoint and no webhook authentication anymore.
This guide walks you through converting old channels.bluebubbles configurations to channels.imessage. No other migration route is supported. On current OpenClaw builds, a leftover channels.bluebubbles block does nothing, since no runtime reads it.
Note
For the brief announcement and operator-level details, check BlueBubbles removal and the imsg iMessage path.
Migration checklist
If you already have your old BlueBubbles config handy, this is the quickest safe route:
- Use
openclaw plugins install @openclaw/imessageto install the official plugin, then restart the Gateway. - Confirm
imsgdirectly on the Mac where Messages.app runs (imsg chats,imsg history,imsg send,imsg rpc --help). - Move behavior-related keys from
channels.bluebubblesover tochannels.imessage:dmPolicy,allowFrom,groupPolicy,groupAllowFrom,groups,includeAttachments,attachmentRoots,mediaMaxMb,textChunkLimit, andactions. - Remove transport keys that are no longer valid:
serverUrl,password, webhook URLs, and BlueBubbles server configuration. - When the Gateway lives on a different machine than the Messages Mac, point
channels.imessage.cliPathat the absolute Gateway-local path for an SSH wrapper, and leavedbPathas an absolute path on that Mac. For wrappers with more complexity, setremoteHostto the Messages Mac; the simple transparent wrapper shape is auto-detected by OpenClaw for compatibility. - Turn on
channels.imessage, restart the Gateway, and then executeopenclaw channels status --probe --channel imessage. - Try out one DM, one approved group, attachments if they are enabled, and every private API action the agent is expected to use.
- Once the iMessage path checks out, remove the BlueBubbles server and the old
channels.bluebubblesconfig.
Note
Remote
imsgv0.13.4 comes with two specific RPC constraints: poll votes have to usepollOptionIdrather than an index or option text, and attachment replies cannot point at a nonzero message part index. Localimsgbehavior stays the same.
What imsg does
imsg is a local macOS command-line tool for Messages. OpenClaw launches imsg rpc as a child process and communicates via JSON-RPC over stdin/stdout. No HTTP server, webhook URL, background daemon, launch agent, or exposed port is involved.
- Data reads go through
~/Library/Messages/chat.dbusing a read-only SQLite connection. - Incoming messages in real time come from
imsg watch/watch.subscribe, which trackschat.dbfilesystem events and falls back to polling. - Sending regular text and files relies on Messages.app automation.
- More advanced operations use
imsg launchto load theimsghelper into Messages.app. This is what enables read receipts, typing indicators, rich sends, edit, unsend, threaded reply, tapbacks, polls, and group management. - Linux builds can look at a copied
chat.db, but they cannot send, monitor the live Mac database, or drive Messages.app. For OpenClaw iMessage, runimsgon the signed-in Mac or via an SSH wrapper pointing to that Mac.
Before you start
-
Put
imsgon the Mac that runs Messages.app:brew install steipete/tap/imsg brew update && brew upgrade imsg imsg --version imsg chats --limit 3
For typical on-device deployments, OpenClaw setup can present a Homebrew installation or upgrade that the user approves for imsg on the Messages Mac that is signed in. Manual configuration and SSH-wrapper arrangements stay under operator control: execute the Homebrew refresh again in the same local or remote user context that will launch imsg. Should imsg chats terminate with unable to open database file, no output, or authorization denied, provide Full Disk Access to the terminal, editor, Node process, Gateway service, or SSH parent process that starts imsg, and then relaunch that parent process.
-
Prior to adjusting OpenClaw configuration, confirm the read, watch, send, and RPC capabilities:
imsg chats --limit 10 --json | jq -s imsg history --chat-id 42 --limit 10 --attachments --json | jq -s imsg watch --chat-id 42 --reactions --json imsg send --chat-id 42 --text "OpenClaw imsg test" imsg rpc --helpSwap
42for an actual chat id obtained fromimsg chats. Sending demands Automation permission for Messages.app. When OpenClaw operates via SSH, execute these commands through the same SSH wrapper or user context that OpenClaw will employ. If reading succeeds but sending fails with AppleEvents-1743, inspect whether Automation was assigned to/usr/libexec/sshd-keygen-wrapper; consult SSH wrapper sends fail with AppleEvents -1743. -
Turn on the private API bridge. For OpenClaw iMessage this is highly recommended, since replies, tapbacks, effects, polls, attachment replies, and group actions all rely on it:
imsg launch imsg status --jsonimsg launchdemands that SIP be turned off (and on recent macOS, library validation loosened, see Enabling the imsg private API). Basic sending, history, and watching function withoutimsg launch; the complete OpenClaw iMessage action set does not. -
Once
channels.imessageis active and the Gateway is running, confirm the bridge through OpenClaw:openclaw channels status --probeThe iMessage account should show
works; when--jsonis set, the probe payload containsprivateApi.available: true. If it displaysfalse, resolve that first, see Capability detection. Probing requires a Gateway that is reachable (otherwise the CLI falls back to config-only output) and only probes accounts that are configured and enabled. -
Take a snapshot of your config:
cp ~/.openclaw/openclaw.json ~/.openclaw/openclaw.json.bak
Config translation
Most channel-level behavior keys are shared between iMessage and BlueBubbles. The differences lie in transport (REST server versus local CLI) and the group registry key format.
| BlueBubbles | iMessage plugin | Notes |
|---|---|---|
channels.bluebubbles.enabled | channels.imessage.enabled | Equivalent behavior (defaults to true once the block is present). |
channels.bluebubbles.serverUrl | (removed) | No REST server, the plugin launches imsg rpc through stdio instead. |
channels.bluebubbles.password | (removed) | Webhook authentication is unnecessary. |
| (implicit) | channels.imessage.cliPath | Points to imsg (defaults to imsg); under SSH, provide an absolute path to a wrapper on the Gateway host. |
| (implicit) | channels.imessage.dbPath | Optional override for Messages.app chat.db; with SSH, this must be an absolute path on the Messages Mac and is not resolved against the Gateway home directory. |
| (implicit) | channels.imessage.remoteHost | Treats the Messages Mac as either host or user@host; explicit settings take priority, while straightforward transparent SSH wrappers are detected automatically once per process. This allows inbound attachment retrieval and owner-only outbound staging over strict SSH/SCP. Cleanup runs best-effort, a failure only logs a warning and may leave owner-only residue. |
channels.bluebubbles.dmPolicy | channels.imessage.dmPolicy | Identical options (pairing / allowlist / open / disabled); falls back to pairing. |
channels.bluebubbles.allowFrom | channels.imessage.allowFrom | Handle formats match (+15555550123, user@example.com). Approvals from the pairing store do not carry over, see below. |
channels.bluebubbles.groupPolicy | channels.imessage.groupPolicy | Same choices (allowlist / open / disabled); defaults to allowlist. |
channels.bluebubbles.groupAllowFrom | channels.imessage.groupAllowFrom | Identical. When left unset, iMessage uses allowFrom as a fallback; an explicitly empty groupAllowFrom: [] disables all groups under groupPolicy: "allowlist". |
channels.bluebubbles.groups | channels.imessage.groups | Copy the "*" wildcard entry exactly as is; re-key group-specific entries using numeric iMessage chat_id, see "Group registry footgun". requireMention, tools, toolsBySender, systemPrompt transfer unchanged. |
channels.bluebubbles.sendReadReceipts | channels.imessage.sendReadReceipts | Defaults to true. It triggers only when the private API probe is active. |
channels.bluebubbles.includeAttachments | channels.imessage.includeAttachments | Same structure, also off by default. If BlueBubbles delivered attachments, set this explicitly, otherwise inbound photos and media are discarded without a Inbound message log entry until you do. |
channels.bluebubbles.attachmentRoots | channels.imessage.attachmentRoots | Local roots, wildcard rules apply the same way. |
| (N/A) | channels.imessage.remoteAttachmentRoots | Relevant only when remoteHost is configured for SCP fetches. |
channels.bluebubbles.mediaMaxMb | channels.imessage.mediaMaxMb | iMessage defaults to 16 MB (BlueBubbles used 8 MB). Set it explicitly to preserve the lower limit. |
channels.bluebubbles.textChunkLimit | channels.imessage.textChunkLimit | Both default to 4000. |
channels.bluebubbles.coalesceSameSenderDms | (removed) | Leave this key out of the migration. Starting with imsg 0.13.1, Apple's URL-preview split-sends are merged before OpenClaw sees them; openclaw doctor --fix clears an outdated iMessage key. |
channels.bluebubbles.enrichGroupParticipantsFromContacts | (N/A) | Sender display names are already provided by imsg through chat.db. |
channels.bluebubbles.actions.* | channels.imessage.actions.* | The same per-action switches apply (reactions, edit, unsend, reply, sendWithEffect, renameGroup, setGroupIcon, addParticipant, removeParticipant, leaveGroup, sendAttachment) along with the new polls. Every one defaults to enabled; private API actions still depend on the bridge. |
Configurations with multiple accounts (channels.bluebubbles.accounts.*) map one-to-one onto channels.imessage.accounts.*.
Group registry footgun
Two group gates run consecutively within the iMessage plugin. For a group message to reach the agent, it must clear both:
- Sender / chat-target allowlist (
channels.imessage.groupAllowFrom), checks the sender handle or the chat target (entries forchat_id:,chat_guid:,chat_identifier:). WithoutgroupAllowFromset, this gate defaults toallowFrom; an explicitgroupAllowFrom: []turns off that default and rejects every group message undergroupPolicy: "allowlist". - Group registry (
channels.imessage.groups), organized by numeric iMessagechat_id:- When no
groupsblock exists (or it is empty): groups pass this gate provided gate 1 has a non-empty effective sender allowlist; sender filtering controls access, and no drop-all startup warning is emitted. groupspresent with entries but no"*": only thechat_idkeys listed are allowed. Adding any group turns the registry into an allowlist, even whengroupPolicy: "open"is in effect.groups: { "*": { ... } }: all groups pass this gate.
- When no
The migration pitfall: BlueBubbles stored groups entries using chat GUID / chat identifier, but the iMessage registry uses numeric chat_id keys. Copying per-group entries verbatim yields a non-empty registry whose keys never line up, so gate 2 drops every group message. Keep the "*" wildcard as-is; for specific groups, re-key entries with chat_id values taken from imsg chats.
At the default log level, both drop paths show up as warn lines:
- Once per account at startup, when
groupPolicy: "allowlist"is configured and the effective group sender allowlist is empty:imessage: groupPolicy="allowlist" for account "<id>" but no group sender allowlist is configured .... To admit senders, setgroupAllowFrom(orallowFrom); addinggroupsby itself does not pass the sender gate. - Once per
chat_idduring runtime, when the registry rejects a group:imessage: dropping group message from chat_id=<id> ... not in channels.imessage.groups allowlist, which names the exact key to add.
DMs continue to work regardless, since they follow a separate code path; DM success therefore does not confirm group routing.
The minimal sender-scoped setup with groupPolicy: "allowlist":
{
channels: {
imessage: {
groupPolicy: "allowlist",
groupAllowFrom: ["+15555550123", "chat_guid:any;-;..."],
},
},
}
This lets the configured senders through in any group. Add groups entries to limit allowed chats or set per-chat options such as requireMention; copy the BlueBubbles "*" entry verbatim, but re-key specific entries with numeric iMessage chat_id values.
Step-by-step
-
Translate the configuration. Keep the new block disabled while editing; current OpenClaw ignores the old
channels.bluebubblesblock, so it can remain alongside as a reference:{ channels: { imessage: { enabled: false, // flip to true when ready to cut over cliPath: "/opt/homebrew/bin/imsg", dmPolicy: "pairing", allowFrom: ["+15555550123"], // copy from bluebubbles.allowFrom groupPolicy: "allowlist", groupAllowFrom: [], // copy from bluebubbles.groupAllowFrom groups: { "*": { requireMention: true } }, // wildcard copies verbatim; re-key per-chat entries by chat_id // actions default to enabled; set individual toggles false to disable }, }, } -
Switch over and test. Configure
channels.imessage.enabled: true, restart the Gateway, then verify that the channel reports healthy:openclaw gateway restart openclaw channels status --probe --channel imessage # expect "works"; --json shows privateApi.available: trueTesting needs a running Gateway and only checks accounts that are configured and enabled. To validate the Mac itself, use the direct
imsgcommands from Before you start. -
Check DMs. Send a direct message to the agent and confirm that the reply comes back.
-
Check groups separately. DMs and group chats use different code paths, so a working DM does not guarantee that groups are being routed. Send a message in an allowed group chat and confirm the reply arrives. If the group stays silent, with no agent reply and no error, inspect the gateway log for the two
warnlines mentioned in "Group registry footgun" above. A warning at startup indicates that the effective sender allowlist is empty, while a per-chat_idwarning means that a populatedgroupsregistry does not include that chat. -
Check the action surface. From a paired DM, ask the agent to react, edit, unsend, reply, send a photo, and, in a group, rename the group or add or remove a participant. Each action should appear natively in Messages.app. If any action throws
iMessage <action> requires the imsg private API bridge, runimsg launchagain and refresh withopenclaw channels status --probe. -
Remove the BlueBubbles server and the
channels.bluebubblesblock once iMessage DMs, groups, and actions are confirmed working. OpenClaw does not readchannels.bluebubbles.
Action parity at a glance
| Action | legacy BlueBubbles | iMessage plugin |
|---|---|---|
| Send text / SMS fallback | ✅ | ✅ |
| Send media (photo, video, file, voice) | ✅ | ✅ |
Threaded reply (reply_to_guid) | ✅ | ✅ (closes #51892) |
Tapback (react) | ✅ | ✅ |
| Edit / unsend (macOS 13+ recipients) | ✅ | ✅ |
| Send with screen effect | ✅ | ✅ (closes part of #9394) |
| Rich text bold / italic / underline / strikethrough | ✅ | ✅ (typed-run formatting via attributedBody) |
| Native Messages polls (create and vote) | ❌ | ✅ (actions.polls; recipients need iOS/macOS 26+ for native rendering) |
| Rename group / set group icon | ✅ | ✅ |
| Add / remove participant, leave group | ✅ | ✅ |
| Read receipts and typing indicator | ✅ | ✅ (gated on private API probe) |
| Apple URL-preview split-send coalescing | ✅ | ✅ (handled upstream by imsg 0.13.1 and newer; no OpenClaw setting) |
| Inbound recovery after a restart | ✅ | ✅ (automatic: since_rowid replay + GUID dedupe; wider window on local) |
While the gateway is offline, iMessage catches up on anything missed: at startup it replays from the last dispatched rowid through imsg watch.subscribe since_rowid, dedupes by GUID, and a stale-backlog age fence stops the Push-flush "backlog bomb". Because this runs over the imsg RPC connection, remote SSH cliPath setups get the same behavior, though local setups have a broader recovery window since they can read chat.db. See Inbound recovery after a bridge or gateway restart.
Pairing, sessions, and ACP bindings
- Allowlists transfer by handle. The same
+15555550123/user@example.comstrings that BlueBubbles used are recognized bychannels.imessage.allowFrom, so copy them exactly. - Pairing-store approvals do not carry over. Since the pairing store is per channel, nothing migrates the old BlueBubbles store. Senders approved only through pairing need to pair once more under iMessage, or you can add their handles to
allowFrom. - Sessions remain scoped per agent and chat. With default
session.dmScope=main, DMs collapse into the agent main session; with defaultsession.groupScope="per-group", group sessions stay isolated perchat_id(agent:<agentId>:imessage:group:<chat_id>). History from BlueBubbles session keys does not carry into iMessage sessions. - ACP bindings that reference
match.channel: "bluebubbles"must switch to"imessage". Thematch.peer.idshapes (chat_id:,chat_guid:,chat_identifier:, bare handle) are unchanged.
No rollback channel
No supported BlueBubbles runtime exists to fall back to. If iMessage verification fails, set channels.imessage.enabled: false, restart the Gateway, resolve the imsg blocker, and try the cutover again.
The reply cache is stored in SQLite plugin state. When present, openclaw doctor --fix imports and archives the old imessage/reply-cache.jsonl sidecar.
Related
- BlueBubbles removal and the imsg iMessage path, short announcement and operator summary.
- iMessage, full iMessage channel reference, including
imsg launchsetup and capability detection. /channels/bluebubbles, legacy URL that redirects to this migration guide.- Pairing, DM authentication and pairing flow.
- Channel Routing, how the gateway picks a channel for outbound replies.