I use email in the terminal with a combination of:
- protonmail-bridge-cli
- local IMAP server to fetch Protonmail mails
- tmux
- to run protonmail-bridge in
- isync (or offlineimap etc)
- to sync IMAP with maildirs
- notmuch
- to tag and search mails
- afew
- to move mails found by certain notmuch queries in certain maildirs
- alot
- to read, compose, search and tag mails
- msmtp
- to send mails
- w3m (or lynx etc)
- to read HTML mails in the terminal
- rsync
- to copy archived mails
- a shell function and an alias
- to make it all less ridiculous
To most, Thunderbird will be suitable, the target audience here is unreasonable people.
Key takeaways about notmuch
notmuch sort of doesn't care where your messages are stored, the folder is just one of the parameters you can search by.
notmuch sort of doesn't care about individual messages, it cares about threads. This is good because if you trashed all the messages in a thread and a new reply comes in, the previous messages in the thread will come along. Also, folders are usually not really folders, they're more like tags, so we might as well treat them as such.
Here are the principles or rules:
- Anything in Protonmail will get the pm tag,
- if it's in the inbox, it will get the inbox tag,
- if it's in the mailings folder, it'll get the mailings tag.
- anything in gmail gets the gmail and also the mailings tag, because basically I don't ever get real email from people in gmail.
- to archive a message in the server, which is an option I only allow for Protonmail, I remove the mailings tag or the inbox tag.
- to archive a message locally, taking it off the server, I add the archive tag.
- to move a message to the trash, I add the killed tag.
These are the tags that have an impact in moving mails from one maildir to another, I can also use any other tags just for organizing mails as I please.
Key takeaway about using Protonmail-bridge
When you delete a message from Protonmail via IMAP, it sits in a sort of "root" folder, a folder inaccessible other than via the All Mail folder in the web app and mobile app. To properly delete a message from Protonmail, unfortunately, you need to move it to the trash (meaning you have to sync the trash), and then delete it in the app. There is no way to properly delete it via IMAP, even if you move it to the trash, sync it, then delete it and sync it, you will find the message floating in your All Mail folder.
Snippets
~/.config/notmuch/default/hooks/post-new
#!/usr/bin/zsh
echo "Running afew lists filter."
afew -nt
echo "Tagging new mails."
cat <<-EOF | notmuch tag --batch
+pm +mailings -new -- folder:pm/mailings AND tag:new
+pm +inbox -new -- folder:pm/inbox AND tag:new
+pm -new -- folder:pm/archive AND tag:new
+pm +sent -new -- folder:pm/sent AND tag:new
+gmail +mailings -new -- folder:gmail/inbox
EOF
echo "Untagging some archived mails."
notmuch tag -inbox -mailings -- tag:archive AND \(tag:inbox OR tag:mailings\)
Any message in gmail will have the mailings tag, always, if I delete it, it'll just be re-added.
~/.zshrc (or your shell rc file)
alias pmb="/usr/bin/tmux new-session -d -s mail '/usr/bin/protonmail-bridge --cli'"
syncmail () {
echo "Archiving mails (if any)..."
/usr/bin/notmuch search --format=text0 --output=files \
\
tag:archive AND NOT folder:local/archive | rsync -v0 --no-R --files-from=- / /home/user/.mail/local/archive/new
echo "Moving mails..."
/usr/bin/afew -vam
echo "Syncing mailboxes..."
/usr/bin/mbsync -a
echo "Updating database..."
/usr/bin/notmuch new
}
alot () {
if [[ $1 == "-x" ]]
then
echo "text/html; qutebrowser %s; nametemplate=%s.html; copiousoutput" > ~/.mailcap
else
echo "text/html; w3m -dump -o -document_charset=%{charset} %s; nametemplate=%s.html; copiousoutput" > ~/.mailcap
fi
command alot
}
The bad thing about the qutebrowser option is that for example if you're looking at a thread, you'll open one tab for each message in the thread. Not such a big deal, because usually emails that are in a thread will be better read in the terminal.
~/.config/afew/config
[ListMailsFilter]
[MailMover]
folders = pm/inbox pm/sent pm/mailings pm/archive pm/trash gmail/inbox
rename = True
pm/inbox = 'tag:killed OR tag:archive':pm/trash 'NOT tag:inbox':pm/archive
pm/sent = 'tag:killed OR tag:archive':pm/trash
pm/mailings = 'tag:killed OR tag:archive':pm/trash 'NOT tag:mailings':pm/archive
pm/archive = 'tag:killed OR tag:archive':pm/trash
pm/trash = 'NOT tag:killed AND NOT folder:local/archive':pm/inbox
gmail/inbox = 'tag:killed':local/trash
In summary, if I tag something killed, move it to the trash. If I untag inbox or mailings, archive it in Protonmail. If a message has the archive tag, it'll have been copied to the local archive folder in the first operation within the syncmail function, so move this copy to the trash folder, so that it can later be deleted in Protonmail's apps.
~/.config/alot/config
terminal_cmd = 'kitty'
[accounts]
[[protonmail]]
realname = HPR Listener
address = hprlistener@pm.me
#gpg_key = ABCDEFGH
sendmail_command = msmtp --account=protonmail -t
sent_box = maildir:///home/user/.mail/pm/sent
draft_box = maildir:///home/user/.mail/local/drafts
[[work-email]]
realname = Corporate Drone
address = drone@acme.com
#gpg_key = ABCDEFGH
sendmail_command = msmtp --account=corporate -t
sent_box = maildir:///home/user/.mail/pm/sent
draft_box = maildir:///home/user/.mail/local/drafts
[[[abook]]]
type = shellcommand
command = khard email --parsable
regexp = '^(?P<email>[^@]+@[^\t]+)\t+(?P<name>[^\t]+)'
ignorecase = True
[bindings]
g m = search tag:mailings AND NOT tag:killed
g i = search tag:inbox AND NOT tag:killed
g u = search tag:unread AND NOT tag:killed
g v = search tag:view
A = toggletags archive
M = toggletags mailings
I = t
& = toggletags killed
U = toggletags unread
V = toggletags view
s =
a =
~/.config/notmuch/default/config or ~/.notmuchrc
[database]
path=/home/user/.mail
[user]
name=HPR Listener
primary_email=hprlistener@pm.me
other_email=hprcontributor@pm.me;winniethepooh@gmail.com;
[new]
tags=new
ignore=
[search]
exclude_tags=deleted;spam;
[maildir]
synchronize_flags=true
~/.mbsyncrc
IMAPStore pm-remote
Host 127.0.0.1
Port 1143
User hprlistener@pm.me
PassCmd "pass Protonmail-Bridge"
SSLType STARTTLS
CertificateFile ~/.config/protonmail/bridge/cert.pem
PathDelimiter /
MaildirStore pm-local
Path ~/.mail/pm/
Inbox ~/.mail/pm/inbox
SubFolders Verbatim
Channel pm-inbox
Far :pm-remote:INBOX
Near :pm-local:inbox
SyncState *
Channel pm-mailings
Far :pm-remote:Folders/mailings
Near :pm-local:mailings
SyncState *
Channel pm-archive
Far :pm-remote:Archive
Near :pm-local:archive
SyncState *
Channel pm-sent
Far :pm-remote:Sent
Near :pm-local:sent
SyncState *
Channel pm-trash
Far :pm-remote:Trash
Near :pm-local:trash
Sync PushNew
SyncState *
IMAPStore gmail-remote
Host imap.gmail.com
User hprlistener@gmail.com
PassCmd "pass Google/IMAP/hprlistener@gmail.com"
AuthMechs LOGIN
SSLType IMAPS
SSLVersions TLSv1.3
PathDelimiter /
MaildirStore gmail-local
Path ~/.mail/gmail/
Inbox ~/.mail/gmail/inbox
Trash trash
SubFolders Verbatim
Channel gmail-inbox
Far :gmail-remote:INBOX
Near :gmail-local:inbox
Expunge Both
SyncState *
~/.config/msmtp/config
# Set default values for all following accounts.
defaults
logfile ~/.msmtp.log
# Protonmail
account protonmail
host 127.0.0.1
port 1025
from user@pm.me
user hprlistener@pm.me
passwordeval "pass Protonmail-Bridge"
auth plain
tls on
tls_starttls on
tls_trust_file /home/user/.config/protonmail/bridge/cert.pem
account corporate
host imap.acme.com
port 1025
from drone@acme.com
user drone@acme.com
passwordeval "pass ACME/IMAP"
# Set a default account
account default : protonmail
a few things that I forgot to say in the show and didn't want to interject
- Another bad thing about protonmail-bridge is that it needs to use either the atrocious GNOME keychain, or pass to temporarily save secrets, this means your pass git history will be littered with changes made each time you start and stop the protonmail bridge. In https://github.com/ProtonMail/proton-bridge/issues/31 someone had the brilliant idea to add protonmail-credentials to .gitignore in your pass repo.
- Another annoying thing is that some mailing lists will show a From field like "'<sender>' via <mailinglist>". Protonmail's mobile app (and probably web app too) will not show this, showing just the name of the mailing list, so you don't know who actually sent it. This is despite the actual original sender's email address being available in an email header.
- When messages are first ingested by
notmuch new
, they receive thenew
tag and then we use that to run a bunch of notmuch tag statements in the notmuch post-new hook script, found above, to add other tags depending what folder the messages are in. Here you could add whatever else you want likenotmuch tag +receipt -- tag:new AND subject:/receipt/
(the slashes enclose a regex). Tagging with the names of the folders the messages are in can also be achieved with a built-in filter from afew. - Speaking of mailing lists, you'll see in the afew config the
[ListMailsFilter]
, which adds tagslists
andlists/<name-of-list>
for mails that come from mailing lists. This is based on an email header that will usually be present but will not always be meaningful. The HPR mailing list gets taggedlist/hpr
, but a lot of other mails will get tagged with human-hostile names likelist/ch2038cn20398cn2309c8h2308ch
. - The notmuch database can be exported into an easy plain text file for back up (notmuch-dump) and restore (notmuch-restore).
- There is the option for notmuch to synchronize IMAP tags (represented in maildir by certain characters at the end of the file name). What any specific IMAP client or server does with them is anyone's guess. The "seen" maildir tag (S) works fine with mbsync and protonmail-bridge. See
man notmuch-config
, under maildir.synchronizeflags. notmuch
understands that when the same message is in 2 different folders, they're the same message. It doesn't show it twice, unless you run a search with the "files" output option, in which case it will list every copy of it.- For
alot
to send replies from the same email address that received the mail you're replying to, you must configure each of your accounts in your alot config. alot
features related to mailing lists have not worked for me. I can't get thereply --list 1
command to do anything different than justreply
.- Someone has written a command to open a given message in a graphical web browser, but I haven't looked into it. It would be nice to have it. See https://github.com/pazz/alot/wiki/Contrib-Hooks#open-html-emails-in-external-browser.
Links
- Article about password managers by Tavis Ormandy. No license information posted on the site, I'm going to say my reading the excerpt was fair use: https://lock.cmpxchg8b.com/passmgrs.html
- notmuch: https://notmuchmail.org/
- alot: https://github.com/pazz/alot
- afew: https://github.com/afewmail/afew
- msmtp: https://marlam.de/msmtp/
- protonmail-bridge: https://protonmail.com/bridge/
- mbsync: https://isync.sourceforge.io/mbsync.html
- w3m: http://w3m.sourceforge.net/
- qutebrowser: https://www.qutebrowser.org
- tmux: https://github.com/tmux/tmux/wiki
- rsync: https://rsync.samba.org/