macOS chat-relay bridge: iMessage <-> Telegram, web UI, and pluggable integrations
Project description
chatwire
Access your iMessages from anywhere — your phone, your laptop, your browser, wherever you are. A lightweight bridge that runs on your Mac and gives you a web UI, a Telegram relay, push notifications, and a plugin system for extending it however you want.
This is the product Apple should have come out with years ago. iMessage is the best messaging platform out there, but Apple locks it to their devices and offers no remote access, no API, no way to check your messages when you're away from your Mac. chatwire fixes that.
Status: beta — actively looking for testers. The author runs this daily and it's stable, but you'll be among the first to install it on a fresh machine. If you hit a wall, open an issue. See
docs/OPEN_SOURCE_PLAN.mdfor the roadmap.
What it does
- Inbound. A lightweight Python service watches
~/Library/Messages/chat.db, resolves senders against Contacts.app, and forwards messages (text, photos, videos, attachments) to your configured integrations. - Outbound. Reply from anywhere — your phone, a browser tab, a Telegram chat. The service drives Messages.app via AppleScript to send back as you.
- Group chats. First-class support. Replies route by chat GUID so group conversations stay intact across surfaces.
- Automation rules. A condition/action rules engine fires on every
inbound message (or on outbound sends, or on a cron schedule). Actions include
auto-reply, webhook, suppress, and log. A human-readable DSL lets you write
from:Alice AND contains:urgentinstead of clicking through dropdowns. Rules live in~/.chatwire/rules.jsonand are fully editable from the Settings UI. - Anti-spam protection. A six-step message fuse automatically pauses outbound sends if an abnormal send rate is detected — stepping through counting, timed cooldown, and a permanent lockout requiring an unlock code. The UI shows a live-countdown banner and a persistent warning bar so you always know the fuse state.
- Hiatus mode. Pause outbound messaging on a schedule or on demand. A sidebar indicator and End button let you manage hiatus without navigating to Settings. Supports a configurable reminder contacts list and pinnable toggles in the sidebar footer.
- Plugins. Extend chatwire with notification services (ntfy), Telegram
relay, MQTT broker, Home Assistant, XMPP, and more. Plugins get
auto-generated settings sections in the web UI. Build your own
or install community plugins with
pipx inject.
Requirements
iMessage is Mac-only, so the bridge needs a Mac with your Apple ID logged
into Messages.app. macOS requires two permission grants — Full Disk Access
(to read chat.db) and Automation→Messages (to send). The setup wizard
walks you through both, but you will click through a couple of system
prompts on first run.
Tested on macOS 12 Monterey. macOS 13-15 should work. Run chatwire doctor
to verify your system is ready.
Install
Recommended path: pipx, against python.org's Python.
# Install python.org Python first if you don't have it:
# https://www.python.org/downloads/macos/
# (Homebrew Python works but TCC treats it as a different identity —
# you'd have to grant Full Disk Access + Automation to that binary
# specifically. python.org is the well-trodden path.)
# Install pipx if you don't have it (once per machine):
python3 -m pip install --user pipx
python3 -m pipx ensurepath
# Install chatwire from PyPI:
pipx install --python /Library/Frameworks/Python.framework/Versions/Current/bin/python3 \
chatwire
# Wire it up:
chatwire install-agents
chatwire setup
The setup wizard walks you through the macOS permission grants (Full Disk
Access + Automation→Messages), identity, contact whitelist, and optional
web UI password. It writes ~/.chatwire/config.json.
Alternate install methods
Homebrew tap. Convenient if you already use brew.
brew install allenbina/tap/chatwire
chatwire install-agents
chatwire setup
Tap source: https://github.com/allenbina/homebrew-tap.
curl-pipe-bash. No PyPI access, no Homebrew, just a shell.
curl -fsSL https://raw.githubusercontent.com/allenbina/chatwire/main/scripts/install.sh | bash
Pin to a specific version with CHATWIRE_REF=v1.1.0. The script refuses
Xcode CLT's Python stub and warns on Homebrew Python (TCC identity
protection). Same post-install steps (chatwire install-agents etc.).
Developer / git-clone path. For hacking on the bridge itself:
git clone https://github.com/allenbina/chatwire.git ~/projects/chatwire
cd ~/projects/chatwire
python3 -m venv .venv
.venv/bin/pip install -e .
# Render and load the launchd agents:
.venv/bin/chatwire install-agents
# Sanity check:
.venv/bin/chatwire doctor
The setup wizard writes ~/.chatwire/config.json (chmod 600) for you.
For manual edits or headless installs, see
docs/REFERENCE_INSTALL.md.
macOS permissions
Both the FDA grant and the Automation→Messages grant need to be given to the
python.org Python binary (not Homebrew's), because the python.org
installer ships two Mach-O binaries with different code-signing identities
that TCC tracks separately. See
docs/REFERENCE_INSTALL.md section 5 for the
full walkthrough — that section was the reason the bridge worked at all on
the first install, and it's the same on every Mac.
scripts/check-permissions.sh (or chatwire doctor) will tell you
which prompts you still need to click.
React UI
chatwire ships a full-featured React SPA at /app/ alongside the legacy
server-rendered UI. The React UI is the default — navigating to / redirects
to /app/ automatically.
Features
- Real-time chat — SSE stream + react-query polling, optimistic sends, group chat with sender names.
- Rich message display — tapback reaction badges (❤️ 👍 👎 😂 ‼️ ❓ 🎉), read receipts ("Read at H:MM AM/PM"), iOS-style quoted-reply ghost bubbles (click to scroll to original), "edited" badge (macOS 13+), location share cards, sticker/Memoji display, SMS text-pattern reaction fallback, and tapback corner overlays.
- Automation rules — rule builder dialog with condition and action
dropdowns, DSL toggle for raw grammar (
from:Alice AND contains:urgent),on_sendand cron schedule triggers, rule reordering with ↑/↓ buttons, and enable/disable toggles per rule. - Anti-spam / message fuse — CooldownBanner with live countdown at fuse steps 1-3, LockoutTopBanner (persistent warning bar across all pages) at steps 4+, and LockoutFooterNote in the compose area replacing the textarea when locked out.
- Hiatus mode — sidebar indicator and "End" button, live countdown in the sidebar and Settings, reminder contacts picker, and pinnable toggles in the sidebar footer.
- Theme system — per-theme CSS variable editor, custom CSS overrides per theme pack (sanitized), theme ZIP export/import, sliding hover highlight, unified color-scheme dropdown with sun/moon toggle, and Rose Pine theme plugin (Moon / Dawn / Main schemes).
- Full settings — all settings sections: themes, notifications, whitelist, automation rules, anti-spam, plugins, export, structured log viewer, and more.
- Data exposure warning — a dismissable modal on first launch explains which data chatwire can access; not shown again once acknowledged.
- PWA — installable on desktop and mobile. Workbox service worker handles offline access (cached conversations and messages), background sync (unsent messages are queued and retried on reconnect), and auto-update notifications.
- Plugin slot system — third-party plugins can register React components
into named slots (
sidebar.panel,message.toolbar,compose.extension,settings.page). The built-in StatsWidget uses thesidebar.panelslot. - Performance — message list is virtualised with
@tanstack/react-virtual(renders ≤ 30 DOM nodes regardless of conversation length). HEIC attachments are converted to JPEG once and cached (~/.chatwire/img_cache/) with a startup warmer that pre-converts recent photos before the first UI request. Settings and Popout pages are lazy-loaded (separate chunks, not in the main bundle).
PWA install
Visit /app/ in Chrome or Edge and click the install icon in the address bar
(or use the browser's "Add to Home Screen" on mobile). Once installed,
chatwire appears as a standalone window with no browser chrome.
Legacy UI
The original htmx/Jinja2 UI is still available at /?legacy=1 for one more
release cycle, then will be removed. If you relied on the legacy UI for any
integrations or automations, migrate to /app/ before the next major version.
Plugin slot system
Build a frontend plugin by registering a React component via the window.chatwire API:
<!-- load your plugin script after the chatwire app boots -->
<script>
window.addEventListener('chatwire:ready', () => {
window.chatwire.registerSlot('sidebar.panel', MyWidget, { key: 'my-widget' })
})
</script>
See docs/PLUGIN_DEVELOPMENT.md for the full
slot reference, BaseIntegration hook table, and an end-to-end example.
Mobile App
chatwire has a native iOS + Android app built with React Native and Expo. It connects to your existing chatwire server over the local network or Tailscale — no cloud relay, no account required.
Download
- Android APK — grab the latest
.apkfrom GitHub Releases and side-load it (enable "Install from unknown sources"). - iOS — TestFlight link coming soon (requires Apple Developer account). Build from source in the meantime (see below).
Connect the app to your server
- Open the app. On first launch you'll see the Server Setup screen.
- Enter your chatwire server URL:
http://192.168.1.x:8723(or your Tailscale hostname). Usehttp://—https://requires a reverse-proxy with a valid cert. - Enter your web UI password if you've set one in Settings.
- Tap Connect. The app runs
/healthzand saves the URL on success.
The server URL is stored in the app's AsyncStorage. To change it:
Settings tab → Disconnect → re-enter on next launch.
Features
- Conversation list — FlatList with pull-to-refresh, unread badge, live updates via SSE.
- Message list — inverted scroll (newest at bottom), load-older pagination, sender names in group chats.
- Compose — multiline text input, haptic feedback on send, camera/gallery picker (stub — full upload in a future release).
- Image viewer — full-screen pinch-to-zoom via
expo-image+ react-native-gesture-handler. - Video player — inline thumbnail → tap to play via
expo-video. - Push notifications — register your Expo push token with the server; the server fires a push when a new message arrives (requires a server upgrade to 1.7.0+ which is not yet released).
- Dark / light theme — Dracula palette by default; theme follows the server's active theme setting.
Build from source
git clone https://github.com/allenbina/chatwire.git
cd chatwire/packages/mobile
# Install dependencies (Node 22 required)
npm install
# Start Expo dev server
npx expo start
# iOS simulator (macOS only)
npx expo start --ios
# Android emulator / device
npx expo start --android
For production builds, see docs/MOBILE_DISTRIBUTE.md.
Repo layout (mobile)
packages/
shared/ @chatwire/shared — types + ChaiwireClient (used by web + mobile)
mobile/ React Native + Expo app
App.tsx Root: NavigationContainer + AppStateProvider
app.json Expo config (name, icons, bundle IDs)
eas.json EAS Build profiles (development / preview / production)
src/
navigation/ RootNavigator, MainTabNavigator (bottom tabs)
screens/ ConversationListScreen, MessageListScreen,
ServerConfigScreen, SettingsScreen
components/ ComposeBox, MessageBubble, ImageViewer, VideoPlayer
hooks/ useServerEvents (SSE), usePushNotifications, useBackgroundFetch
state/ AppStateContext (ChaiwireClient instance, serverUrl)
theme/ colors.ts (Dracula tokens)
src/__tests__/ Jest smoke tests for each screen + hook
Web UI access
By default the web UI has no auth — anyone who can reach the URL can read and send messages. The intended posture is to gate access at the network layer (Tailscale, LAN-only, Cloudflare Access, etc.).
For setups where the URL leaks past that boundary, the wizard's Security
step (or Settings → Web UI password) sets an optional shared password.
It's a single password (not multi-user) stored as a PBKDF2-SHA256 hash in
~/.chatwire/config.json; sessions are signed cookies that expire after
30 days. Forgot it? Stop the web agent, edit config.json (already
chmod 600), drop the web.auth block, restart.
Privacy
Zero telemetry. Period. chatwire collects no analytics, sends no usage
data, phones home to nobody, and includes no third-party SDKs that report
back. You run this on your own hardware and your data stays on your hardware.
Your messages, contacts, and chat.db never leave your Mac — outbound
traffic only goes to integrations you explicitly configure (your Telegram
bot, your ntfy server, etc.).
Two narrow third-party requests the web UI makes, neither carrying any of your data:
- An update-check fetches
api.github.com/repos/<repo>/releases/latestonce a day to surface new-version notices. Disable by settingUPDATE_CHECK_REPO=""in the launchd agent's environment. - Static assets (htmx, emoji-picker-element) load from
unpkg.comandcdn.jsdelivr.net.
Repo layout
bridge.py message relay loop + integration dispatcher
chat_db.py reads chat.db, HEIC -> JPEG via sips
chat_send.py osascript wrappers (send_text, send_file)
config.py config.json loader
chatwire_cli.py CLI: setup / install-agents / doctor / logs / status / migrate
contacts.py Contacts.app -> handle/name lookup
echo_log.py cross-process echo dedup
whitelist.py runtime-mutable contact allowlist
rules.py automation rules engine (loads ~/.chatwire/rules.json)
_version.py semver source of truth
integrations/ built-in plugins (web, stats, favorites)
chatwire-plugins/ standalone plugin packages
chatwire-ntfy/ ntfy notification plugin
chatwire-telegram/ Telegram relay plugin
chatwire-webhook/ Webhook output plugin
chatwire-mqtt/ MQTT broker relay plugin
chatwire-ha/ Home Assistant plugin
chatwire-xmpp/ XMPP relay plugin
chatwire-theme-rosepine/ Rose Pine theme (Moon / Dawn / Main)
packages/sdk/ chatwire-sdk Python package (BaseIntegration, plugin CLI)
packages/shared/ @chatwire/shared TypeScript types + ChaiwireClient (web + mobile)
packages/mobile/ React Native + Expo mobile app (iOS + Android)
web/ FastAPI server: REST API, SSE stream, SPA host
web/frontend/ React SPA (TypeScript, Vite, TanStack Query, Zustand)
src/components/ UI components (MessageList, ComposeBox, Layout, …)
src/pages/ Route-level pages (ChatPage, SettingsPage, PopoutPage)
src/plugins/ Plugin slot system (registry, SlotRenderer, StatsWidget)
e2e/ Playwright E2E + axe accessibility tests
migrations/ config-schema migration runner
templates/launchd/ plist templates rendered by install-agents
scripts/ install.sh, chatwire-loop.sh (dev automation)
docs/ OPEN_SOURCE_PLAN.md, REFERENCE_INSTALL.md, HANDOFF.md,
PLUGIN_DEVELOPMENT.md, master-migration-plan.md
docs/wiki/ Developer reference: architecture, disk layout, permissions,
plugin development, install/uninstall
docs/admin/ Admin guides: unlock-setup.md
Trademarks
iMessage, Messages, macOS, and AppleScript are trademarks of Apple Inc., referenced here in their descriptive sense — this project relays to and from Apple's iMessage service. chatwire is not affiliated with, endorsed by, or sponsored by Apple Inc.
License
MIT — see LICENSE. Copy it, fork it, put your thang down, flip
it, and reverse it. Just follow the MIT requirements and give credit to the
original project.
Contributing
Pull requests are welcome. Whether it's a bug fix, a new feature, or a whole plugin — get involved. Check the plugin system docs if you want to build an integration, or browse the open issues to find something to pick up.
The developer wiki covers architecture, disk layout, macOS permissions, plugin development, and install/uninstall in detail. See the macOS compatibility matrix for a feature-by-feature breakdown across macOS 12–15 and hardware configurations.
Looking for beta testers. If you have a Mac with iMessage and want to try chatwire, open an issue with your setup details and we'll get you running.
Sponsors
If chatwire is useful to you, consider sponsoring the project.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file chatwire-1.15.0.tar.gz.
File metadata
- Download URL: chatwire-1.15.0.tar.gz
- Upload date:
- Size: 640.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2d20910658b5762d7e99bc13b7b9887e500803f5bb0480943ae553f054bba398
|
|
| MD5 |
b475b08ff110edc8ba2324975c903050
|
|
| BLAKE2b-256 |
be11587a116c5fdc13bf217602b391980362bc475d25ba400dcce783615d942e
|
File details
Details for the file chatwire-1.15.0-py3-none-any.whl.
File metadata
- Download URL: chatwire-1.15.0-py3-none-any.whl
- Upload date:
- Size: 570.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2aad9be7309f49bb86b33ba1d350a8e5ca57308b05ee3e0b868fe43ee9023b0a
|
|
| MD5 |
fe466ef444c98900f8eca7f860ad0369
|
|
| BLAKE2b-256 |
e6c7e49b2865bce1d0f4befffff89416ee9af63538776afa05c41f0d4dcb1ac9
|