A native macOS web client for iMessage and Find My, powered by imessage-rs. Access your messages and locate your devices from any browser on your network β no Apple device required on the viewing end.
iMessage
- Send and receive texts, reactions (tapbacks), replies, edits, and unsends
- Attachments β send images, videos, and files via drag-and-drop or paste
- Scheduled messages β create, edit, and cancel via IMCore private API
- Typing indicators β see when someone is typing, and broadcast yours
- Read receipts β automatic when you open a conversation
- Pin/unpin conversations β via IMCore, synced with Messages.app
- Compose β search contacts, start 1:1 or group conversations
- Chat deduplication β same contact via phone and email merged into one thread
- Edit/unsend eligibility β 15-minute edit window, 2-minute unsend window, with countdown timers
Find My
- Device and friend locations on an interactive map (street, satellite, hybrid views)
- Reverse geocoding β coordinates resolved to street addresses
- Distance calculations from your current location
- Real-time location updates via webhooks
Architecture
- Local-first β IndexedDB (Dexie.js) mirrors all data for instant UI and offline reads
- Real-time β Server-Sent Events push new messages, typing indicators, and location updates
- Private β access restricted to localhost and Tailscale IPs only, no login needed
- Dark mode with system detection
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Browser β
β β
β SvelteKit SPA ββ> IndexedDB (Dexie.js) β
β β β
β β fetch /api/* EventSource /api/events (SSE) β
βββββββββΌβββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββ
β β
v β
βββββββββββββββββββββββββββββββββββββββββββββββββ
β SvelteKit Server (Node.js, adapter-node) β
β β
β /api/proxy/* βββ> imessage-rs REST API β
β /api/webhook <βββ imessage-rs webhooks β
β /api/events βββ> SSE broadcaster β
β /api/contacts βββ> AddressBook SQLite β
β /api/scheduled-messages ββ> imcore-bridge β
β /api/plist/pinned ββ> pinned-chats β
β /api/geocode βββ> reverse-geocode β
β /api/me βββ> osascript (Contacts.app) β
βββββββββββββββββββββ¬ββββββββββββββββββββββββββββ
β
β http://127.0.0.1:1234
v
ββββββββββββββββββββ
β imessage-rs β
β (native macOS) β
β β
β Reads chat.db β
β Sends via IDS β
β Find My API β
ββββββββββββββββββββ
Data flow for a new message:
- imessage-rs polls
~/Library/Messages/chat.dbevery ~5 seconds - New message detected β HTTP POST to
/api/webhook - SvelteKit server broadcasts to all connected SSE clients
- Browser receives SSE event β sync engine writes to IndexedDB
- Dexie
liveQuerytriggers reactive UI update
Three Swift binaries extend what imessage-rs can't do alone:
| Binary | Framework | Purpose |
|---|---|---|
imcore-bridge |
IMCore (private) | Scheduled messages CRUD, connects to imagent daemon |
pinned-chats |
IMCore (private) | Reads pinned conversation list from local store |
reverse-geocode |
CoreLocation | Converts lat/lon to street addresses |
These require SIP disabled and ad-hoc code signing with private entitlements (handled automatically).
| Requirement | Why |
|---|---|
| macOS Sequoia 15+ | imessage-rs requires modern macOS private frameworks |
| SIP disabled | Swift binaries and imessage-rs access private IMCore/IDS frameworks |
| Full Disk Access | Server reads ~/Library/Messages/chat.db and AddressBook databases |
| Node.js 22+ | Runs the SvelteKit web server |
Disable SIP: Boot into Recovery Mode (hold Power on Apple Silicon, Cmd+R on Intel), open Terminal:
csrutil disableReboot into macOS.
Full Disk Access: System Settings > Privacy & Security > Full Disk Access > add your terminal app.
curl -fsSL https://github.com/pandoks/webMessages/releases/latest/download/webmessages -o webmessages
chmod +x webmessages
./webmessagesThat's it. A random port is chosen automatically and printed to the terminal. Press Ctrl+C to stop. Nothing is left on disk β everything extracts to a temp directory and is cleaned up on exit.
To pin a specific port:
PORT=3000 ./webmessagesThe downloaded file is a self-extracting shell script. Each time you run it, it:
- Extracts pre-compiled binaries and the web server to a temp directory
- Strips macOS quarantine attributes and re-signs
imcore-bridge - Auto-generates an API password and finds free ports
- Starts imessage-rs and the Node.js web server
- Prints the URL to the terminal
- On exit (Ctrl+C), kills both services and deletes the temp directory
No compilation, no config files, no persistent state. Everything is pre-built by GitHub Actions.
All settings have sensible defaults. Override via env vars if needed:
| Variable | Default | Description |
|---|---|---|
PORT |
(random free port) | Web server port |
IMESSAGE_RS_PORT |
(random free port) | imessage-rs API port |
IMESSAGE_RS_PASSWORD |
(auto-generated) | Shared secret between web server and imessage-rs |
Download the latest release and replace your binary. There's nothing else to update.
| Tool | Version | Install |
|---|---|---|
| macOS | 15+ | β |
| SIP | disabled | Recovery Mode |
| Node.js | 22+ | nodejs.org or nvm install 22 |
| pnpm | latest | corepack enable |
| Xcode CLT | latest | xcode-select --install (provides swiftc, codesign) |
| Rust + cargo | latest | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh |
| imessage-rs | latest | cargo install imessage-rs |
# Clone
git clone https://github.com/pandoks/webMessages.git
cd webMessages
# Install JS dependencies
pnpm install
# Compile Swift binaries (once, ad-hoc signed)
pnpm build:swift
# Start imessage-rs in a separate terminal (all via CLI flags, no config file)
imessage-rs --password secret --enable-private-api true \
--enable-findmy-private-api true \
--webhook "http://localhost:5173/api/webhook"
# Use localhost, not 127.0.0.1 β Vite binds on IPv6
# Start dev server (password must match)
IMESSAGE_RS_PASSWORD=secret pnpm devDev server runs at http://localhost:5173. Use pnpm dev --host to expose on your Tailscale IP.
| Variable | Default | Description |
|---|---|---|
IMESSAGE_RS_URL |
http://127.0.0.1:1234 |
imessage-rs API URL |
IMESSAGE_RS_PASSWORD |
(required) | Must match the --password flag you started imessage-rs with |
src/
βββ lib/
β βββ components/ # Svelte 5 UI (ChatView, MessageBubble, FindMyMap, etc.)
β βββ db/ # Dexie IndexedDB schema (chats, messages, handles, attachments)
β βββ server/ # Server-only: auth, contacts, env, events broadcaster
β β βββ imcore-bridge.swift # Scheduled messages via IMCore
β β βββ imcore-bridge.entitlements
β β βββ pinned-chats.swift # Pinned conversations via IMCore
β β βββ reverse-geocode.swift # CLGeocoder wrapper
β βββ stores/ # Svelte 5 reactive stores (findmy, theme, visibility)
β βββ sync/ # Sync engine + SSE client + data transforms
β βββ types/ # TypeScript types (BlueBubbles API, DB models, API contracts)
β βββ utils/ # Formatting, phone normalization, haversine distance
βββ routes/
β βββ api/
β β βββ proxy/[...path]/ # Catch-all proxy to imessage-rs
β β βββ webhook/ # Receives webhook POSTs from imessage-rs
β β βββ events/ # SSE stream to browser
β β βββ contacts/ # Contact map from AddressBook SQLite
β β βββ me/ # Current user name + photo via osascript
β β βββ scheduled-messages/ # CRUD via imcore-bridge binary
β β βββ plist/pinned/ # Pinned chats via pinned-chats binary
β β βββ geocode/ # Reverse geocoding via reverse-geocode binary
β β βββ chat-previews/ # Retracted message preview overrides
β β βββ message-eligibility/ # Edit/unsend time windows
β β βββ unread-counts/ # Per-chat unread counts from chat.db
β βββ messages/ # iMessage UI (ChatList + ChatView)
β βββ findmy/ # Find My UI (map, sidebar, detail panel)
scripts/
βββ build-release.sh # Build self-extracting archive
βββ start.sh # Production orchestrator
| Layer | Technology |
|---|---|
| Frontend | SvelteKit 2, Svelte 5 (runes: $state, $derived, $effect, $props) |
| Styling | Tailwind CSS 4 (via Vite plugin, zero-config) |
| Offline storage | Dexie.js 4 (IndexedDB with liveQuery for reactive subscriptions) |
| Maps | Leaflet 1.9 (CartoDB, ArcGIS tile layers) |
| Server | SvelteKit adapter-node on Node.js 22 |
| Real-time | SSE (Server-Sent Events) via custom EventBroadcaster |
| Native bridge | imessage-rs (Rust) + 3 Swift binaries |
| Testing | Vitest 4 (Node + browser projects), Playwright |
| Formatting | Prettier with Svelte and Tailwind plugins |
No login flow. Access is controlled by IP allowlist in src/hooks.server.ts:
127.0.0.1/::1(localhost)100.64.0.0/10(Tailscale CGNAT range)
All other IPs get a 403.
pnpm build # Vite builds to build/
node build # Run the production server (reads env vars at runtime)pnpm vitest run --project server # Server-side unit tests (Node)
pnpm vitest run --project client # Component tests (Chromium via Playwright)
pnpm test:unit -- --run # Both
pnpm test:e2e # End-to-end (Playwright)
pnpm test # Everythinggit tag v1.0.0
git push origin v1.0.0GitHub Actions (release.yml) runs on a macOS 15 runner and:
- Compiles the 3 Swift binaries with ad-hoc signing
- Downloads the latest imessage-rs release binary
- Builds SvelteKit (
pnpm build) - Packages everything into a self-extracting archive via
build-release.sh - Uploads
webmessages-v1.0.0to GitHub Releases
To build a release locally:
bash scripts/build-release.sh --version v1.0.0
# Output: dist/webmessages-v1.0.0 (~9 MB)ci.ymlruns on every push tomasterand on PRs: lint, type-check, server unit tests, build verification (Ubuntu, no macOS needed)release.ymlruns onv*tags: full macOS build + release (macOS 15 runner)
- Fork the repository
- Create a feature branch (
git checkout -b feature/my-feature) - Make your changes
- Run
pnpm testandpnpm lint - Open a pull request
This project is not yet licensed. Contact the maintainer for usage terms.