A Matrix-iMessage puppeting bridge. Send and receive iMessages from any Matrix client.
This is the v2 rewrite using rustpush and bridgev2 — it connects directly to Apple's iMessage servers without SIP bypass, Barcelona, or relay servers.
Features: text, images, video, audio, files, reactions/tapbacks, edits, unsends, typing indicators, read receipts, group chats, SMS forwarding, and contact name resolution.
Platforms: macOS (full features) and Linux (via hardware key extracted from a Mac once).
macOS 13+ required (Ventura or later). Sign into iCloud on the Mac running the bridge (Settings → Apple ID) — this lets Apple recognize the device so login works without 2FA prompts.
git clone https://github.com/lrhodin/imessage.git
cd imessage
make install-beeperThe installer handles everything: Homebrew, dependencies, building, Beeper login, iMessage login, config, and LaunchAgent setup.
git clone https://github.com/lrhodin/imessage.git
cd imessage
make installThe installer auto-installs Homebrew and dependencies if needed, asks three questions (homeserver URL, domain, your Matrix ID), generates config files, handles iMessage login, and starts the bridge as a LaunchAgent. It will pause and tell you exactly what to add to your homeserver.yaml to register the bridge.
The bridge runs on Linux using a hardware key extracted once from a real Mac. No Mac needed at runtime for Intel keys; Apple Silicon Macs require the NAC relay (a small background process on the Mac).
Ubuntu 22.04+ (or equivalent). Only git, make, and sudo are needed — the build installs everything else:
sudo apt install -y git makeIf the Mac has Go installed (macOS 13+):
git clone https://github.com/lrhodin/imessage.git
cd imessage
go run tools/extract-key/main.goIf the Mac is older (macOS 10.13 High Sierra through 12) or doesn't have Go:
Cross-compile on any Mac that has Go, then copy the binary over:
# On your newer Mac (with Go installed):
git clone https://github.com/lrhodin/imessage.git
cd imessage
make extract-key-intel
# Copy to the older Mac:
scp extract-key-intel user@old-mac:~/
# On the older Mac:
cd ~ && ./extract-key-intelThis reads hardware identifiers (serial, MLB, ROM, etc.) and outputs a base64 key. The Mac is not modified and can continue to be used normally.
Apple Silicon Macs lack the encrypted IOKit properties needed by the x86_64 NAC emulator. You must also run the NAC relay — a small HTTP server that generates Apple validation data using the Mac's native AAAbsintheContext framework.
Set up the relay:
go build -o ~/bin/nac-relay ./tools/nac-relay/
~/bin/nac-relay --setupThis installs a LaunchAgent that starts on login and auto-restarts if it crashes. On first run, grant Full Disk Access and Contacts access when prompted — this enables chat history backfill (including images and attachments), contact name resolution, and SMS forwarding from the Mac.
The relay auto-generates a self-signed TLS certificate and a random bearer token on first start, stored in ~/Library/Application Support/nac-relay/. All endpoints (except /health) require the token. The bridge verifies the relay's certificate fingerprint (Go side) and authenticates with the token (both Go and Rust sides).
# Check it's running
tail -f /tmp/nac-relay.logExtract the key with the relay URL:
go run tools/extract-key/main.go -relay https://<your-mac-ip>:5001/validation-dataThe extract-key tool reads the token and certificate fingerprint from relay-info.json (written by the relay) and embeds them in the hardware key automatically. The relay must be running before you run extract-key.
If the bridge runs outside your LAN (e.g., cloud VM), forward port 5001 TCP to your Mac's local IP. Lock the allowed source IPs to your bridge server's IP for defense in depth — the relay is also protected by TLS + bearer token auth.
Intel Macs: The NAC relay is not needed. The bridge runs the x86_64 NAC emulator locally on Linux using hardware data from the extracted key. Chat history starts from when you log in and contacts appear by phone number / email.
git clone https://github.com/lrhodin/imessage.git
cd imessage
make install-beepergit clone https://github.com/lrhodin/imessage.git
cd imessage
make installOn first run expect ~3 minutes for the Rust library to compile.
DM the bridge bot and choose the "Apple ID (External Key)" login flow:
- Paste your hardware key (base64)
- Enter your Apple ID and password
- Enter the 2FA code sent to your trusted devices
The bridge registers with Apple's servers and starts receiving iMessages.
Follow the prompts: Apple ID → password → 2FA (if needed) → handle selection. If the Mac is signed into iCloud with the same Apple ID, login completes without 2FA.
If your Apple ID has multiple identities registered (e.g. a phone number and an email address), you'll be asked which one to use for outgoing messages. This is what recipients see your messages "from". To change it later, set preferred_handle in the config (see Configuration) or log in again.
Tip: In a DM with the bot, commands don't need a prefix. In a regular room, use
!im login,!im help, etc.
To bridge SMS (green bubble) messages, enable forwarding on your iPhone:
Settings → Messages → Text Message Forwarding → toggle on the bridge device.
Incoming iMessages automatically create Matrix rooms. If Full Disk Access is granted (macOS), existing conversations from Messages.app are also synced.
To start a new conversation:
resolve +15551234567
This creates a portal room. Messages you send there are delivered as iMessages.
The bridge connects directly to Apple's iMessage servers using rustpush with local NAC validation (no SIP bypass, no relay server). On macOS with Full Disk Access, it also reads chat.db for message history backfill and contact name resolution.
On Linux, NAC validation uses one of two paths:
- Intel key: open-absinthe emulates Apple's
IMDAppleServicesx86_64 binary via unicorn-engine, hooking IOKit/CoreFoundation calls and feeding them hardware data from the extracted key - Apple Silicon key + relay: The bridge fetches validation data from a NAC relay running on the Mac, which calls Apple's native
AAAbsintheContextframework
flowchart TB
subgraph macos["🖥 macOS"]
HS1[Homeserver] -- appservice --> Bridge1[mautrix-imessage]
Bridge1 -- FFI --> RP1[rustpush]
RP1 -- IOKit/AAAbsinthe --> NAC1[Local NAC]
end
subgraph linux["🐧 Linux"]
HS2[Homeserver] -- appservice --> Bridge2[mautrix-imessage]
Bridge2 -- FFI --> RP2[rustpush]
RP2 -- unicorn-engine --> NAC2[open-absinthe]
RP2 -. "Apple Silicon key (HTTPS + token)" .-> Relay[NAC Relay on Mac]
end
Client1[Matrix client] <--> HS1
Client2[Matrix client] <--> HS2
RP1 <--> Apple[Apple IDS / APNs]
RP2 <--> Apple
style macos fill:#f0f4ff,stroke:#4a6fa5,stroke-width:2px,color:#1a1a2e
style linux fill:#f0fff4,stroke:#4aa56f,stroke-width:2px,color:#1a1a2e
style Apple fill:#1a1a2e,stroke:#1a1a2e,color:#fff
style Client1 fill:#fff,stroke:#999,color:#333
style Client2 fill:#fff,stroke:#999,color:#333
style Relay fill:#ffe0b2,stroke:#e65100,color:#333
Real-time messages flow through Apple's push notification service (APNs) via rustpush and appear in Matrix immediately.
Backfill runs once on first login: the bridge reads chat.db and creates portals for all chats with activity in the last initial_sync_days (default: 1 year, configurable). On macOS this reads the local database directly; on Linux with the NAC relay, it proxies queries over HTTP to the Mac. After initial sync, everything is real-time via rustpush.
# View logs
tail -f data/bridge.stdout.log
# Restart (auto-restarts via KeepAlive)
launchctl kickstart -k gui/$(id -u)/com.lrhodin.mautrix-imessage
# Stop until next login
launchctl bootout gui/$(id -u)/com.lrhodin.mautrix-imessage
# Uninstall
make uninstall# If using systemd (from make install / make install-beeper)
systemctl --user status mautrix-imessage
journalctl --user -u mautrix-imessage -f
systemctl --user restart mautrix-imessage
# If running directly
./mautrix-imessage-v2 -c data/config.yaml# View logs
tail -f /tmp/nac-relay.log
# Restart
launchctl kickstart -k gui/$(id -u)/com.imessage.nac-relay
# Stop
launchctl bootout gui/$(id -u)/com.imessage.nac-relayConfig lives in data/config.yaml (generated during install). To reconfigure from scratch:
rm -rf data
make install # or make install-beeperKey options:
| Field | Default | What it does |
|---|---|---|
network.initial_sync_days |
365 |
How far back to backfill on first login |
network.displayname_template |
First/Last name | How bridged contacts appear in Matrix |
network.preferred_handle |
(from login) | Outgoing identity (tel:+15551234567 or mailto:user@example.com) |
backfill.max_initial_messages |
10000 |
Max messages to backfill per chat |
encryption.allow |
true |
Enable end-to-bridge encryption |
database.type |
sqlite3-fk-wal |
sqlite3-fk-wal or postgres |
make build # Build .app bundle (macOS) or binary (Linux)
make rust # Build Rust library only
make bindings # Regenerate Go FFI bindings (needs uniffi-bindgen-go)
make clean # Remove build artifactscmd/mautrix-imessage/ # Entrypoint
pkg/connector/ # bridgev2 connector
├── connector.go # bridge lifecycle + platform detection
├── client.go # send/receive/reactions/edits/typing
├── login.go # Apple ID + external key login flows
├── chatdb.go # chat.db backfill + contacts (macOS)
├── ids.go # identifier/portal ID conversion
├── capabilities.go # supported features
└── config.go # bridge config schema
pkg/rustpushgo/ # Rust FFI wrapper (uniffi)
rustpush/ # OpenBubbles/rustpush (vendored)
└── open-absinthe/ # NAC emulator (unicorn-engine, cross-platform)
nac-validation/ # Local NAC via AppleAccount.framework (macOS)
tools/
├── extract-key/ # Hardware key extraction (run on Mac)
└── nac-relay/ # NAC validation + contacts + backfill relay (run on Mac)
imessage/ # macOS chat.db + Contacts reader
AGPL-3.0 — see LICENSE.