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:

  1. Use openclaw plugins install @openclaw/imessage to install the official plugin, then restart the Gateway.
  2. Confirm imsg directly on the Mac where Messages.app runs (imsg chats, imsg history, imsg send, imsg rpc --help).
  3. Move behavior-related keys from channels.bluebubbles over to channels.imessage: dmPolicy, allowFrom, groupPolicy, groupAllowFrom, groups, includeAttachments, attachmentRoots, mediaMaxMb, textChunkLimit, and actions.
  4. Remove transport keys that are no longer valid: serverUrl, password, webhook URLs, and BlueBubbles server configuration.
  5. When the Gateway lives on a different machine than the Messages Mac, point channels.imessage.cliPath at the absolute Gateway-local path for an SSH wrapper, and leave dbPath as an absolute path on that Mac. For wrappers with more complexity, set remoteHost to the Messages Mac; the simple transparent wrapper shape is auto-detected by OpenClaw for compatibility.
  6. Turn on channels.imessage, restart the Gateway, and then execute openclaw channels status --probe --channel imessage.
  7. Try out one DM, one approved group, attachments if they are enabled, and every private API action the agent is expected to use.
  8. Once the iMessage path checks out, remove the BlueBubbles server and the old channels.bluebubbles config.

Note

Remote imsg v0.13.4 comes with two specific RPC constraints: poll votes have to use pollOptionId rather than an index or option text, and attachment replies cannot point at a nonzero message part index. Local imsg behavior 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.db using a read-only SQLite connection.
  • Incoming messages in real time come from imsg watch / watch.subscribe, which tracks chat.db filesystem events and falls back to polling.
  • Sending regular text and files relies on Messages.app automation.
  • More advanced operations use imsg launch to load the imsg helper 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, run imsg on the signed-in Mac or via an SSH wrapper pointing to that Mac.

Before you start

  1. Put imsg on 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.

  1. 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 --help
    

    Swap 42 for an actual chat id obtained from imsg 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.

  2. 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 --json
    

    imsg launch demands that SIP be turned off (and on recent macOS, library validation loosened, see Enabling the imsg private API). Basic sending, history, and watching function without imsg launch; the complete OpenClaw iMessage action set does not.

  3. Once channels.imessage is active and the Gateway is running, confirm the bridge through OpenClaw:

    openclaw channels status --probe
    

    The iMessage account should show works; when --json is set, the probe payload contains privateApi.available: true. If it displays false, 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.

  4. 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.

BlueBubblesiMessage pluginNotes
channels.bluebubbles.enabledchannels.imessage.enabledEquivalent 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.cliPathPoints to imsg (defaults to imsg); under SSH, provide an absolute path to a wrapper on the Gateway host.
(implicit)channels.imessage.dbPathOptional 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.remoteHostTreats 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.dmPolicychannels.imessage.dmPolicyIdentical options (pairing / allowlist / open / disabled); falls back to pairing.
channels.bluebubbles.allowFromchannels.imessage.allowFromHandle formats match (+15555550123, user@example.com). Approvals from the pairing store do not carry over, see below.
channels.bluebubbles.groupPolicychannels.imessage.groupPolicySame choices (allowlist / open / disabled); defaults to allowlist.
channels.bluebubbles.groupAllowFromchannels.imessage.groupAllowFromIdentical. When left unset, iMessage uses allowFrom as a fallback; an explicitly empty groupAllowFrom: [] disables all groups under groupPolicy: "allowlist".
channels.bluebubbles.groupschannels.imessage.groupsCopy 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.sendReadReceiptschannels.imessage.sendReadReceiptsDefaults to true. It triggers only when the private API probe is active.
channels.bluebubbles.includeAttachmentschannels.imessage.includeAttachmentsSame 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.attachmentRootschannels.imessage.attachmentRootsLocal roots, wildcard rules apply the same way.
(N/A)channels.imessage.remoteAttachmentRootsRelevant only when remoteHost is configured for SCP fetches.
channels.bluebubbles.mediaMaxMbchannels.imessage.mediaMaxMbiMessage defaults to 16 MB (BlueBubbles used 8 MB). Set it explicitly to preserve the lower limit.
channels.bluebubbles.textChunkLimitchannels.imessage.textChunkLimitBoth 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:

  1. Sender / chat-target allowlist (channels.imessage.groupAllowFrom), checks the sender handle or the chat target (entries for chat_id:, chat_guid:, chat_identifier:). Without groupAllowFrom set, this gate defaults to allowFrom; an explicit groupAllowFrom: [] turns off that default and rejects every group message under groupPolicy: "allowlist".
  2. Group registry (channels.imessage.groups), organized by numeric iMessage chat_id:
    • When no groups block 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.
    • groups present with entries but no "*": only the chat_id keys listed are allowed. Adding any group turns the registry into an allowlist, even when groupPolicy: "open" is in effect.
    • groups: { "*": { ... } }: all groups pass this gate.

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, set groupAllowFrom (or allowFrom); adding groups by itself does not pass the sender gate.
  • Once per chat_id during 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

  1. Translate the configuration. Keep the new block disabled while editing; current OpenClaw ignores the old channels.bluebubbles block, 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
        },
      },
    }
    
  2. 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: true
    

    Testing needs a running Gateway and only checks accounts that are configured and enabled. To validate the Mac itself, use the direct imsg commands from Before you start.

  3. Check DMs. Send a direct message to the agent and confirm that the reply comes back.

  4. 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 warn lines mentioned in "Group registry footgun" above. A warning at startup indicates that the effective sender allowlist is empty, while a per-chat_id warning means that a populated groups registry does not include that chat.

  5. 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, run imsg launch again and refresh with openclaw channels status --probe.

  6. Remove the BlueBubbles server and the channels.bluebubbles block once iMessage DMs, groups, and actions are confirmed working. OpenClaw does not read channels.bluebubbles.

Action parity at a glance

Actionlegacy BlueBubblesiMessage 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.com strings that BlueBubbles used are recognized by channels.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 default session.groupScope="per-group", group sessions stay isolated per chat_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". The match.peer.id shapes (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.

2,703 words · updated Aug 22, 2026