Aerc Proton

Email with Proton Bridge, mbsync, and aerc

I had been using Neomutt with Proton bridge as terminal email setup for awhile and it just wasn’t working for me. After doing some investigation I landed on Aerc as a potential replacement. In the process I made some changes to my bridge setup. Previously I had setup a seperate user to avoid issues with pass with git commit signing and bridge. Now I have a seperate store for bridge. On thing I have disabled is tls between bridge and clients, the threat model tls protects against is irrelevant with communication only on localhost. If an attacker can see local host traffic I have bigger problems.

Proton Bridge

Proton bridge accesses and writes to a secrets store quite frequently. I have commit signing on by default for git and my pass store is a git repo. Given that my gpg key is on a hardware yubikey I end up having to enter my pin quite frequently. Previously this was solved with a seperate user to run bridge, howver this is painful to setup. On my new laptop I created a script to start bridge that uses a seperate per host pass store, this eliminates the signing issue for bridge use. On initial login to bridge run ‘info’ and then use pass to insert the displayed password into your store.

pass local/<hostname>-bridge
# ~/bin/start-bridge.sh
#!/bin/sh
PASSWORD_STORE_DIR=$HOME/.local/share/bridge-pass exec bridge --cli "$@"

Initialize the store before first run:

PASSWORD_STORE_DIR=~/.local/share/bridge-pass pass init <your-gpg-key-id>

Note: use PASSWORD_STORE_DIR=... pass init not pass init --path — the --path flag initializes a subdirectory within an existing store, not a new store at an arbitrary location.

SSL on localhost

Bridge generates a self-signed cert for its local IMAP endpoint. For a localhost-only connection this provides essentially no security benefit — an attacker with local access has bigger problems available to them. Use SSLType None in mbsync for simplicity:

# ~/.mbsyncrc
TLSType None

mbsync

Full config with hostname-based pass lookup

Using $HOSTNAME in the pass path makes the config portable across machines — each machine stores its own Bridge password under local/$HOSTNAME-bridge and the same .mbsyncrc works everywhere:

IMAPAccount proton
Host 127.0.0.1
Port 1143
User youremail@example.com
PassCmd "/usr/bin/pass local/$HOSTNAME-bridge"
TLSType None

IMAPStore protonmail-remote
Account protonmail

MaildirStore protonmail-local
Path ~/.mail/example.com/
Inbox ~/.mail/example.com/Inbox
Flatten .

Channel example.com
Far :protonmail-remote:
Near :protonmail-local:
Patterns * !"All Mail" !Inbox !tmpsearch

aerc

Proton Bridge will create a ‘Recovered Email’ box if emails get lost between the local store and bridge. To avoid this we want to make sure we move emails instead of deleting. To enforce this I enabled the ‘restrict-delete’ option in aerc. This is a per account option so probably need to enable it for gmail or other providers as well. We set smtp+insecure to match mbsyncrc and make setup easier.

accounts.conf

The outgoing URL requires URL-encoding the @ in the email address (the @ separating username from hostname must remain unencoded):

#.config/aerc/accounts.conf
[Proton]
source   = maildir://~/.mail/example.com/
outgoing = smtp+insecure://youremail%40example.com@127.0.0.1:1025
outgoing-cred-cmd = pass local/$HOSTNAME-bridge
check-mail-cmd = mbsync example.com
check-mail-timeout = 30s
default  = INBOX
from     = Your Name <youremail@example.com>
copy-to  = Sent
folders-sort = Inbox, Starred, Sent, Labels.followup, Labels.readlater
restrict-delete = true

aerc.conf — filters and pager

The critical combination for readable HTML email:

# .config/aerc/aerc.conf
#...

[ui]
sort="-r date"

#...

[viewer]
pager = bat --style=plain --theme=ansi

#...

[filters]
text/plain = colorize
text/calendar = calendar
message/delivery-status = colorize
message/rfc822 = colorize
text/html = html2text --ignore-links | sed 's/^/\x1b[39m/'

#...

--theme=ansi is essential — without it bat applies its own color theme which produces invisible text in light terminal themes. ansi defers color decisions entirely to the terminal, making it compatible with both light and dark mode switching.

The sed ANSI reset on each line ensures html2text’s plain text output uses the terminal’s default foreground color rather than whatever the pager happens to have active.

Address book

khard integration for tab-completion in the composer:

# .config/aerc/aerc.conf
#...

[compose]
address-book-cmd = khard email --parsable --remove-first-line %s

#...

Completion is triggered by <C-y> (defined in binds.conf as $complete) like vim.

binds.conf — custom keys

Key customizations made from the defaults:

# .config/aerc/binds.conf
#...

[messages]
# Semantic remapping — m for move, c for compose
O = :check-mail<Enter>
m = :menu -dc fzf :move<Enter>
c = :compose<Enter>

# Delete moves to Trash (safer than expunge with restrict-delete=true)
dd = :read<Enter>:move Trash<Enter>

# Folder navigation with g-prefix
gi = :cf Inbox<Enter>
gr = :cf Labels.readlater<Enter>
gf = :cf Labels.followup<Enter>
gs = :cf Sent<Enter>
gt = :cf Trash<Enter>
gd = :cf Drafts<Enter>

# Fuzzy folder picker via fzf
gf = :menu -c :cf -f 'fzf --layout=reverse'<Enter>

[view]
m = :menu -dc fzf :move<Enter>
dd = :read<Enter>:move Trash<Enter>

fzf folder picker

aerc’s :menu command pipes a list to any external filter and passes the selection back to any command. This gives fuzzy folder selection for both navigation and moving messages:

# Fuzzy move
m = :menu -dc fzf :move<Enter>

# Fuzzy folder switch
gf = :menu -c :cf -f 'fzf --layout=reverse'<Enter>

The -dc flag in :menu -dc fzf :move sets fzf as the default menu command for this invocation.

URL handling with context

aerc’s :pipe sends the raw message source to a command regardless of what filter rendered the view. To get urlscan with readable context from HTML emails, pipe through html2text first:

gx = :pipe sh -c 'html2text --ignore-links | urlscan'<Enter>

Light/dark mode compatibility

The full terminal color inheritance chain requires every tool to defer color decisions upward:

With these in place, aerc inherits from the terminal, which inherits from your Sway/Ghostty theme toggle. One toggle, everything follows.


Security considerations

What the Bridge architecture protects

What it doesn’t protect

Per-device Bridge vs centralized server

Running Bridge on each device (rather than a homelab server) keeps decrypted mail off always-on network-accessible machines. Manual sync via mbsync reinforces this — mail only syncs when you’re present and intentional.


Dependencies

protonmail-bridge
isync (mbsync)
aerc
pass
html2text (python-html2text)
bat
urlscan
khard
fzf