Read, search, and send messages across iMessage, SMS/RCS, Telegram, WhatsApp, and Facebook Messenger from the terminal. macOS only.
- Messages -- iMessage and SMS/RCS (blue and green bubbles)
- Telegram -- Full support including sending (piggybacks on your Telegram.app session)
- WhatsApp -- Full support including sending (requires WhatsApp Desktop and one-time QR auth for sending)
- Messenger -- Full support including sending (requires one-time browser login for cookie extraction)
All commands work across platforms by default. Use --platform/-p to filter by a specific platform (messages, telegram, whatsapp, or messenger). Platform tags [ms]/[tg]/[wa]/[fb] appear in merged output.
Requires Python 3.11+ and uv.
uv run messages --helpReading WhatsApp messages works out of the box (reads the WhatsApp Desktop database directly). To enable sending, you need to authenticate once:
# Build the auth tool (requires Go)
cd wa-auth-tool && go build -o wa-auth && cd ..
# Build the send tool
cd wa-send-tool && go build -o wa-send && cd ..
# Authenticate by scanning a QR code with your phone
messages auth whatsappThis creates a session at ~/.whatsapp-cli/whatsapp.db. You only need to do this once.
# Build the pagination tools (requires Go)
cd fb-fetch-tool && go build -o fb-fetch && cd ..
cd fb-threads-tool && go build -o fb-threads && cd ..
# Log in via browser to extract cookies
messages auth messengerCookies are saved to ~/.config/messages-cli/messenger_cookies.json. The Go binaries are only needed for pagination: fb-fetch loads older messages within a thread, fb-threads discovers older threads beyond the initial ~15 from the inbox sync. Basic reading and sending work without them.
On first run, fb-threads automatically registers an E2EE device with Facebook's Signal Protocol infrastructure (ICDC) to access end-to-end encrypted threads. Device keys are persisted in ~/.config/messages-cli/messenger_e2ee.db.
$ messages chats recent --limit 4
Alice Johnson 2026-02-28 14:30:12 [tg] @alicej +41 79 123 45 67
John Smith 2026-02-28 14:30:12 [ms] +1 206-555-1234
Family Group 2026-02-27 09:15:43 [wa]
Book Club 2026-02-26 20:00:01 [ms]
$ messages chats recent --platform whatsapp --limit 3
Family Group 2026-02-27 09:15:43
Alice Johnson 2026-02-26 20:00:01 +41 79 123 45 67
Work Chat 2026-02-25 18:00:00$ messages chats find "Alice"
Alice Johnson [tg] @alicej +41 79 123 45 67
Alice Johnson [wa] +41 79 123 45 67
$ messages chats find "John"
John Smith [ms] +1 206-555-1234
Book Club [ms]Accepts contact names, group chat names, phone numbers, or Telegram usernames. Auto-detects the platform unless --platform is specified.
$ messages read "Sarah" --limit 3
2026-02-27 08:10:00 Sarah Chen Running 5 min late
2026-02-27 08:12:30 Me No worries, I'll grab us a table
2026-02-27 09:15:43 Sarah Chen Thanks for breakfast! [image: IMG_2041.heic]
$ messages read "Family Group" -p whatsapp --limit 3
2026-02-27 10:00:00 Dad Anyone free for dinner Sunday?
2026-02-27 10:05:00 Me I'm in
2026-02-27 10:10:00 Mom Me too!$ messages search "dinner" --limit 3
2026-02-27 18:30:00 John Smith Me Dinner at 7? [ms]
2026-02-26 12:15:00 Family Group Dad Dinner plans? [wa]
2026-02-25 09:00:00 Sarah Chen Sarah Chen Thanks for dinner! [ms]
$ messages search "dinner" --chat "Family Group" -p whatsapp
2026-02-26 12:15:00 Family Group Dad Dinner plans?
$ messages search "dinner" --context 2
--- Family Group [wa] ---
2026-02-26 12:10:00 Mom What should we do this weekend?
2026-02-26 12:12:00 Me Not sure yet
2026-02-26 12:15:00 Dad Dinner plans? <--
2026-02-26 12:20:00 Mom Great idea!
2026-02-26 12:25:00 Me I'm inDry-run by default. Pass --confirm to actually send.
$ messages send "Sarah" "Hey, are we still on for tomorrow?"
Would send [ms] to +1 415-555-9876: Hey, are we still on for tomorrow?
Pass --confirm to actually send.
$ messages send "Alice" "See you at 3" -p whatsapp --confirm
Message sent.$ messages contacts search "John"
John Smith
phone: +1 206 555 1234
phone: +41 78 555 6789
email: john.smith@example.com$ messages auth whatsapp
Scan the QR code below with WhatsApp on your phone:
Open WhatsApp > Settings > Linked Devices > Link a Device
# QR code appears here
$ messages auth messenger
# Opens a browser window to log into messenger.com
# Cookies are saved to ~/.config/messages-cli/messenger_cookies.json$ messages stats
messages Messages: 48,291 Chats: 142
telegram Messages: 28,529 Chats: 207
whatsapp Messages: 12,847 Chats: 89
messenger Messages: 1,204 Chats: 31- Full Disk Access -- Grant your terminal app Full Disk Access in System Settings > Privacy & Security. Required for reading the iMessage, Telegram, and WhatsApp databases.
- sqlcipher --
brew install sqlcipher. Required for Telegram support. - Go -- Required to build the WhatsApp send/auth tools and the Messenger pagination tool.
brew install go. - WhatsApp Desktop -- The native macOS app (not the web version). Required for WhatsApp reading.
- Messenger cookies -- Run
messages auth messengerto log in via browser. Required for Messenger support.
Messages -- Queries the Messages SQLite database (~/Library/Messages/chat.db) directly for reading. Sends via AppleScript. Resolves phone numbers to contact names, shows reactions and attachments, formats phone numbers by country code. Image attachments are returned as local file paths from ~/Library/Messages/Attachments/.
Telegram -- Decrypts the local Telegram database (SQLCipher-encrypted postbox) for reading. For sending, it extracts the persistent MTProto auth key from the local database and uses Telethon to make API calls, no separate login needed. Image attachments are resolved from the local media cache by parsing photo resource IDs from the binary message format.
WhatsApp -- Reads the WhatsApp Desktop SQLite databases (~/Library/Group Containers/group.net.whatsapp.WhatsApp.shared/) directly. Resolves contact names from the contacts database and group member tables. For sending, uses a Go binary (whatsmeow) with a one-time QR code pairing flow. Image attachments are returned as local file paths from the WhatsApp media cache.
Messenger -- Authenticates with browser cookies extracted via a one-time login flow. Reads messages by fetching thread pages from messenger.com and parsing the embedded Lightspeed payloads. Two Go binaries (using mautrix-meta) connect via Facebook's MQTT WebSocket protocol for pagination: fb-fetch pages through older messages within a thread, and fb-threads pages through the thread list itself (the initial Lightspeed sync only returns ~15 recent threads). fb-threads also registers an E2EE device via Facebook's ICDC protocol (Signal Protocol key exchange) to access end-to-end encrypted threads, which Facebook migrated most conversations to in 2023. E2EE 1-on-1 DMs are discovered by intercepting Lightspeed createOpenToE2EEThreadLink entries, and contact names are resolved via GetContactsFullTask over MQTT. Device keys are persisted locally in a SQLite database. Sends messages via Facebook's Lightspeed GraphQL API. Image attachments are downloaded from Facebook's CDN to ~/.cache/messages-cli/messenger/.
Message dicts from read_messages() include:
timestamp-- ISO 8601 with local timezone (e.g.,2026-03-10T11:09:49+01:00)sender-- display name or"Me"text-- message content with attachment tagsis_from_me-- booleanimage_paths-- list of local file paths to image attachments
Chat/thread dicts from recent_chats() include message_count (per-thread message count) on iMessage, WhatsApp, and Telegram. Messenger's all_threads() returns {"threads": [...], "has_more": bool} indicating whether pagination was exhaustive.
If you find this useful, buy me a coffee.
