Skip to content

svcho/protonmail-cli

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

protonmail-cli

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.


Architecture

┌─────────────────────────────────────────────────────────────────┐
│                        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)               │
└──────────────────────────────────────────────────────────────┘

Key design decisions

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

Requirements

  • macOS (Keychain integration via keyring)
  • Rust toolchainrustup, cargo
  • Xcode Command Line Tools
  • Proton Mail Bridge installed, signed in, and running

Install & Build

# 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

First-Time Setup

# 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 default

setup 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.


Doctor

./target/release/protonmail-cli doctor --profile default

Reports:

  • 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

TUI Layout

┌─ 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

Keyboard Reference

Navigation

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

Reading & Actions

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

Compose

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

Confirmation prompts

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


Structured Search

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


Project Structure

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

Troubleshooting

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, then doctor.

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=1 to bypass.

Security notes

  • 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.

License

MIT — see LICENSE if present, or treat as MIT until one is added.

About

A keyboard-driven terminal mail client for Proton Mail Bridge, written in Rust.

Topics

Resources

Stars

Watchers

Forks

Contributors

Languages