Local-first Google Messages + WhatsApp inbox for Mac, web, and MCP.
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗OpenMessage is a local-first messaging workspace for Google Messages, WhatsApp, and Signal. Use it from the native macOS app, the localhost web UI, or any MCP-compatible client.
Built on mautrix/gmessages (libgm) for the Google Messages protocol and mcp-go for the MCP server.
git clone https://github.com/MaxGhenis/openmessage.git
cd openmessage
go build -o openmessage .
./openmessage pair
By default, a QR code appears in your terminal. On your phone, open Google Messages > Settings > Device pairing > Pair a device and scan it. The session saves to ~/.local/share/openmessage/session.json.
If Google only offers account pairing, you can also pair with Google account cookies copied from browser devtools:
pbpaste | ./openmessage pair --google
The CLI accepts either a JSON cookie object or a full curl command for messages.google.com/web/config, then prompts you to confirm an emoji on your phone.
./openmessage serve
This starts the Web UI at http://127.0.0.1:7007. A bare serve is web-only.
MCP transports require an explicit opt-in:
./openmessage serve --mcp-sse starts MCP Streamable HTTP and SSE without the web UI../openmessage serve --web --mcp-sse starts the web UI and both HTTP MCP endpoints../openmessage serve --mcp-stdio starts MCP over stdio for pipe-based clients.After serve is running, open the local UI and link WhatsApp or Signal from the Connections surface. OpenMessage keeps those bridges local and syncs them into the same inbox as Google Messages.
./openmessage demo
or:
./openmessage serve --demo
Demo mode starts the same local UI on the normal port, but:
This is the safest way to capture website screenshots, App Store assets, or demo recordings without real messages bleeding back in.
Add to ~/.mcp.json:
{
"mcpServers": {
"openmessage": {
"command": "/path/to/openmessage",
"args": ["serve", "--mcp-stdio"]
}
}
}
Restart Claude Code. The MCP tools appear automatically.
To let a client connect over HTTP, start OpenMessage with --mcp-sse (or --web --mcp-sse to keep the web UI), then point it at:
http://127.0.0.1:7007/mcphttp://127.0.0.1:7007/mcp/sse| Tool | Description |
|---|---|
get_messages | Recent messages with filters (phone, date range, limit) |
get_conversation | Messages in a specific conversation |
search_messages | Full-text search across all messages |
send_message | Send a direct text by platform. Defaults to SMS/RCS and also supports direct WhatsApp/Signal recipients |
send_to_conversation | Send a text reply directly to an existing conversation ID |
send_media_to_conversation | Send a local file attachment to an existing conversation ID |
send_group_message | Send to a group conversation by conversation ID |
react_to_message | Add, remove, or switch a reaction on an existing message |
set_message_transcript | Save or update a transcript for an audio/media message (not on a v2-primary install, whose v2 store keeps no transcripts) |
list_conversations | List recent conversations |
list_contacts | List/search contacts |
resolve_contact_routes | Resolve a person or phone number to the available SMS/WhatsApp/Signal routes |
get_status | Google Messages, WhatsApp, and Signal connection status |
download_media | Resolve an attachment (message ID or v2msg:<id>:<n> media_id) to its local API URL and metadata; save=true also writes a private copy under the export directory |
draft_message | Save a draft for the local app to review/send later |
import_messages | Import Google Chat, iMessage, WhatsApp export, or Signal Desktop history |
get_person_messages | Load messages for conversations matching a person's name |
get_person_messages_range | Load a bounded time range of messages matching a person's name |
conversation_stats | Summarize conversation counts, cadence, and activity |
person_stats | Summarize a person's cross-route activity |
generate_story | Generate a narrative summary for one conversation |
generate_person_story | Generate a narrative summary for a person across routes |
generate_viz | Generate a local HTML visualization from a conversation |
render_story | Render a story/viz HTML artifact with optional local photos |
list_conversations(source_platform="signal")search_messages(query="airbnb")send_message(platform="signal", recipient="+15551230000", message="On my way")send_to_conversation(conversation_id="whatsapp:[email protected]", message="On my way")send_media_to_conversation(conversation_id="signal-group:abc123", file_path="/tmp/photo.jpg", caption="Here")react_to_message(conversation_id="signal-group:abc123", message_id="signal:...", emoji="🔥")resolve_contact_routes(query="Taylor")get_person_messages(name="Taylor", limit=50)import_messages(source="signal", path="$HOME/Library/Application Support/Signal", name="Your Name", address="+15551230000")The web UI runs at http://localhost:7007 when the server is started. It provides:
The repo also includes a native Swift wrapper around the same local backend:
The macOS app target lives under OpenMessage/.
| Env var | Default | Purpose |
|---|---|---|
OPENMESSAGES_DATA_DIR | ~/.local/share/openmessage | Data directory (DB + session) |
OPENMESSAGES_LOG_LEVEL | info | Log level (debug/info/warn/error/trace) |
OPENMESSAGES_PORT | 7007 | Web UI port |
OPENMESSAGES_HOST | 127.0.0.1 | Host/interface to bind the local web server to |
OPENMESSAGES_MY_NAME | system user name | Display name for outgoing imported iMessage/WhatsApp messages |
OPENMESSAGES_STARTUP_BACKFILL | auto | Startup history sync mode: auto, shallow, deep, or off |
OPENMESSAGES_BACKFILL_DISCOVER_ORPHANS | 0 | Opt in to deep backfill's Phase C (contact-based orphan discovery). Off by default because it creates an empty SMS thread on your phone for each contact without prior message history. Enable with 1/true/yes/on only if you understand the side effect. |
OPENMESSAGES_KLIPY_API_KEY | unset | Optional Klipy API key for the web compose GIF picker. GIF search is unavailable until this is set. KLIPY_API_KEY is also accepted as a fallback. |
OPENMESSAGE_COOKIE_REFRESH_SCRIPT | unset | Optional command run before reconnect when Google session cookies expire. If unset, OpenMessage backs off and prompts for manual re-pair instead of hammering Google's auth endpoint. |
OPENMESSAGES_MACOS_NOTIFICATIONS | interactive macOS serve sessions only | Enable/disable native macOS notifications for fresh inbound live messages (1/0). Click-through opens the matching thread when terminal-notifier is available. The macOS app sets this to 0 and posts its own notifications (with tap-to-open and active-thread suppression) instead. |
OPENMESSAGES_SIGNAL_TMP_SWEEP | enabled | Set to 0 to disable the cleanup of stale signal-cli temp directories (the app-owned run dirs plus libsignal* dirs older than 24h that pre-v0.2.10 builds leaked into the system temp dir — see #27). |
OPENMESSAGES_SIGNAL_CLI | auto-detected signal-cli | Optional path to a specific signal-cli binary. |
OPENMESSAGES_GOOGLE_AVATAR_SYNC | enabled | Set to 0 to disable Google contact avatar sync. The older singular OPENMESSAGE_GOOGLE_AVATAR_SYNC name is also accepted. |
OPENMESSAGES_EXPORT_DIR | ~/Documents/OpenMessage | Directory for generate_viz / render_story HTML outputs and photo inputs when using the default confined export mode. |
OPENMESSAGES_ALLOW_ANY_EXPORT_PATH | unset (off) | Set to 1/true/yes/on to allow viz/story tools to read photos from, or write HTML to, arbitrary local paths. |
OPENMESSAGE_TELEMETRY | unset (off) | Set to 1 to send one anonymous heartbeat per launch (max one per 24h). Reports only: random install ID, version, OS/arch, and which platforms are paired (Google Messages / WhatsApp / Signal). No message content, no contact info, no IP-based identity. See internal/telemetry/. |
OPENMESSAGES_HOST defaults to localhost for safety. If you bind the server to another interface for LAN testing, keep it on a trusted network; local-control APIs and MCP endpoints are meant for same-origin/local clients.
The web compose GIF picker uses Klipy search. To enable it:
Create a Klipy API key.
Start OpenMessage with the key in the environment:
export OPENMESSAGES_KLIPY_API_KEY=<your key>
./openmessage serve
Optional: verify the local endpoint can reach Klipy:
curl "http://127.0.0.1:7007/api/gifs/trending?limit=1"
Open the web UI and use the GIF button in a media-capable conversation. GIF sends use the same media path as other attachments, so they are only enabled for routes that currently support media.
OpenMessage does not include a bundled GIF provider key. Without OPENMESSAGES_KLIPY_API_KEY, GIF search endpoints return a setup error and the rest of messaging continues to work normally.
If you launch OpenMessage from the macOS app, launchd, systemd, or another supervisor, configure the environment there too; exporting the key in a shell only affects serve processes started from that shell.
/mcp and SSE at /mcp/sse when enabled with --mcp-sse; stdio is available with --mcp-stdiosession.json; expired Google cookies can be refreshed by an optional script or surfaced as a guided re-pair flowgo test ./... # Run all tests
go build . # Build binary
npm install # Install Playwright test runner
npx playwright install chromium
npm run test:e2e # Run browser-level web UI tests
./openmessage pair # Pair with phone
./openmessage serve # Start server
./openmessage demo # Start isolated fake-data demo mode
Before publishing a build or website update, run through the release checklist.
Debugging a live install (failing sends, re-pairing, the two-data-dir gotcha, signal-cli version)? See docs/agent-runbook.md.
MIT