Skip to content

# iMessage (legacy: imsg)

Recommended: Use BlueBubbles for new iMessage setups.

The imsg channel is a legacy external-CLI integration and may be removed in a future release.

Status: legacy external CLI integration. Gateway spawns imsg rpc (JSON-RPC over stdio).

# Quick setup (beginner)

  1. Ensure Messages is signed in on this Mac.
  2. Install imsg:
    • brew install steipete/tap/imsg
  3. Configure OpenSoul with channels.imessage.cliPath and channels.imessage.dbPath.
  4. Start the gateway and approve any macOS prompts (Automation + Full Disk Access).

Minimal config:

json5
{
  channels: {
    imessage: {
      enabled: true,
      cliPath: "/usr/local/bin/imsg",
      dbPath: "/Users/<you>/Library/Messages/chat.db",
    },
  },
}

# What it is

  • iMessage channel backed by imsg on macOS.
  • Deterministic routing: replies always go back to iMessage.
  • DMs share the agent's main session; groups are isolated (agent:<agentId>:imessage:group:<chat_id>).
  • If a multi-participant thread arrives with is_group=false, you can still isolate it by chat_id using channels.imessage.groups (see “Group-ish threads” below).

# Config writes

By default, iMessage is allowed to write config updates triggered by /config set|unset (requires commands.config: true).

Disable with:

json5
{
  channels: { imessage: { configWrites: false } },
}

# Requirements

  • macOS with Messages signed in.
  • Full Disk Access for OpenSoul + imsg (Messages DB access).
  • Automation permission when sending.
  • channels.imessage.cliPath can point to any command that proxies stdin/stdout (for example, a wrapper script that SSHes to another Mac and runs imsg rpc).

# Troubleshooting macOS Privacy and Security TCC

If sending/receiving fails (for example, imsg rpc exits non-zero, times out, or the gateway appears to hang), a common cause is a macOS permission prompt that was never approved.

macOS grants TCC permissions per app/process context. Approve prompts in the same context that runs imsg (for example, Terminal/iTerm, a LaunchAgent session, or an SSH-launched process).

Checklist:

  • Full Disk Access: allow access for the process running OpenSoul (and any shell/SSH wrapper that executes imsg). This is required to read the Messages database (chat.db).
  • Automation → Messages: allow the process running OpenSoul (and/or your terminal) to control Messages.app for outbound sends.
  • imsg CLI health: verify imsg is installed and supports RPC (imsg rpc --help).

Tip: If OpenSoul is running headless (LaunchAgent/systemd/SSH) the macOS prompt can be easy to miss. Run a one-time interactive command in a GUI terminal to force the prompt, then retry:

bash
imsg chats --limit 1
# or
imsg send <handle> "test"

Related macOS folder permissions (Desktop/Documents/Downloads): /platforms/mac/permissions.

# Setup (fast path)

  1. Ensure Messages is signed in on this Mac.
  2. Configure iMessage and start the gateway.

# Dedicated bot macOS user (for isolated identity)

If you want the bot to send from a separate iMessage identity (and keep your personal Messages clean), use a dedicated Apple ID + a dedicated macOS user.

  1. Create a dedicated Apple ID (example: my-cool-bot@icloud.com).
    • Apple may require a phone number for verification / 2FA.
  2. Create a macOS user (example: opensoulhome) and sign into it.
  3. Open Messages in that macOS user and sign into iMessage using the bot Apple ID.
  4. Enable Remote Login (System Settings → General → Sharing → Remote Login).
  5. Install imsg:
    • brew install steipete/tap/imsg
  6. Set up SSH so ssh <bot-macos-user>@localhost true works without a password.
  7. Point channels.imessage.accounts.bot.cliPath at an SSH wrapper that runs imsg as the bot user.

First-run note: sending/receiving may require GUI approvals (Automation + Full Disk Access) in the bot macOS user. If imsg rpc looks stuck or exits, log into that user (Screen Sharing helps), run a one-time imsg chats --limit 1 / imsg send ..., approve prompts, then retry. See Troubleshooting macOS Privacy and Security TCC.

Example wrapper (chmod +x). Replace <bot-macos-user> with your actual macOS username:

bash
#!/usr/bin/env bash
set -euo pipefail

# Run an interactive SSH once first to accept host keys:
#   ssh <bot-macos-user>@localhost true
exec /usr/bin/ssh -o BatchMode=yes -o ConnectTimeout=5 -T <bot-macos-user>@localhost \
  "/usr/local/bin/imsg" "$@"

Example config:

json5
{
  channels: {
    imessage: {
      enabled: true,
      accounts: {
        bot: {
          name: "Bot",
          enabled: true,
          cliPath: "/path/to/imsg-bot",
          dbPath: "/Users/<bot-macos-user>/Library/Messages/chat.db",
        },
      },
    },
  },
}

For single-account setups, use flat options (channels.imessage.cliPath, channels.imessage.dbPath) instead of the accounts map.

# Remote/SSH variant (optional)

If you want iMessage on another Mac, set channels.imessage.cliPath to a wrapper that runs imsg on the remote macOS host over SSH. OpenSoul only needs stdio.

Example wrapper:

bash
#!/usr/bin/env bash
exec ssh -T gateway-host imsg "$@"

Remote attachments: When cliPath points to a remote host via SSH, attachment paths in the Messages database reference files on the remote machine. OpenSoul can automatically fetch these over SCP by setting channels.imessage.remoteHost:

json5
{
  channels: {
    imessage: {
      cliPath: "~/imsg-ssh", // SSH wrapper to remote Mac
      remoteHost: "user@gateway-host", // for SCP file transfer
      includeAttachments: true,
    },
  },
}

If remoteHost is not set, OpenSoul attempts to auto-detect it by parsing the SSH command in your wrapper script. Explicit configuration is recommended for reliability.

# Remote Mac via Tailscale (example)

If the Gateway runs on a Linux host/VM but iMessage must run on a Mac, Tailscale is the simplest bridge: the Gateway talks to the Mac over the tailnet, runs imsg via SSH, and SCPs attachments back.

Architecture:

┌──────────────────────────────┐          SSH (imsg rpc)          ┌──────────────────────────┐
│ Gateway host (Linux/VM)      │──────────────────────────────────▶│ Mac with Messages + imsg │
│ - opensoul gateway           │          SCP (attachments)        │ - Messages signed in     │
│ - channels.imessage.cliPath  │◀──────────────────────────────────│ - Remote Login enabled   │
└──────────────────────────────┘                                   └──────────────────────────┘

              │ Tailscale tailnet (hostname or 100.x.y.z)

        user@gateway-host

Concrete config example (Tailscale hostname):

json5
{
  channels: {
    imessage: {
      enabled: true,
      cliPath: "~/.opensoul/scripts/imsg-ssh",
      remoteHost: "bot@mac-mini.tailnet-1234.ts.net",
      includeAttachments: true,
      dbPath: "/Users/bot/Library/Messages/chat.db",
    },
  },
}

Example wrapper (~/.opensoul/scripts/imsg-ssh):

bash
#!/usr/bin/env bash
exec ssh -T bot@mac-mini.tailnet-1234.ts.net imsg "$@"

Notes:

  • Ensure the Mac is signed in to Messages, and Remote Login is enabled.
  • Use SSH keys so ssh bot@mac-mini.tailnet-1234.ts.net works without prompts.
  • remoteHost should match the SSH target so SCP can fetch attachments.

Multi-account support: use channels.imessage.accounts with per-account config and optional name. See gateway/configuration for the shared pattern. Don't commit ~/.opensoul/opensoul.json (it often contains tokens).

# Access control (DMs + groups)

DMs:

  • Default: channels.imessage.dmPolicy = "pairing".
  • Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
  • Approve via:
    • opensoul pairing list imessage
    • opensoul pairing approve imessage <CODE>
  • Pairing is the default token exchange for iMessage DMs. Details: Pairing

Groups:

  • channels.imessage.groupPolicy = open | allowlist | disabled.
  • channels.imessage.groupAllowFrom controls who can trigger in groups when allowlist is set.
  • Mention gating uses agents.list[].groupChat.mentionPatterns (or messages.groupChat.mentionPatterns) because iMessage has no native mention metadata.
  • Multi-agent override: set per-agent patterns on agents.list[].groupChat.mentionPatterns.

# How it works (behavior)

  • imsg streams message events; the gateway normalizes them into the shared channel envelope.
  • Replies always route back to the same chat id or handle.

# Group-ish threads (is_group=false)

Some iMessage threads can have multiple participants but still arrive with is_group=false depending on how Messages stores the chat identifier.

If you explicitly configure a chat_id under channels.imessage.groups, OpenSoul treats that thread as a “group” for:

  • session isolation (separate agent:<agentId>:imessage:group:<chat_id> session key)
  • group allowlisting / mention gating behavior

Example:

json5
{
  channels: {
    imessage: {
      groupPolicy: "allowlist",
      groupAllowFrom: ["+15555550123"],
      groups: {
        "42": { requireMention: false },
      },
    },
  },
}

This is useful when you want an isolated personality/model for a specific thread (see Multi-agent routing). For filesystem isolation, see Sandboxing.

# Media + limits

  • Optional attachment ingestion via channels.imessage.includeAttachments.
  • Media cap via channels.imessage.mediaMaxMb.

# Limits

  • Outbound text is chunked to channels.imessage.textChunkLimit (default 4000).
  • Optional newline chunking: set channels.imessage.chunkMode="newline" to split on blank lines (paragraph boundaries) before length chunking.
  • Media uploads are capped by channels.imessage.mediaMaxMb (default 16).

# Addressing / delivery targets

Prefer chat_id for stable routing:

  • chat_id:123 (preferred)
  • chat_guid:...
  • chat_identifier:...
  • direct handles: imessage:+1555 / sms:+1555 / user@example.com

List chats:

imsg chats --limit 20

# Configuration reference (iMessage)

Full configuration: Configuration

Provider options:

  • channels.imessage.enabled: enable/disable channel startup.
  • channels.imessage.cliPath: path to imsg.
  • channels.imessage.dbPath: Messages DB path.
  • channels.imessage.remoteHost: SSH host for SCP attachment transfer when cliPath points to a remote Mac (e.g., user@gateway-host). Auto-detected from SSH wrapper if not set.
  • channels.imessage.service: imessage | sms | auto.
  • channels.imessage.region: SMS region.
  • channels.imessage.dmPolicy: pairing | allowlist | open | disabled (default: pairing).
  • channels.imessage.allowFrom: DM allowlist (handles, emails, E.164 numbers, or chat_id:*). open requires "*". iMessage has no usernames; use handles or chat targets.
  • channels.imessage.groupPolicy: open | allowlist | disabled (default: allowlist).
  • channels.imessage.groupAllowFrom: group sender allowlist.
  • channels.imessage.historyLimit / channels.imessage.accounts.*.historyLimit: max group messages to include as context (0 disables).
  • channels.imessage.dmHistoryLimit: DM history limit in user turns. Per-user overrides: channels.imessage.dms["<handle>"].historyLimit.
  • channels.imessage.groups: per-group defaults + allowlist (use "*" for global defaults).
  • channels.imessage.includeAttachments: ingest attachments into context.
  • channels.imessage.mediaMaxMb: inbound/outbound media cap (MB).
  • channels.imessage.textChunkLimit: outbound chunk size (chars).
  • channels.imessage.chunkMode: length (default) or newline to split on blank lines (paragraph boundaries) before length chunking.

Related global options:

  • agents.list[].groupChat.mentionPatterns (or messages.groupChat.mentionPatterns).
  • messages.responsePrefix.

Released under the MIT License.