An open source reference app from XMTP Labs demonstrating how to build secure messaging for Bluesky users.
This app publishes XMTP signatures to the org.xmtp.inbox AT Protocol record to cryptographically bind Bluesky handles to XMTP inboxes, enabling end-to-end encrypted DMs and group chats for the Bluesky network.
For more details, read the XMTP blog post on unlocking quantum-encrypted group chats for AT Protocol.
Bluesky doesn't have encrypted DMs or group chats. This app gives Bluesky users secure messaging—Signal-level encryption with forward secrecy and quantum resistance—on decentralized infrastructure.
pnpm install
pnpm devLog in with your Bluesky account.
XMTP (Extensible Message Transport Protocol) is an open protocol for secure, decentralized messaging:
- End-to-end encrypted — Only you and your recipient can read messages
- Decentralized — Messages live on the XMTP network, not a company's servers
- Portable — Your inbox works across any app built on XMTP
XMTP supports cryptographically verifiable identities. This app brings secure, encrypted chat to Bluesky by binding your @handle.bsky.social to an XMTP inbox.
- End-to-end encrypted messaging powered by XMTP
- Bluesky identity integration — log in with your Bluesky handle
- Direct messages and group chats (up to 250 members)
- Desktop notifications with badge counts
- Secure key storage using Electron's safeStorage API
- Real-time message streaming
This app creates a cryptographic link between your Bluesky account and an XMTP inbox.
For you, this means:
- Your
@handle.bsky.socialbecomes your chat address - Anyone can find and message you using your Bluesky handle
- Your messages are encrypted and stored on XMTP, not Bluesky
How it works under the hood:
┌─────────────────────────────────────────────────────────────────────────┐
│ │
│ 1. Login 2. Generate Key 3. Create Inbox 4. Publish │
│ ┌──────────┐ ┌──────────────┐ ┌─────────────┐ ┌──────────┐ │
│ │ Bluesky │ ───▶ │ Ethereum │ ──▶ │ XMTP │ ─▶│ Bluesky │ │
│ │ OAuth │ │ Private Key │ │ Inbox │ │ Record │ │
│ └──────────┘ └──────────────┘ └─────────────┘ └──────────┘ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ Your DID Stored locally Your inbox ID Signed │
│ (did:plc:...) in secure storage on XMTP network proof │
│ │
└─────────────────────────────────────────────────────────────────────────┘
When someone wants to message you:
- They look up your
org.xmtp.inboxrecord on Bluesky - They verify the cryptographic signature matches a key in your XMTP inbox
- If valid, they know this Bluesky user controls this XMTP inbox
When you search for a Bluesky user to message, the app fetches their org.xmtp.inbox record directly from their PDS.
For the reverse — identifying who sent you a message — the app uses Jetstream, Bluesky's real-time firehose, to watch for new org.xmtp.inbox records and build a lookup index.
To use the same inbox on multiple devices, export your key from Settings > Identity & Security and import it on the new device. Each device creates an "installation" (up to 10 per inbox).
Note: Message history does not sync between devices yet. Each device will only see messages sent/received while it's active.
Keep your key backup safe—anyone with your private key can read your messages.
| Component | Technology |
|---|---|
| Framework | Electron 40 + Vite + React 19 + TypeScript |
| Styling | Tailwind CSS v4 |
| State | Zustand |
| Messaging | @xmtp/browser-sdk v6 |
| Identity | @atproto/api, @atproto/oauth-client-browser |
| Crypto | viem (Ethereum key generation/signing) |
| Storage | Electron safeStorage for keys |
- Node.js 18+
- pnpm
pnpm install # Install dependencies
pnpm dev # Run in development mode
pnpm build # Build for production
pnpm dist # Package for distribution
pnpm dist:beta # Package beta build (with devtools)
pnpm dist:prod # Package production build (no devtools)
pnpm test # Run testsThe app has three build modes with different developer tools access:
| Mode | Trigger | DevTools |
|---|---|---|
| Development | pnpm dev |
Auto-opens on launch |
| Beta | Version contains -beta (e.g., 1.0.0-beta.1) |
View → Toggle Developer Tools |
| Production | Clean version (e.g., 1.0.0) |
Disabled |
Build mode is detected automatically from the version string — no environment variables needed.
To build signed and notarized apps locally:
- Install the Developer ID certificate in your Keychain (get
.p12from team admin) - Copy
.env.exampleto.env.localand fill in your values - Build:
pnpm build && pnpm dist:prodRequired environment variables (see .env.example):
| Variable | Description |
|---|---|
APPLE_ID |
Your Apple ID (must be added to XMTP team) |
APPLE_APP_SPECIFIC_PASSWORD |
From appleid.apple.com → App-Specific Passwords |
APPLE_TEAM_ID |
Team ID (from Apple Developer portal or team admin) |
CI builds use GitHub Secrets — no local setup needed for releases.
bluesky-chat/
├── electron/
│ ├── main.ts # Electron main process
│ ├── preload.ts # IPC preload script
│ └── services/
│ ├── storage.ts # Secure key storage
│ └── notifications.ts # Desktop notifications
├── src/
│ ├── components/
│ │ ├── auth/ # Login flow
│ │ ├── chat/ # Chat UI, groups, message composer
│ │ ├── composer/ # Message input
│ │ ├── connection/ # XMTP connection status
│ │ ├── conversation/ # Conversation list
│ │ ├── layout/ # App layout
│ │ ├── onboarding/ # First-run experience
│ │ ├── profile/ # User profiles
│ │ ├── settings/ # App settings
│ │ └── shared/ # Reusable components
│ ├── hooks/ # React hooks
│ ├── services/
│ │ ├── bluesky.ts # Bluesky API
│ │ ├── xmtp.ts # XMTP client
│ │ ├── signer.ts # Ethereum signer for XMTP
│ │ ├── identity.ts # Bluesky ↔ XMTP identity mapping
│ │ └── crypto.ts # Encryption utilities
│ ├── stores/ # Zustand state stores
│ ├── types/ # TypeScript types
│ └── utils/ # Utility functions
└── public/
└── client-metadata.json # OAuth client metadata
pnpm release:beta # Bumps to x.x.x-beta.N, creates tag, pushespnpm release # Bumps patch version, creates tag, pushesFor minor or major releases:
npm version minor && git push --follow-tags
npm version major && git push --follow-tagsGitHub Actions builds for macOS, Windows, and Linux, then uploads artifacts to a draft release. Beta releases include developer tools access; production releases have devtools disabled.
Click "Login with Bluesky" to authenticate via OAuth. The app opens a window for Bluesky authentication.
Alternatively, create an App Password and log in with your handle and app password.
Currently only bsky.social is supported as a PDS (Personal Data Server). PRs are welcome to add support for other federated AT Protocol instances.
Your local inbox doesn't match your published Bluesky record. This happens if you logged in on a different device first or cleared app data.
Options:
- Republish — Update your Bluesky record to point to this device's inbox
- Import key — Import the backup from your original device
MIT