tuimail/PROTON.md
2026-02-22 18:57:26 +01:00

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/info with username → receive modulus, server ephemeral, salt
  • Compute SRP proof locally using proton-srp
  • POST /auth/login with 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-bridge Go 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

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