Warning
Experimental — use at your own risk. This is an unofficial terminal client for Proton Mail Bridge. It may contain bugs or data-loss risks.
Important
Not affiliated with Proton AG / Proton Mail. Independent open-source project. Not endorsed by or affiliated with Proton.
A keyboard-driven terminal mail client for Proton Mail Bridge, written in Rust.
It speaks standard IMAP and SMTP to Bridge running locally — no Proton API calls, no proprietary protocol.
┌─────────────────────────────────────────────────────────────────┐
│ protonmail-cli │
│ │
│ ┌──────────────────────┐ ┌──────────────────────────┐ │
│ │ UI layer │ │ CLI layer │ │
│ │ src/ui/app.rs │ │ src/cli.rs │ │
│ │ src/ui/theme.rs │ │ setup / doctor │ │
│ │ │ │ │ │
│ │ ratatui TUI │ │ clap + dialoguer │ │
│ │ crossterm events │ │ │ │
│ └──────────┬───────────┘ └────────────┬─────────────┘ │
│ │ │ │
│ └──────────────┬────────────────┘ │
│ ▼ │
│ ┌─────────────────────────────┐ │
│ │ Mail backend │ │
│ │ src/mail/backend.rs │ │
│ │ src/mail/model.rs │ │
│ │ │ │
│ │ MailBackend trait │ │
│ │ BridgeBackend impl │ │
│ └──────────┬──────────────────┘ │
│ │ │
│ ┌──────────────┴──────────────┐ │
│ ▼ ▼ │
│ ┌─────────────┐ ┌───────────────┐ │
│ │ IMAP │ │ SMTP │ │
│ │ imap 2.4 │ │ lettre 0.11 │ │
│ │ UID-safe │ │ │ │
│ └──────┬──────┘ └──────┬────────┘ │
│ │ │ │
└──────────┼─────────────────────────────┼───────────────────────┘
│ │
▼ ▼
┌──────────────────────────────────────────────────────────────┐
│ Proton Mail Bridge (local) │
│ IMAP 127.0.0.1:1143 SMTP 127.0.0.1:1025 │
│ (STARTTLS / TLS with self-signed cert) │
└──────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ Proton Mail servers │
│ (end-to-end encrypted via Bridge) │
└──────────────────────────────────────────────────────────────┘
| Concern | Approach |
|---|---|
| Authentication | Bridge mailbox password entered once via setup, stored in macOS Keychain (keyring) |
| Config | ~/.config/protonmail-cli/config.toml — host/port/security only, no credentials |
| IMAP safety | All mutating ops use UID variants (uid_store, uid_copy, uid_expunge, uid_mv) — never sequence-number ops that could hit the wrong message |
| Move | Tries RFC 6851 uid_mv first; falls back to copy+flag+expunge when server lacks MOVE |
| Draft replace | APPEND new draft → delete old UID → relocate new UID via HEADER Message-ID search |
| TLS | Accepts self-signed certs (required for Bridge's local proxy) |
| Non-ASCII names | Decodes IMAP Modified UTF-7 (RFC 3501) so Umlauts etc. display correctly |
| Threading | Single-threaded with 20 s poll; no IMAP IDLE thread needed for local Bridge |
- macOS (Keychain integration via
keyring) - Rust toolchain —
rustup,cargo - Xcode Command Line Tools
- Proton Mail Bridge installed, signed in, and running
# Clone
git clone https://github.com/svcho/protonmail-cli.git
cd protonmail-cli
# Build release binary
cargo build --release
# Binary is at
./target/release/protonmail-cli# 1. Configure profile (uses Bridge mailbox credentials, NOT your Proton web password)
./target/release/protonmail-cli setup --profile default
# 2. Verify connectivity
./target/release/protonmail-cli doctor --profile default
# 3. Launch
./target/release/protonmail-cli --profile defaultsetup prompts for:
- Account email
- Bridge username
- IMAP host / port / security (
tls|starttls|plain) - SMTP host / port / security
- Bridge mailbox password (stored in macOS Keychain — never on disk)
Config is saved to ~/.config/protonmail-cli/config.toml.
./target/release/protonmail-cli doctor --profile defaultReports:
- Resolved IMAP / SMTP endpoints with security mode
- IMAP capability flags (MOVE, UIDPLUS, IDLE, ID — warns if MOVE/UIDPLUS absent)
- Per-special-mailbox STATUS: message count + unread count for Sent, Drafts, Archive, Trash, Spam, All Mail
┌─ Mailboxes ──────────────┬─ Inbox (12–51 of 204) ────────────────────────┐
│ Inboxes │ │
│ All Mail │ alice@example.com Hello there Today │
│ Archive │▶ bob@example.com ● Meeting tomorrow 14:22 │
│ Drafts │ noreply@service ★ Your receipt Wed │
│ INBOX 12 │ │
│ Sent │ │
│ Spam ├────────────────────────────────────────────────┤
│ Trash │ From: bob@example.com │
│ │ To: you@proton.me │
│ Folders │ Subject: Meeting tomorrow │
│ Folders/Archive │ Date: Today 14:22 (Sat, 26 Apr 2026 ...) │
│ Folders/Work 3 │ ────────────────────────────────────────────── │
│ Folders/Persönlich │ Hi, │
│ │ Can we meet at 10am? │
│ Labels │ │
│ Gehalt │ │
│ Rechnungen 1 │ │
└──────────────────────────┴────────────────────────────────────────────────┘
↑↓ navigate Enter open Tab switch / search c compose ? help q quit
●= unread,★= flagged- Unread counts shown right-aligned per mailbox
- Dates humanised: Today HH:MM / Yesterday HH:MM / Weekday HH:MM / D Mon / YYYY-MM-DD
| Key | Action |
|---|---|
Tab |
Cycle focus: Sidebar → List → Reader |
↑ / ↓ |
Move selection or scroll reader |
PageUp / PageDn |
Page-scroll reader; prev/next page in list |
Home / End |
Jump to top / bottom of reader |
Enter |
Open selected message or switch mailbox |
n / N |
Jump to next / previous unread message |
] / [ |
Next / previous page (40 messages per page) |
Esc |
Close reader |
q |
Close reader, or quit |
? |
Toggle grouped help overlay |
| Key | Action |
|---|---|
r |
Reply |
R |
Reply-all |
f |
Forward |
m |
Toggle read / unread |
* |
Toggle flagged / unflagged ★ |
a |
Archive (confirms first) |
d |
Delete to Trash (confirms first) |
v |
Move to folder (confirms first) |
s |
Save selected attachment to ~/Downloads |
| Key | Action |
|---|---|
c |
New compose |
Ctrl+S |
Send |
Ctrl+D |
Save draft (replaces previous revision) |
Ctrl+A |
Add attachment by file path |
Tab |
Next field: To → Cc → Bcc → Subject → Body |
Enter |
New line in body; advance field elsewhere |
Esc |
Cancel — prompts: d discard / s save draft / c stay |
Every destructive action (a, d, v, *) shows a confirmation:
| Key | Outcome |
|---|---|
y |
Confirm |
n / Esc / Enter |
Cancel |
Skip all confirmations: PROTONMAIL_CLI_SKIP_CONFIRM=1 protonmail-cli --profile default
Press / to open the search prompt. Tokens are space-separated (implicit AND):
| Token | IMAP equivalent |
|---|---|
from:alice@example.com |
FROM "alice@example.com" |
to:bob@example.com |
TO "bob@example.com" |
cc:carol@example.com |
CC "carol@example.com" |
subject:invoice |
SUBJECT "invoice" |
since:2026-01-01 |
SINCE 01-Jan-2026 |
before:2026-04-01 |
BEFORE 01-Apr-2026 |
unseen |
UNSEEN |
seen |
SEEN |
flagged |
FLAGGED |
unflagged |
UNFLAGGED |
hello world |
TEXT "hello" TEXT "world" |
Example: from:alice subject:invoice since:2026-01-01 unseen
protonmail-cli/
├── src/
│ ├── main.rs — entry point, arg parsing, profile loading
│ ├── cli.rs — setup + doctor commands
│ ├── config.rs — AppConfig / ProfileConfig / ServerConfig (TOML)
│ ├── keychain.rs — macOS Keychain wrapper (keyring crate)
│ └── mail/
│ │ ├── mod.rs
│ │ ├── backend.rs — MailBackend trait + BridgeBackend (IMAP/SMTP impl)
│ │ └── model.rs — Mailbox, MessageSummary, MessageDetail, ComposeDraft, …
│ └── ui/
│ ├── mod.rs
│ ├── app.rs — TUI state machine, event loop, rendering (ratatui)
│ └── theme.rs — colour palette
├── docs/
│ └── manual_bridge_smoke_test.md — nonce-filtered live verification recipe
├── Cargo.toml
└── README.md
doctor fails IMAP login
- Bridge must be running and signed in.
- Use the Bridge mailbox password (shown in Bridge UI), not your Proton web password.
- Re-check IMAP security mode and port in Bridge → Mailbox → Show password.
doctor fails SMTP test
- Re-check SMTP host / port / security in Bridge settings.
- Ensure the account is fully connected (not "connecting…") in Bridge.
No mailboxes shown
- Confirm the mailbox is enabled in Bridge.
- Re-run
setup, thendoctor.
Non-ASCII folder names garbled (e.g. &APY-)
- Update to v0.2.0-alpha.1 or later — IMAP Modified UTF-7 decoding was added.
Confirm prompt on every action
- By design. Use
PROTONMAIL_CLI_SKIP_CONFIRM=1to bypass.
- Credentials never touch disk — Bridge password is stored exclusively in macOS Keychain.
- TLS certificate validation is intentionally disabled for the local Bridge connection (Bridge uses a self-signed cert on
127.0.0.1). Remote SMTP/IMAP connections should use properly validated certs. - All mutating IMAP operations use UID commands (
uid_store,uid_mv, …) to ensure the correct message is modified even under concurrent folder changes.
MIT — see LICENSE if present, or treat as MIT until one is added.