Unofficial, agent-native CLI for Proton Mail Bridge — search, read, send, and organize mail through the local IMAP/SMTP gateway. Multi-account, JSON output for AI agents, robust from WSL against a Windows-hosted bridge. Not operated by Proton.
uv tool install git+https://github.com/evrenverse/proton-mail-bridge-cli
pmb account add # wizard: host/ports + email + bridge password, tests the login
pmb --json message search --text "invoice" --since 2026-01-01 --with-body
pmb --json message search --attachment-text 26593328 --since 2026-01-01 # inside the PDFsProton Mail Bridge must be running. The bridge password (≠ your Proton password) is shown
in the Bridge under info. Short alias: pmb = proton-mail-bridge.
You can hand this block to an agent as-is ("here's the repo, install it"):
# 1. Install (requires uv: https://docs.astral.sh/uv/)
uv tool install git+https://github.com/evrenverse/proton-mail-bridge-cli
# 2. Check connectivity — Proton Mail Bridge must be running
pmb --json bridge doctor
# 3. Set up an account (non-interactive). The bridge password (≠ Proton password)
# is shown in the Bridge under `info` — ask the human for it, never guess.
pmb account add-raw --email you@proton.me --password '<bridge-password>'
pmb --json account test
# 4. Install the agent skill into the project (docs + workflows for the agent)
pmb skill install --agent claude # or --agent codexOperating rules (details in the skill):
- Always
--json, bulk-first (one task = 1–3 calls). - Multi-account: without
--account,message search/listfan out over all accounts. - Token-efficient:
message search --ids-only(pipelines),--count-only(count questions). - Discovery:
pmb --help,pmb describe <path...>(e.g.describe account identity add),pmb fields message. - Write operations:
--dry-runfirst (every writing command has it), never pass--yeson your own. - Read
search.truncated:--limitbounds the matches, and a result that had to stop early says so instead of looking complete.
pmb --json message senders --min-count 20 # who sends the volume (headers only)
pmb --json message search --list-unsubscribe --limit 0 # everything with an unsubscribe link
pmb --json --account you@proton.me message bulk-delete --all-folders \
--from newsletter@example.com --dry-run # preview, one entry per folderbulk-move --dest F and bulk-delete take the same selection options as search, run folder
by folder and skip All Mail (read-only duplicate view). --log FILE writes one JSON line per
deleted message — the only record that survives --expunge.
List-Unsubscribe is a bulk-sender signal, not proof of advertising: project boards and
portals set it on notifications that matter. Selection criterion, never an auto-delete rule.
Env vars (PROTON_BRIDGE_HOST/IMAP_PORT/SMTP_PORT/USER/PASS/ACCOUNT) or a config file
(~/.config/proton-mail-bridge/config.toml, Windows %APPDATA%). Template: config.example.toml.
macOS: the Bridge often runs SMTP in SSL mode (IMAP stays STARTTLS). pmb account add
detects this automatically; after the fact: pmb bridge config --smtp-security ssl
(diagnosis: pmb bridge doctor).
A Proton account can own several addresses. With the Bridge in combined-addresses mode a single login covers all of them:
pmb --json account identity discover # preview: senders found in Sent
pmb --json account identity discover --save # write them into the config
pmb account identity set-default kontakt@proton.me # default sender for this account
pmb --json compose send --identity kontakt@proton.me --to a@x.de --subject S --body B --dry-rundiscover fills in email/name only — it never invents labels. Labels come from
account identity add --label (or editing the config file); use the address with
--identity/set-default otherwise. reply and forward answer from the address the
original mail was sent to.
The Bridge is a plain SMTP relay: it forwards the message it is handed and never adds the signature configured in the Proton apps — that one is inserted by Proton's own composers only. So it has to live in a local file per identity:
pmb --json account identity signature import --identity kontakt # preview
pmb --json account identity signature import --identity kontakt --save # write + linkimport reads back through the Sent folder (--scan, 25 messages by default) until it finds
one that a Proton composer sent — mail sent through this CLI carries no signature and is
exactly what sits at the top of Sent. It then writes kontakt.sig (plus kontakt.sig.html)
next to config.toml and points the identity at them. Only Proton's own
protonmail_signature_block counts; when an address has no signature configured, Proton
marks the block empty and the command says so instead of guessing. Writing the files by hand
works just as well:
identities = [
{ email = "kontakt@proton.me", label = "kontakt",
signature_file = "kontakt.sig", signature_html_file = "kontakt.sig.html" },
]Relative paths resolve against the config directory. Every compose command appends the
signature of the sending identity; --no-signature sends without it, and the HTML signature
is used only when the mail actually has an HTML part (--html-file).
127.0.0.1 is tried first (works with WSL in mirrored networking mode, native macOS,
Windows, and Linux without any special path). If that fails inside WSL, the CLI automatically
probes the Windows host IP (gateway/nameserver). Diagnosis: pmb bridge doctor.
Write operations are risk-tiered (🟢/🟡/🔴) and every one of them takes --dry-run:
pmb --json --account you@proton.me message delete --uid 12,13,14 --dry-runThe dry run resolves the whole operation and prints it — for UID operations including one entry
per message (uid, date, sender, subject, size, flags), the resulting risk tier, and the UIDs the
folder does not hold — then stops. It changes nothing, not even \Seen (headers are fetched
with BODY.PEEK), and it needs no confirmation, because there is nothing to confirm. That makes
it the way to inspect a 🔴 operation that would otherwise demand a terminal.
Read-only commands (search, read, list, mailbox list, …) have no --dry-run; neither
does account identity discover, which without --save already is the preview.
TLS against the self-signed bridge certificate is unverified by default (loopback/trusted host);
pin it via tls_cert_path in the config file. See SECURITY.md.
Unofficial community client, not operated by Proton. Apache-2.0.