A lightweight REST + WebSocket bridge for iMessage on macOS.
Send and receive messages programmatically. No Electron. No Firebase. Just a thin API.
Quick Start • API • WebSocket • Deployment • How It Works
Existing iMessage automation tools are either bloated (BlueBubbles, 200MB+ Electron app) or painfully slow (AppleScript with 120s+ send times). imsg-bridge wraps the imsg CLI — which talks directly to Apple's private iMessage frameworks — and exposes a clean async API that sends messages in under a second.
| BlueBubbles | AppleScript | imsg-bridge | |
|---|---|---|---|
| Send latency | ~2s | ~120s | <1s |
| Memory | ~200MB | ~50MB | ~30MB |
| Dependencies | Electron, Firebase | osascript | FastAPI, uvicorn |
| Real-time receive | Polling | None | WebSocket stream |
- macOS (Sonoma 15+ recommended)
- Python 3.12+
imsgCLI installed at/opt/homebrew/bin/imsg- iMessage signed in and working in Messages.app
- Full Disk Access granted to your terminal app and Python (for
chat.dbreads)
Use a one-liner like:
claude -p "Install, configure, test, and optionally deploy imsg-bridge from https://github.com/heyfinal/imsg-bridge.git on this macOS machine."
Or:
codex exec "Install, configure, test, and optionally deploy imsg-bridge from https://github.com/heyfinal/imsg-bridge.git on this macOS machine."
gemini -p "Install, configure, test, and optionally deploy imsg-bridge from https://github.com/heyfinal/imsg-bridge.git on this macOS machine."
If you already have the repo locally, generate a tool-specific one-liner and copy it to clipboard:
./setup.sh --ai-prompt codex
# or: ./setup.sh --ai-prompt claude
# or: ./setup.sh --ai-prompt geminiIf you are already inside an interactive AI terminal, generate a plain copy/paste task prompt:
./setup.sh --ai-taskPaste this inside Claude/Codex/Gemini interactive mode:
Install, configure, test, and optionally deploy imsg-bridge from https://github.com/heyfinal/imsg-bridge.git on this macOS machine.
curl -fsSL https://raw.githubusercontent.com/heyfinal/imsg-bridge/main/install.sh | bashThis clones the repo to ~/.imsg-bridge, creates a virtual environment, and installs dependencies using only Python's built-in venv and pip. No Homebrew, no uv, no global installs.
git clone https://github.com/heyfinal/imsg-bridge.git
cd imsg-bridge
uv syncgit clone https://github.com/heyfinal/imsg-bridge.git
cd imsg-bridge
python3 -m venv .venv
.venv/bin/pip install -e .cd imsg-bridge # or ~/.imsg-bridge if you used the one-liner
# Generate auth token and install as a background service
./setup.sh
# Or run manually
imsg-bridge
# → Listening on configured host:5100All endpoints require a bearer token in the Authorization header:
Authorization: Bearer <your-token>
The token is generated by setup.sh and stored in your macOS Keychain.
curl -X POST http://127.0.0.1:5100/send \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"to": "+15551234567", "text": "Hello from the bridge"}'curl http://127.0.0.1:5100/chats \
-H "Authorization: Bearer $TOKEN"curl "http://127.0.0.1:5100/history/23?limit=50" \
-H "Authorization: Bearer $TOKEN"curl http://127.0.0.1:5100/health \
-H "Authorization: Bearer $TOKEN"Returns {"status": "ok", "imsg_version": "<detected-version>"} when everything is working.
curl http://127.0.0.1:5100/pingReturns {"status": "ok"} — useful for monitoring, load balancers, and launchd health checks.
curl "http://127.0.0.1:5100/contact-name?identifier=%2B15551234567" \
-H "Authorization: Bearer $TOKEN"Returns {"identifier": "+15551234567", "name": "John Doe"} from the macOS Contacts database.
curl "http://127.0.0.1:5100/avatar?identifier=%2B15551234567" \
-H "Authorization: Bearer $TOKEN" --output avatar.jpgReturns the contact's photo as image/jpeg, image/png, or image/tiff.
Connect to /ws for a real-time stream of incoming messages:
websocat "ws://127.0.0.1:5100/ws?token=$TOKEN"Each message arrives as a JSON object:
{
"id": 43327,
"guid": "A1B2C3D4-...",
"chat_id": 23,
"text": "Hey, what's up?",
"sender": "+15551234567",
"is_from_me": false,
"created_at": "2026-03-03T06:00:00.000Z",
"attachments": [],
"reactions": []
}Authentication via Authorization: Bearer <token> header or ?token=<token> query parameter.
The setup.sh script handles everything:
./setup.shThis will:
- Generate a secure auth token and store it in macOS Keychain
- Let you choose bind mode (
0.0.0.0for LAN clients or127.0.0.1for local-only) - Install a LaunchAgent that starts on login and auto-restarts on crash
- Optionally deploy Linux client via:
- LAN scan for SSH hosts (or manual IP entry) + username/password prompt
- USB/external drive scan (or manual mount path)
- If Messages DB access is blocked, automatically open Full Disk Access settings and walk you through enabling it
- Verify the service is running
Logs are written to ~/Library/Logs/imessage-bridge.log and ~/Library/Logs/imessage-bridge.err.
By default, the LaunchAgent binds to 127.0.0.1 (loopback only). If you want to access the bridge from other machines on your LAN (e.g. imsg-gtk on Linux), bind to 0.0.0.0 instead:
IMSG_BRIDGE_BIND_HOST=0.0.0.0 ./setup.shThis exposes the bridge to your local network. Keep the bearer token private and consider using a firewall, Tailscale, or SSH tunneling.
imsg-bridge --host 127.0.0.1 --port 5100Or with uvicorn directly:
uvicorn imsg_bridge.bridge:app --host 127.0.0.1 --port 5100Your app / bot / agent
↕ HTTP + WebSocket
imsg-bridge (FastAPI, port 5100)
↕ async subprocess
imsg CLI (/opt/homebrew/bin/imsg)
↕ private frameworks
~/Library/Messages/chat.db
- REST endpoints spawn
imsgsubprocesses for each request (send, chats, history) - WebSocket runs a persistent
imsg watchsubprocess that streams new messages as JSONL - State persistence tracks the last processed message ROWID in
~/.imessage-bridge/state.jsonso no messages are lost across restarts - Auth uses a bearer token stored in macOS Keychain — no config files with secrets
A native GTK4/libadwaita desktop app for reading and sending iMessages from Linux over your LAN.
- Real-time message streaming via WebSocket
- Contact name resolution and avatar display from macOS Contacts
- Inline image attachments
- Desktop notifications for incoming messages (when window is not focused)
- Reconnection banner with automatic retry
- Send failure indicator with visual feedback
- Search/filter conversations
- Right-click context menu (open, copy contact, clear)
- Dark mode support via libadwaita
- Linux with GTK 4.10+, libadwaita 1.3+
- Python 3.12+ with PyGObject
- Network access to the Mac running imsg-bridge
The easiest way is via setup.sh on the Mac, which offers SSH push or USB copy:
./setup.sh --deploy-ssh user@linux-machine
./setup.sh --deploy-usb /Volumes/USBOr install manually on Linux:
cd imsg_gtk
pip install -e .
imsg-gtkConfiguration is stored in ~/.config/imsg-gtk/config.json:
{
"host": "192.168.1.100",
"port": 5100,
"token": "your-bearer-token"
}- Setup supports both bind modes:
127.0.0.1local-only (recommended default)0.0.0.0LAN-accessible (required for direct Linux client access)
- Bearer token required on all endpoints (REST and WebSocket) except
/ping - Token stored in macOS Keychain, never on disk
- Uses constant-time token comparison for auth checks
- Rate limiting on
/send(20 requests/minute sliding window) imsgbinary path configurable viaIMSG_PATHenvironment variable- For remote access, use Tailscale or an SSH tunnel
# Install dev tooling
python3 -m pip install -e ".[dev]"
# Optional: run checks automatically on commit
pre-commit install
# Lint + tests
ruff check imsg_bridge imsg_gtk tests
pytest -qCI runs these checks on every push and pull request.
