This note documents my current setup for email.
It includes:
- aerc as an email client
- vdirsyncer for syncing contacts
- khard for reading and searching locally-synced contacts
- mbsync for syncing email to a local Maildir
- notmuch for organising and searching locally-synced email
Pre-start: Handling local credentials
To securely store credentials for syncing, I use pass
. I recommend installing this for your system and setting it up with a GPG key.
One issue I had whilst using pass
was that it would frequently timeout, and the GPG dialog would fail to work or would interfere with the running mail client. As such, I extended the TTLs of the agent by adding these lines to my ~/.gnupg/gpg-agent.conf
:
default-cache-ttl 34560000
max-cache-ttl 34560000
Once set-up, add the needed credentials for your various mail accounts to pass
. For some services (e.g. Fastmail and Google) an app-specific password should be generated for this. E.g.:
pass insert Email/fastmail
Step 1: Managing contacts (vdirsyncer
and khard
)
I use vdirsyncer
to sync contacts to a local directory. This allows khard
to read and search contacts locally and surface them to aerc
.
To install both of these, use Homebrew on a Mac: brew install vdirsyncer khard
.
vdirsyncer
setup
Once installed, create a new configuration file for vdirsyncer at ~/.config/vdirsyncer/config
:
[general]
status_path = "~/.config/vdirsyncer/status/"
# Personal Contacts
[pair personal_addressbook]
a = "personal_addressbook_local"
b = "personal_addressbook_remote"
collections = ["from a", "from b"]
metadata = ["displayname"]
[storage personal_addressbook_local]
type = "filesystem"
path = "~/.config/khard/personal/"
fileext = ".vcf"
[storage personal_addressbook_remote]
type = "carddav"
auth = "basic"
url = "https://carddav.fastmail.com/dav/addressbooks/user/USERNAME@fastmail.com/Default"
username = "USERNAME@fastmail.com"
password.fetch = ["command", "pass", "Email/fastmail"]
This syncs my Fastmail (i.e. personal, non-work) contacts to ~/.config/khard/personal/
. The final three blocks of the config file (the pair
and two storage
blocks) can be repeated for each account you wish to sync contacts for.
I use it with both Fastmail and Google, and it works well. For Google contacts, the url
I use is: https://www.googleapis.com/carddav/v1/principals/USERNAME@DOMAIN.COM/lists
.
khard
setup
For khard
, create a new config file at ~/.config/khard/khard.conf
with contents along the lines of the example on the website. Specifically, for the [addressbooks]
section, I use the ~/.config
dir primarily out of laziness, as this auto-syncs with my dotfiles.:
[addressbooks]
[[personal]]
path = ~/.config/khard/personal/Default
[[work]]
path = ~/.config/khard/work/default
...
In the latter example, the default
is lowercase as this is how Google names its default address books.
Testing contacts
To test this is all working, run a sync and then try searching for a contact:
vdirsyncer sync
khard list john
If you have a contact named John, you should see them listed.
Step 2: Syncing email (mbsync
)
Although aerc
can directly connect to IMAP servers, I prefer to have mail synced locally. This allows me to manage it using local tools (such as notmuch
) but it also enables me to back things up more easily.
I use mbsync
for syncing IMAP accounts to a local Maildir directory. It is part of a package called isync
and can be installed with Homebrew: brew install isync
.
To set it up, create a file at ~/.mbsyncrc
. I find the mbscync
config file fiddly and unintuitive, but have set my personal Fastmail IMAP account up as follows. These blocks can be repeated for your other IMAP accounts (just ensure to use unique names for the accounts, stores, and channels).
IMAPAccount personal
Host imap.fastmail.com
Port 993
User USERNAME@fastmail.com
PassCmd "pass Email/fastmail"
TLSType IMAPS
AuthMechs LOGIN
IMAPStore personal-remote
Account personal
MaildirStore personal-local
Path ~/Syncthing/Maildir/personal/
Inbox ~/Syncthing/Maildir/personal/INBOX
SubFolders Verbatim
Channel personal
Far :personal-remote:
Near :personal-local:
Patterns *
Expunge None
CopyArrivalDate yes
Sync All
SyncState *
Create Both
You may notice that I reference a Maildir in my Syncthing directory. This is to ensure my mail is backed-up to other machines.
To test this is working, run mbsync -a
and check the Maildir directory for your account. You should see your mail syncing.
Step 3: Organising email (notmuch
)
notmuch
is a tool I use for parsing and searching email in a local Maildir. It’s super fast and works well with aerc
.
To install it, use Homebrew (on a Mac): brew install notmuch
.
To set it up, run notmuch
. This will prompt for a few bits of info and setup a database at the root of your Maildir and a configuration file at ~/.notmuch-config
.
The defaults for the config are pretty good, but for reference I add mine below:
[database]
# Path to your Maildir
path=/Users/will/Syncthing/Maildir
[user]
# I don't think this affects much unless you use notmuch for sending mail too
primary_email=USER@DOMAIN.COM
other_email=email1;email2;
[new]
# default tags for new messages
tags=unread;inbox;
ignore=
[search]
# how to manage searches (i.e. ignore deleted/spam mail)
exclude_tags=deleted;spam;
[maildir]
# sync some notmuch tags with maildir flags (e.g. read/unread)
synchronize_flags=true
I also ensure to add the following to ~/Syncthing/.stignore
to prevent Syncthing from syncing the notmuch
database (which is huge, but can be rebuilt):
.notmuch/xapian
notmuch hooks can be set up to run commands alongside notmuch
. For example, to add/remove tags before or after mail is processed.
My approach is simple, and I just have a single post-new
hook in ~/Syncthing/Maildir/.notmuch/hooks/post-new
to make my mail management a little easier:
#!/bin/bash
# Reference Maildir folders for tagging
notmuch tag +personal "folder:/personal/"
notmuch tag +account2 "folder:/account2/"
# Handle sent and spam mail by IMAP folders
# e.g. if Gmail marks it as spam, tag it as spam
notmuch tag +sent "folder:/Sent/" and not tag:sent
notmuch tag +spam "folder:/Spam/" and not tag:spam
# Handle customer specific tags
notmuch tag +customer1 from:customer1.com
notmuch tag +customer2 from:customer2.com and tag:personal
# Remove the new tag from all new emails
notmuch tag -new tag:new
(Note: after creating this file for the first time, be sure to ensure it’s executable: chmod +x ~/Syncthing/Maildir/.notmuch/hooks/post-new
)
Because our config file auto-applies the unread
tag, we don’t need to handle it here, but we could if we wanted to.
If you now run notmuch new
, the Maildir will be processed according to the config and any hooks you’ve setup.
You can now try running a search to show how fast it runs:
notmuch search from:domain1.com
notmuch search tag:personal
Step 4: Setting-up aerc
I now talk about installing and setting-up aerc
, which will bring all of this together into one place.
Installation
Although it’s installable from Homebrew, the distributed binary does not include notmuch
support. As such, I compile aerc
from source using the headers installed as part of the notmuch
installation from earlier.
First, make sure to have go
installed (according to the website).
Clone the aerc
repo and change into it:
git clone https://git.sr.ht/~rjarry/aerc
cd aerc
Set some flags so the build process knows where to find the notmuch
headers and so the linker can find the library. In my case (and versions), this was done using:
export CPATH=/opt/homebrew/Cellar/notmuch/0.38.3/include
export LIBRARY_PATH=/opt/homebrew/Cellar/notmuch/0.38.3/lib
Now compile aerc
with the notmuch
tag:
gmake GOFLAGS=-tags=notmuch
Mail sync script
To help automate some of the mail fetching and tagging tasks, I added a script (largely taken from the example on the aerc wiki). This script will be invoked whenever aerc
wants to fetch new mail (as we’ll set up later):
#!/bin/sh
MBSYNC=$(pgrep mbsync)
NOTMUCH=$(pgrep notmuch)
if [ -n "$MBSYNC" -o -n "$NOTMUCH" ]; then
echo "Already running one instance of mbsync or notmuch. Exiting..."
exit 0
fi
# Actually delete the emails tagged as deleted
notmuch search --format=text0 --output=files tag:deleted | xargs -0 --no-run-if-empty rm -v
mbsync -a
notmuch new
I save this in ~/.config/aerc/mail-sync
. As above, ensure this script is executable: chmod +x ~/.config/aerc/mail-sync
.
Accounts setup
The aerc
accounts setup is highly flexible. The man page (like this Arch Wiki one) is a good reference.
Below is a sample of mine (in ~/.config/aerc/accounts.conf
), which demonstrates notmuch
and khard
integration:
[Combined]
source = notmuch://~/Syncthing/Maildir
query-map = ~/.config/aerc/map.conf
default = INBOX
from = Will Webberley <USERNAME@DOMAIN.COM>
check-mail-cmd = ~/.config/aerc/mail-sync
check-mail = 2m
check-mail-timeout = 30s
[Personal]
source = notmuch://~/Syncthing/Maildir
outgoing = smtps://USERNAME%40fastmail.com@smtp.fastmail.com:465
maildir-store = ~/Syncthing/Maildir
maildir-account-path = personal
outgoing-cred-cmd = pass Email/fastmail
check-mail-cmd = mbsync personal && notmuch new
check-mail = 2m
default = INBOX
from = Will Webberley <USERNAME@DOMAIN.COM>
cache-headers = true
copy-to = Sent
address-book-cmd = khard email -a personal --parsable --remove-first-line %s
[Work1]
source = notmuch://~/Syncthing/Maildir
... etc.
This setup includes a Combined account which is optional but I like it for having a single view across all my accounts. You can also see this account invoking the mail-sync
script we created earlier.
The khard
integration offers auto-complete suggestions as you type a name or email address into the mail composer.
The Combined account also makes use of a query map which maps “folders” onto notmuch
queries. My ~/.config/aerc/map.conf
looks like this:
Inbox=tag:inbox
Todo=tag:todo and not tag:archived and not tag:deleted
All=not tag:archived and not tag:deleted and not tag:spam
Sent=tag:sent
Spam=tag:spam
General aerc
configuration
I created a ~/.config/aerc/aerc.conf
file to set some general options. This file is also hugely configurable. Mine is very basic by comparison:
[general]
default-save-path=~/Downloads
log-file=~/.aerc.log
[ui]
styleset-name=nord
fuzzy-complete=true
icon-new=✨
icon-attachment=📎
icon-old=🕰️
icon-replied=📝
icon-flagged=🚩
icon-deleted=🗑️
[statusline]
[viewer]
alternatives=text/plain,text/html
header-layout=From,To,Cc,Bcc,Date,Subject
[compose]
header-layout=From,To,Cc,Subject
reply-to-self=false
empty-subject-warning=true
no-attachment-warning=^[^>]*attach(ed|ment)
[multipart-converters]
text/html=pandoc -f markdown -t html --standalone
[filters]
text/html=pandoc -f html -t plain
text/plain=colorize
text/calendar=calendar
message/delivery-status=colorize
message/rfc822=colorize
.headers=colorize
[openers]
[hooks]
[templates]
An interesting part of this is the filter for HTML. By default aerc
cannot display HTML emails. This makes emails impossible to view if there is no text/plain
alternative. In my example I use pandoc
to convert HTML to plain text, which works OK for me. Other solutions might involve w3m
or other tools.
The multipart-converters
section allows for specifying how non-plain parts of the email can be generated. Once an email is written (in text/plain
) an HTML part can then be generated by running :multipart text/html
when in review mode.
Running aerc
aerc
can now be run: /path/to/aerc -C ~/.config/aerc/aerc.conf -A ~/.config/aerc/accounts.conf
.
Run :check-mail
in the Combined account to check that’s working, try sending mail, check out the autocomplete of contacts via Khard, and try searching for emails.
For day-to-day use, I strongly recommend reading through the aerc
wiki and man pages, and also experimenting with the setups published by other people.