iMessage Access Control, DM and Group Routing Rules

Covers iMessage DM and group policy, mention gating, per-group prompts, ACP bindings, and config writes. Useful for operators configuring who can reach the bot and which chats can change settings.

Read this when

  • Deciding who can message the bot
  • Debugging ignored DMs or ignored group messages
  • Binding an iMessage chat to an ACP session

Which senders get in, how incoming messages map to sessions, and which chats are allowed to write config.

Access control and routing

DM policy

Direct messages are governed by channels.imessage.dmPolicy:

  • pairing (default)
  • allowlist (needs at least one allowFrom entry)
  • open (needs allowFrom to contain "*")
  • disabled

Allowlist field: channels.imessage.allowFrom.

Every allowlist entry has to name senders: handles or static sender access groups (accessGroup:<name>). For chat targets like chat_id:*, chat_guid:*, or chat_identifier:*, use channels.imessage.groupAllowFrom; for numeric chat_id registry keys, use channels.imessage.groups.

Group policy + mentions

Group handling is governed by channels.imessage.groupPolicy:

  • allowlist (default)
  • open
  • disabled

Group sender allowlist: channels.imessage.groupAllowFrom.

Entries in groupAllowFrom may also point at static sender access groups (accessGroup:<name>).

Runtime fallback: when groupAllowFrom is unset, iMessage group sender checks rely on allowFrom; set groupAllowFrom if DM and group admission ought to differ. An explicitly empty groupAllowFrom: [] triggers no fallback: under allowlist it blocks all group senders. Runtime note: when channels.imessage is entirely absent, runtime logs a warning and falls back to groupPolicy="allowlist" (even if channels.defaults.groupPolicy is set).

Warning

Under groupPolicy: "allowlist", group routing passes through two gates in sequence:

  1. Sender allowlist (channels.imessage.groupAllowFrom), a handle, accessGroup:<name>, chat_guid, chat_identifier, or chat_id. If the effective list is empty (no groupAllowFrom and no allowFrom fallback), every group sender is blocked.
  2. Group registry (channels.imessage.groups), enforced once the map holds entries: the chat has to match either an explicit per-chat_id entry or a groups: { "*": { ... } } wildcard. While groups is empty or missing, the sender allowlist alone decides admission.

When no effective group sender allowlist is configured, every group message is dropped before the registry gate is reached. At the default log level each gate emits its own warn-level signal, and each one points to a different fix:

  • once per account at startup, when the effective group sender allowlist is empty: imessage: groupPolicy="allowlist" for account "<id>" but no group sender allowlist is configured ..., fix it by setting channels.imessage.groupAllowFrom (or allowFrom); adding groups entries on their own leaves gate 1 blocking every sender.
  • once per chat_id at runtime, when a sender has passed gate 1 but the chat is absent from a populated groups registry: imessage: dropping group message from chat_id=<id> ..., fix it by adding that chat_id (or "*") under channels.imessage.groups.

DMs take a different code path, so they are unaffected.

Recommended config for group flow under groupPolicy: "allowlist":

{
  channels: {
    imessage: {
      groupPolicy: "allowlist",
      groupAllowFrom: ["+15555550123"],
      groups: { "*": { "requireMention": true } },
    },
  },
}

On its own, groupAllowFrom admits those senders in any group; add the groups block to scope which chats are allowed (and to set per-chat options such as requireMention).

Group mention gating:

  • iMessage provides no native mention metadata
  • Detection of mentions relies on agents.entries.*.groupChat.mentionPatterns, then on messages.groupChat.mentionPatterns; if neither is configured, the patterns come from the routed agent's identity.name and identity.emoji
  • By default a group requires a mention, even when no patterns were set explicitly; a message from an allowlisted sender can therefore be dropped unless it carries the agent's name or emoji
  • An explicit mentionPatterns: [] set at the selected agent or at global level suppresses patterns derived from identity; when no usable patterns are left, iMessage cannot enforce mention gating
  • Control commands sent by authorized senders are exempt from mention gating

To have every message from allowed senders in a given group processed, set that chat's requireMention to false:

{
  channels: {
    imessage: {
      groupPolicy: "allowlist",
      groupAllowFrom: ["+15555550123", "+15555550124"],
      groups: {
        "*": {},
        "123": { requireMention: false },
      },
    },
  },
}

Swap 123 for the numeric chat ID taken from imsg chats --limit 20 --json. Modify the map that already provides that account's group policy: channels.imessage.groups, or channels.imessage.accounts.<account-id>.groups where it overrides the root map. The same holds for accounts.default.groups; an account entry on its own does not mean that account needs its own groups map. An account map that is empty inherits the root map only when one account or fewer is configured.

To permit unmentioned follow-ups only inside native reply threads that OpenClaw started, add requireMentionInBotThreads: false next to requireMention: true in that group's entry. A setting on the exact group takes precedence over groups["*"].requireMentionInBotThreads. Assign true to demand mentions in those threads, or leave it out to keep the usual mention behavior. Sender and group restrictions continue to apply.

An explicit requireMentionInBotThreads: true is still enforced even when mention patterns are turned off. Set up usable mention patterns so the bot can be addressed in those threads; otherwise they remain silent apart from authorized control commands.

Thread ownership is determined by the native thread root GUID together with OpenClaw's sent-message cache, which is scoped per account and per conversation. That cache holds as many as 2,000 messages for six hours and persists across restarts inside that window. Roots that are unknown or have been evicted still require a mention as usual. If you reply to a bot message within a thread owned by someone else, that thread does not become bot-owned.

Keep the existing wildcard and all per-group settings intact, and change only the target chat's requireMention. Because account maps replace the entire inherited map, copy the full inherited map first, wildcard and per-group policies included, if you deliberately create an account-specific override. Where no map applied before, "*": {} keeps admission to the other groups while leaving their default mention requirement in place. A restricted map must stay restricted. Sender access is still governed by groupAllowFrom.

When a message is skipped for lacking a mention, a warning is logged at the default level, including the chat ID and the requireMention: false fix. A bounded in-memory cache suppresses repeated warnings for the same chat; restarting the channel or evicting a cache entry lets the warning appear again.

Per-group systemPrompt:

Every entry beneath channels.imessage.groups.* takes an optional systemPrompt string, which is injected into the agent's system prompt on each turn that handles a message in that group. Resolution works the same way as channels.whatsapp.groups:

  1. Group-specific system prompt (groups["<chat_id>"].systemPrompt): applied when the map contains the specific group entry and its systemPrompt key is defined. Should systemPrompt be an empty string (""), the wildcard is suppressed and that group receives no system prompt.
  2. Group wildcard system prompt (groups["*"].systemPrompt): applied when the specific group entry is missing from the map altogether, or when it is present but has no systemPrompt key defined.
{
  channels: {
    imessage: {
      groupPolicy: "allowlist",
      groupAllowFrom: ["+15555550123"],
      groups: {
        "*": { systemPrompt: "Use British spelling." },
        "8421": {
          requireMention: true,
          systemPrompt: "This is the on-call rotation chat. Keep replies under 3 sentences.",
        },
        "9907": {
          // explicit suppression: the wildcard "Use British spelling." does not apply here
          systemPrompt: "",
        },
      },
    },
  },
}

Per-group prompts affect group messages only; direct messages are not touched.

Sessions and reply routing

  • Direct routing serves DMs; group routing serves groups.
  • Under the default session.dmScope=main, iMessage DMs are collapsed into the agent main session.
  • Group sessions stay isolated (agent:<agentId>:imessage:group:<chat_id>).
  • Replies are routed back to iMessage through the originating channel/target metadata.
  • chat_id, chat_guid, and chat_identifier act as delivery selectors rather than group markers. Outbound session routing looks up the chat in the account's Messages chat list: one-to-one chats map to the direct session of the normalized handle, while groups map to the numeric chat ID. If a selector cannot be resolved, no session is guessed.
  • Earlier versions could create extra group sessions holding delivery mirrors for one-to-one chats. This fix stops new misclassified mirrors from appearing; it keeps existing session history as is instead of merging or deleting those rows automatically.

Group-ish thread behavior:

Certain multi-participant iMessage threads may arrive carrying is_group=false. When that chat_id is configured explicitly under channels.imessage.groups, OpenClaw handles it as group traffic (group gating plus group session isolation).

ACP conversation bindings

ACP sessions can be bound to iMessage chats.

Quick operator steps:

  • Inside the DM or an allowed group chat, run /acp spawn codex --bind here.
  • Messages sent later in that same iMessage conversation are routed to the spawned ACP session.
  • /new and /reset reset that same bound ACP session in place.
  • /acp close closes the ACP session and drops the binding.

Persistent bindings configured ahead of time use top-level bindings[] entries containing type: "acp" and match.channel: "imessage".

match.peer.id accepts:

  • a normalized DM handle, for example +15555550123 or user@example.com
  • chat_id:<id> (recommended for stable group bindings)
  • chat_guid:<guid>
  • chat_identifier:<identifier>

Example:

{
  agents: {
    entries: {
      codex: {
        runtime: {
          type: "acp",
          acp: { agent: "codex", backend: "acpx", mode: "persistent" },
        },
      },
    },
  },
  bindings: [
    {
      type: "acp",
      agentId: "codex",
      match: {
        channel: "imessage",
        accountId: "default",
        peer: { kind: "group", id: "chat_id:123" },
      },
      acp: { label: "codex-group" },
    },
  ],
}

Shared ACP binding behavior is covered in ACP Agents.

Config writes

By default, iMessage permits config writes started by the channel (in the case of /config set|unset, when commands.config: true).

To turn this off:

{
  channels: {
    imessage: {
      configWrites: false,
    },
  },
}
1,552 words · updated Oct 26, 2026