3.9 KiB
ProtonMail Mini-Bridge
A minimal ProtonMail Bridge implementation in Rust that exposes local IMAP and SMTP servers,
allowing skim (and any other standard email client) to connect without code changes.
The full ProtonMail Bridge is ~50,000 lines of Go. This mini-bridge targets only the subset
of functionality that skim needs: one account, INBOX only, one concurrent client.
Components
1. Project scaffold
New binary crate with Cargo.toml dependencies (tokio, reqwest, proton-srp, rpgp,
serde, toml). Config file format covering ProtonMail credentials and local bind ports.
2. ProtonMail authentication
SRP 6a login flow against the Proton API:
- POST
/auth/infowith username → receive modulus, server ephemeral, salt - Compute SRP proof locally using
proton-srp - POST
/auth/loginwith client proof → receive access token + encrypted private keys - Handle optional TOTP 2FA interactively on first run
- Persist session (access token + refresh token) to disk to avoid re-authenticating on restart
3. ProtonMail API client
Thin reqwest-based HTTP wrapper around the endpoints the bridge needs:
- List messages (with pagination)
- Fetch single message (metadata + encrypted body)
- Delete message
- Fetch recipient public key (for outbound encryption)
- Send message (modelled from the open-source
ProtonMail/proton-bridgeGo implementation)
4. Crypto layer
Using rpgp or proton-crypto-rs:
- Decrypt the user's private key (delivered encrypted by the API, unlocked with the mailbox password)
- Decrypt incoming message bodies with the private key
- Encrypt outbound messages to recipient public keys
5. Message store
In-memory (optionally persisted) mapping between IMAP sequence numbers and Proton message IDs. IMAP uses stable sequential integers; Proton uses opaque string IDs. The store must:
- Assign sequence numbers to messages in order
- Renumber correctly after deletes
- Survive restarts without breaking existing client state
6. IMAP server
TCP listener on localhost:143 (configurable). Implements the nine commands skim uses:
| Command | Purpose |
|---|---|
LOGIN |
Accept local credentials (no real auth needed) |
NOOP |
Keepalive / connection check |
SELECT INBOX |
Open mailbox, report message count |
FETCH range BODY.PEEK[HEADER.FIELDS (SUBJECT FROM DATE)] |
List emails |
FETCH seq BODY.PEEK[] |
Fetch full message body |
SEARCH OR SUBJECT "..." FROM "..." |
Search by subject or sender |
STORE seq +FLAGS (\Deleted) |
Mark for deletion |
EXPUNGE |
Delete marked messages |
LOGOUT |
Disconnect |
Each command translates to API client + crypto layer calls via the message store.
7. SMTP server
TCP listener on localhost:587 (configurable). Minimal implementation:
- EHLO, AUTH, MAIL FROM, RCPT TO, DATA, QUIT
- On DATA completion: hand the message to the crypto layer to encrypt, then POST via API client
Build order
Components 2 → 3 → 4 can be built and tested with a simple CLI harness before any
network server exists. Component 5 is pure logic with no I/O. Components 6 and 7 are
the final pieces and can be validated by pointing tuimail at localhost.
References
- ProtonMail/proton-bridge — official Bridge (Go, open source)
- ProtonMail/go-proton-api — official Go API client
- ProtonMail/proton-crypto-rs — official Rust crypto crates
- ProtonMail/proton-srp — official Rust SRP implementation
- rpgp — pure Rust OpenPGP implementation
More work:
- local storge in the proton-bridge. Decide whether to store the message body pgp-encrypted, as it came from the proton server, or use decrypt it (and serve to the client using imap) and create a AES-256-GCM encrypted cache
- email replies in the tui client have not yet been implemented