Motivation
Email is tedious. Especially at work I get hundreds of emails every day, which need to be managed somehow. A lot of those emails are noise, and ideally I’d only ever have to worry about emails that are signal.
Additionally, writing email requires a good editor with vim bindings (duh!), spacemacs delivers. Navigating, tagging, and searching emails with modal keybindings also makes dealing with them less annoying.
This documents my setup, which uses spacemail, isync
, msmtp
, and notmuch
.
Setup
isync/mbsync
isync is a command line tool that syncs a remote IMAP account and a local mail directory. I simply run it periodically to poll for new emails. My fork supports the macOS Keychain for password retrieval.
My ~/.mbsyncrc
looks like this:
IMAPAccount forsooth
Host imap.forsooth.org
User or
KeychainName <KeychainName>
KeychainAccount <KeychainAccount>
SSLType IMAPS
AuthMechs LOGIN
IMAPStore forsooth-remote
Account forsooth
MaildirStore forsooth-local
Subfolders Verbatim
# The trailing "/" is important
Path ~/Mail/or@forsooth.com/
Inbox ~/Mail/or@forsooth.com/INBOX/
Channel forsooth
Master :forsooth-remote:
Slave :forsooth-local:
Patterns "INBOX"
Create Slave
SyncState *
This syncs everything in the folder “INBOX”. I leave everything serverside in that folder, because it avoids moving emails around physically and syncing back and forth with IMAP. All the tagging is done locally via notmuch
.
In order to sync multiple folders, the list can be extended to include them, it also supports regular expressions. Either way the Calendar folder of Exchange accounts should be ignored, as Exchange does some non-RFC things that might break the sync (at least that used to happen to me with offlineimap
, I haven’t seen it with mbsync
).
For my work account I also require a specific SSL certificate for the handshake, and the auth configuration differs a little.
CertificateFile "/usr/local/etc/openssl/certs/some-authority.pem"
Credentials via macOS Keychain (recommended)
My config uses
KeychainName
andKeychainAccount
to retrieve the password from the macOS Keychain. This requires Keychain support added in my fork, see above, until it is merged upstream (perhaps).Credentials via GPG (discouraged)
An alternative to the Keychain is retrieving the password for the account from
~/.authinfo.gpg
, an encrypted file containing login information for SMTP and IMAP:machine imap.forsooth.org login or@forsooth.org port 465 password <hunter2> machine imap.forsooth.org login or@forsooth.org port 993 password <hunter2>
In that case you’d have to set a password command like this:
PassCmd "gpg --quiet --for-your-eyes-only --no-tty --decrypt ~/.authinfo.gpg"
Note that anyone can do this, if they have access to your machine while you are logged in, if you use gpg-agent to avoid constant password prompts. The Keychain method is more secure.
Migrate from offlineimap
If you started with offlineimap and want to switch to mbsync
, then that’s easy, just follow the steps outlined here.
notmuch
notmuch processes a local mail directory, building a database that tags and indexes all mails for text searches.
My ~/.notmuch-config
is given below, each setting is described in detail in the notmuch documentation and the template for that file.
[database]
path=/home/or/Mail
[user]
name=or
primary_email=or@forsooth.org
[new]
tags=unread;inbox;new;
ignore=
[search]
exclude_tags=deleted;spam;
[maildir]
synchronize_flags=true
[crypto]
gpg_path=gpg
Importantly there is the setup of the user that sends the email and the tags new mail gets automatically.
Here is the rough structure of the script to process new email, ideally running after offlineimap syncs the mail.
# find out how many threads are unread before the sync
previous_unread_count=$(notmuch count --output=threads tag:unread)
notmuch new
# count new and unread email after the sync
new_count=$(notmuch count tag:new)
unread_count=$(notmuch count --output=threads tag:unread)
# if something changed...
if [[ "$new_count" -gt 0 && \
"$unread_count" -gt 0 && \
"$unread_count" -ne "$previous_unread_count" ]]; then
# use $new_count and $unread_count for some notification, omitted here
fi
# remove the "new" tag from all emails that have it
notmuch tag -new tag:new
tagging rules
The main script containing all the rules is ~/Mail/.notmuch/hooks/post-new
, which may look like this:
notmuch tag +sent folder:sent
notmuch tag +draft folder:drafts
notmuch tag +me tag:new to:or@forsooth.org
notmuch tag +cron tag:new from:"Cron Daemon"
notmuch tag +invitation-response tag:new 'vcalendar AND (subject:"accepted" OR subject:"declined" OR subject:"tentative")'
notmuch tag +ticket tag:new 'subject:ticket'
notmuch tag +mailing-list tag:new some-topic@mailinglist.com
notmuch tag +build tag:new from:build-tools@some-service.com
notmuch tag +review tag:new subject:"Code Review"
notmuch tag +cake tag:new cake
notmuch tag +noise -inbox -unread tag:new AND tag:build AND NOT tag:me AND "successfully"
notmuch tag +noise -inbox -unread tag:new AND tag:invitation-response
notmuch tag +noise -inbox -unread tag:new AND tag:cron
notmuch tag +noise -inbox -unread tag:new AND tag:review AND NOT tag:me
notmuch tag -inbox tag:new AND tag:mailing-list
notmuch tag -inbox -unread from:or@forsooth.org AND NOT to:or@forsooth.org
notmuch tag +muted "$(notmuch search --output=threads tag:muted)"
notmuch tag +noise -inbox -unread tag:new AND tag:muted
notmuch tag -unread -inbox tag:new AND tag:sent
notmuch tag +signal tag:inbox
This is a normal bash script, which allows some pretty cool rules that’d be hard or impossible to apply in the usual email clients.
In the example config I always filter for mails with the tag new
, which makes the processing much faster. Additional tags are applied based on several rules, which identify them as coming from cronjobs or a build service. Many such emails don’t have to be read, so they get the inbox
tag removed right away. I also give them a noise
tag and the remaining emails a signal
tag for some statistics later on.
You may wonder why inbox
and unread
tags are used in the way they are. I usually filter for tag:inbox AND tag:unread
in spacemacs, as you’ll see in the next section, but they both serve a different purpose:
- inbox - the email is in the inbox and probably needs some action
- unread - the email is worth looking at but hasn’t been read yet
An example is the mailing-list
tag, which keeps an email’s unread
tag, but removes inbox
, because usually there’s a lot of traffic and I just read through them, without expecting each of them to require a response or work.
Anything with both tags will show up in the inbox, after reading it will lose the unread
tag, but remain in the inbox
until it is archived explicitly.
Note that any email in a thread given the tag muted
will also get muted and removed from inbox
.
Another example of how this scripting can do a bit more than just tagging is the following block, which pages me whenever an email mentions cake in the office.
cake_query="tag:new AND tag:cake AND tag:unread AND NOT from:or@forsooth.org"
num_cake=$(notmuch search $cake_query | wc -l | awk '{print $1}')
if [[ "$num_cake" > 0 ]]; then
cake_email="/tmp/cake_email.txt"
cat > $cake_email <<EOF
From: pager@forsooth.org
To: pager@forsooth.org
Subject: "${num_cake}x" cake!
There's cake!
EOF
sendmail -t < $cake_email
fi
msmtp
In order to send emails via SMTP, I now use msmtp, which can act as a sendmail
replacement but sends via an SMTP server. Previously I set up spacemacs to send via SMTP, but that required the .authinfo.gpg
method above to retrieve the password. I forked msmtp to also support macOS Keychain access.
My ~/.msmtprc
looks like this:
defaults
tls on
# might need this for your server
# tls_trust_file /usr/local/etc/openssl/certs/some-authority.pem
account or
host smtp.forsooth.org
from or@forsooth.org
auth on
user or
keychain_name <KeychainName>
keychain_account <KeychainAccount>
account default : or
Credentials via macOS Keychain (recommended)
Just like in the
mbsync
configuration, this specifies aKeychainName
andKeychainAccount
to look up the password in the macOS Keychain. This is currently requires my fork, see above, but hopefully it can be merged upstream.Credentials via GPG (discouraged)
Again, the password can be read from
.authinfo.gpg
instead. The configuration would look like this:passwordeval "gpg --quiet --for-your-eyes-only --no-tty --decrypt ~/.authinfo.gpg"
But as mentioned above, this allows anyone with access to an unlocked machine to run the command and to retrieve your password. The Keychain method is more secure.
spacemacs
Finally, we need a way to read and write emails. This is where spacemacs comes in.
With a small layer defined in ~/.emacs.d/private/layers/spacemail/packages.el my .spacemacs
config contains:
(defun dotspacemacs/layers ()
;; ...
dotspacemacs-configuration-layer-path '("~/.emacs.d/private/layers/")
dotspacemacs-configuration-layers '(;; ...
spacemail
;; ...
)
;; ...
)
(defun dotspacemacs/user-config ()
;; ...
;; use msmtp as a sendmail replacement to send mails
(setq message-send-mail-function 'message-send-mail-with-sendmail)
(setq sendmail-program "/usr/local/bin/msmtp")
;; get the "From:" address from the envelope Emacs generates
(setq message-sendmail-envelope-from 'header)
;; ...
)
And that is it. In the package file above there are several shortcuts set up. Most importantly SPC a m i
for inbox/unread mail and SPC a m n
for new mail.
Once in a mail listing useful shortcuts are (evil mode):
h
,j
,k
,l
navigateRET
open mail on cursors
search via filter, e.g.tag:build
,cake AND NOT tag:me
,hey there
/
search in buffert
tag mail on cursor; use -<tag> or +<tag>Z
to switch to tree viewr
respond to senderR
respond to all
And now all your vim bindings and spacemacs features will work when writing emails, which is much more nicer than some typical TextWidget.
For instance, you can format paragraphs quickly and wrap them at the proper width, including quoted text, which can just be selected with V
and reformated with gq
, while keeping the >
prefix correctly at the beginning of each line.
Last but not least you can take org-mode links to emails! Inside an email just hit SPC a o l
to store a link (this is not specific for the emails, it works in other files as well) and insert it in an org-mode document via , i l
. This is very nice to make TODOs to respond to specific emails, or attach an important thread to a projet or task.