Aleph
Interfaces

iMessage

Native macOS iMessage integration for Aleph via Messages.app SQLite polling, plus a cross-platform BlueBubbles transport

The iMessage interface provides native macOS integration by reading messages from the Messages.app SQLite database and sending replies via AppleScript, plus a cross-platform BlueBubbles transport that talks REST + webhook to a running BlueBubbles server. Both transports implement the same Channel trait and are routed through the same inbound pipeline; you can run either (or both) from a single config.

Two Transports

┌──────────────────────────────────────────────────────────────────┐
│                         macOS System                              │
│  ┌──────────────────┐     ┌──────────────────────────────────┐   │
│  │   Messages.app   │     │           Aleph Server            │   │
│  │                  │     │                                   │   │
│  │  chat.db ────────┼─────┼──> SQLite Polling (read-only)     │   │
│  │  (SQLite)        │     │  [IMessageChannel, macOS-only]    │   │
│  │                  │ <───┼─── AppleScript (send message)     │   │
│  └──────────────────┘     └──────────────────────────────────┘   │
└──────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────┐
│                  BlueBubbles Server (any host)                    │
│  ┌──────────────────────────────────────────────────────────┐    │
│  │  REST API + webhook  ◀──▶  [BlueBubblesChannel, any OS]  │   │
│  └──────────────────────────────────────────────────────────┘    │
└──────────────────────────────────────────────────────────────────┘
  • IMessageChannel (local) — SQLite database polling for receiving, AppleScript for sending. Requires macOS, Full Disk Access, and Automation permission. Only registered on macOS (#[cfg(target_os = "macos")] on the module).
  • BlueBubblesChannel (imessage/bluebubbles/) — REST + webhook against a BlueBubbles server. Pure HTTP — compiles and runs on any OS.

Adapter Structure

The iMessage adapter lives under src/gateway/interfaces/imessage/:

ModuleResponsibility
mod.rsIMessageChannel, capability defaults, factory wiring
config.rsIMessageConfig, DmPolicy, GroupPolicy
db.rsMessagesDb — read-only SQLite wrapper around chat.db
sender.rsAppleScript-driven outbound send
target.rsTarget normalization (normalize_phone, parse_target, IMessageTarget, Service)
reaction.rsTapback code ↔ emoji mapping (single source for both transports)
bluebubbles/BlueBubblesChannel + REST/webhook staging

Both transports share the gateway-shared OffsetTracker (currently homed in the Telegram module) for the catch-up watermark so that messages arriving while the daemon is offline are picked up on restart.

Capabilities

IMessageChannel (Local)

FeatureStatus
Text messagesSupported
Rich text (Markdown)Not supported (plain text only)
PhotosSupported (send and receive)
DocumentsSupported (send and receive)
Audio/VideoSupported (send and receive)
Tapback reactionsInbound add-tapbacks are surfaced to the model as context; outbound is unreliable across macOS versions
Reply threadingNot supported
Message editingNot supported
Message deletionNot supported
Typing indicatorNot supported
Read receiptsNot supported
Max message length~20,000 characters
Max attachment size100 MB
Stream protocolNone

BlueBubblesChannel

FeatureStatus
Text messagesSupported
Rich text (Markdown)Not supported
PhotosSupported (send and receive)
DocumentsSupported (send and receive)
Audio/VideoSupported (send and receive)
Tapback reactionsSupported (inbound + outbound via BlueBubbles API)
Reply threadingSupported
Message editingNot supported
Message deletionNot supported
Typing indicatorSupported
Read receiptsSupported
Max message length4,000 characters
Max attachment size100 MB
Stream protocolNone

Prerequisites

Local Transport

macOS Requirement

The local transport is only registered on macOS (#[cfg(target_os = "macos")]). On Linux/Windows builds, only BlueBubblesChannel is available.

Full Disk Access

Aleph needs Full Disk Access permission to read ~/Library/Messages/chat.db:

  1. Open System Settings > Privacy & Security > Full Disk Access
  2. Click the + button
  3. Add the Aleph server binary (or Terminal.app if running from terminal)
  4. Restart the Aleph server

Without this permission, the database open will fail with a "permission denied" error.

Automation Permission

Aleph needs Automation permission to control Messages.app via AppleScript:

  1. The first time Aleph sends a message, macOS will show a permission dialog
  2. Click Allow to grant Automation access
  3. You can manage this in System Settings > Privacy & Security > Automation

BlueBubbles Transport

Run a BlueBubbles Server

You need a running BlueBubbles server (typically on the same Mac that owns the iMessage account). Install and start it per the BlueBubbles docs; note the server URL and the account password.

Open the REST + Webhook Ports

Make sure Aleph can reach the BlueBubbles server (HTTPS) and that the BlueBubbles webhook can reach Aleph (the gateway's HTTP server).

Configuration

Local Transport

[[channels]]
id = "imessage-local"
channel_type = "imessage"
enabled = true

[channels.config]
# Path to the Messages database (default: ~/Library/Messages/chat.db)
db_path = "~/Library/Messages/chat.db"

# Poll interval in milliseconds (default: 1000)
poll_interval_ms = 1000

# DM policy: pairing | allowlist | open | disabled
dm_policy = "pairing"

# Group message policy: open | allowlist | disabled
group_policy = "open"

# Allowlist of phone numbers/emails for DMs
allow_from = ["+15551234567", "user@example.com"]

# Allowlist for group chats (by chat identifier)
group_allow_from = []

# Require @mention in group chats (default: true)
require_mention = true

# Bot's name for mention detection in groups
bot_name = "Aleph"

# Include attachments in inbound messages (default: true)
include_attachments = true

# Maximum attachment size in bytes (0 = unlimited)
max_attachment_size = 0

# Inbound message debounce in milliseconds (default: 500)
inbound_debounce_ms = 500

BlueBubbles Transport

[[channels]]
id = "imessage-bb"
channel_type = "imessage.bluebubbles"
enabled = true

[channels.config]
# BlueBubbles server URL (HTTPS)
server_url = "https://bluebubbles.local:1234"

# BlueBubbles account password
password = "${BLUEBUBBLES_PASSWORD}"

# Optional webhook URL (set on the BlueBubbles server side)
webhook_url = "https://aleph.example.com/webhooks/imessage"

# Poll interval in milliseconds for the fallback poll loop
poll_interval_ms = 1000

# Same DM / group / allowlist policy as the local transport
dm_policy = "pairing"
group_policy = "open"
allow_from = ["+15551234567"]

DM and Group Policy

The dm_policy setting controls how Aleph handles direct messages from unknown senders:

PolicyBehavior
pairing (default)Unknown senders are prompted for a pairing code before Aleph responds
allowlistOnly senders in allow_from are processed; all others are ignored
openAll senders are accepted without restriction
disabledDMs are completely disabled

The pairing policy uses the gateway's single PairingStore. An operator generates a pairing code, the unknown sender sends that code via iMessage, and the operator approves it from the Panel via channel.pairing.approve. The phone number or email is added to the allowlist on approval. There is no per-channel pairing database.

Group Policy

PolicyBehavior
open (default)All group chats are accepted (with optional mention requirement)
allowlistOnly groups in group_allow_from are processed
disabledGroup messages are completely disabled

How Message Polling Works

Local Transport (IMessageChannel)

The polling loop runs in a background Tokio task:

  1. Open databasechat.db is opened read-only (SQLITE_OPEN_READ_ONLY)
  2. Record last ROWID — On startup, the current max ROWID is the baseline
  3. Poll loop — Every poll_interval_ms:
    • Query for messages with ROWID > last_seen and is_from_me = 0
    • For each new message, resolve the sender handle and chat info
    • Extract attachments if cache_has_attachments is set
    • Convert to an InboundMessage and broadcast through the channel state
  4. Update marker — The last seen ROWID is updated after each batch

BlueBubbles Transport (BlueBubblesChannel)

BlueBubblesChannel registers a webhook with the BlueBubbles server for push delivery and falls back to a poll loop (poll_interval_ms) for catch-up. The shared OffsetTracker makes both transports resume after restart from the last persisted GUID/ROWID instead of jumping straight to the newest message.

Apple Timestamps

Messages.app uses "Apple Cocoa Core Data timestamps" — nanoseconds since January 1, 2001 UTC. Aleph converts these to standard Unix timestamps:

unix_timestamp = apple_timestamp / 1,000,000,000 + 978,307,200

Sending Messages

Local Transport — AppleScript

tell application "Messages"
    set targetService to 1st account whose service type = iMessage
    set targetBuddy to participant "+15551234567" of targetService
    send "Hello from Aleph!" to targetBuddy
end tell

Group chats are addressed by chat ID:

tell application "Messages"
    set targetChat to chat id "chat123456"
    send "Hello group!" to targetChat
end tell

BlueBubbles Transport — REST

The BlueBubbles transport sends through the BlueBubbles REST API; no AppleScript is required, so BlueBubbles works on any host and from non-Mac accounts. Reactions, replies, typing indicators, and read receipts go through the same REST surface.

Tapback Reactions

The single source for tapback codes (src/gateway/interfaces/imessage/reaction.rs) maps Apple/BlueBubbles numeric associatedMessageType values to canonical names and emoji:

CodeNameEmoji
2000love❤️
2001like👍
2002dislike👎
2003laugh😂
2004emphasize‼️
2005question

The "remove" variants (3000–3005) and unknown codes are intentionally absent, so any emoji lookup against them yields None and the reaction is dropped at the mapping layer — this is how "only surface add tapbacks" is enforced without a second filter. Inbound add tapbacks are surfaced to the model as context (the user's reaction to a prior assistant message); remove tapbacks are dropped.

Media Handling

Receiving Attachments

Attachments are detected via the cache_has_attachments flag in the message table and resolved through message_attachment_join (local transport) or the BlueBubbles attachment metadata (BlueBubbles transport):

FieldSource
idAttachment GUID
mime_typeFrom attachment.mime_type (local) or BlueBubbles TransferName
filenameFrom attachment.filename or transfer_name
sizeFrom attachment.total_bytes

Sending Attachments

The local transport requires local file paths (sent via AppleScript send POSIX file). The BlueBubbles transport uploads through the BlueBubbles REST API.

Session Routing

ContextSession Key
DM with +15551234567agent:main:dm:+15551234567 or agent:main:imessage:dm:+15551234567
Group chat (chat ID)agent:main:imessage:group:{chat_identifier}

The chat_identifier is typically the phone number for DMs or an internal ID for group chats.

Database Schema (Local Transport)

Aleph reads from three primary tables in chat.db:

message Table

ColumnTypePurpose
ROWIDINTEGERAuto-incrementing message ID
guidTEXTUnique message identifier
textTEXTMessage body
handle_idINTEGERFK to handle table
dateINTEGERApple timestamp (nanoseconds)
is_from_meINTEGER1 if sent by the Mac owner
cache_has_attachmentsINTEGER1 if message has attachments

handle Table

ColumnTypePurpose
ROWIDINTEGERHandle ID
idTEXTPhone number or email
serviceTEXT"iMessage" or "SMS"

chat Table

ColumnTypePurpose
ROWIDINTEGERChat ID
guidTEXTChat GUID
chat_identifierTEXTPhone number or group ID
display_nameTEXTGroup name (if set)
group_idTEXTNon-null for group chats

Limitations

Local Transport

  • macOS only — Cannot run on Linux or Windows
  • Full Disk Access required — Database is protected by macOS TCC
  • No real-time delivery — Polling-based with configurable interval (minimum practical: ~500ms)
  • No rich text — AppleScript sends plain text only
  • No typing indicator — Would require deeper system integration
  • Outbound tapbacks unreliable — Sending tapbacks via AppleScript is not reliably supported across macOS versions
  • No message editing/deletion — AppleScript does not expose these operations

BlueBubbles Transport

  • Requires a BlueBubbles server — Run it on a Mac with an iMessage account; Aleph talks to it over HTTPS
  • No rich text — Plain text only
  • No message editing/deletion — BlueBubbles REST API does not expose these operations

Troubleshooting

ProblemSolution
"Failed to open Messages database"Grant Full Disk Access to the Aleph binary in System Settings (local transport)
"AppleScript execution failed"Grant Automation permission; ensure Messages.app is installed and signed in
Bot does not see new messagesVerify db_path points to the correct database; check that is_from_me = 0 filter is working
Messages.app opens unexpectedlyThis is normal — AppleScript may activate the app when sending
Slow response timeDecrease poll_interval_ms (e.g., to 500); note this increases SQLite read frequency
"Invalid target" errorEnsure the recipient is a valid phone number (with country code) or email address
Group messages ignoredCheck group_policy and group_allow_from; verify require_mention settings
BlueBubbles: webhook not receivedVerify webhook_url is reachable from the BlueBubbles host; check the Aleph gateway HTTP port

On this page