email setup
Table of Contents
1. Introduction
Setting up email automation on my primary desktop.
1.1. TL;DR
To tidy emails:
$ mbsync -a --pull # fetch new emails from providers $ cleanupmbox # apply automatic rules (on local copy) $ mbsync -a --push # propagate deletes/moves back to providers $ notmuch new # recognize new mail
Read email locally (with favored maildir++
-compliant reader),
and/or enjoy tidied email on provider's system (e.g. gmail in browser)
Alternatively can search from command line:
$ notmuch search sometext
1.2. Overview
After some investigation settled on:
- isync
- (aka
mbsync
) to synchronize provider email with a maildir tree - notmuch
- for offline email indexing/search.
- notmuch-emacs
- emacs module for local email reading
Wrote a custom python script cleanupmbox
to operate programatically
on the local MAILDIR
tree. Script has enough content to deserve its own page,
next after this one.
Notes:
mbsync
configuration in~/.mbsyncrc
- emails kept under
~/.mail
cleanmbox
symlinks to~/proj/env/bin/cleanupmbox.py
cleanmbox
rules in~/.config/cleanmbox/rules.csv
1.3. Goals
I had the following goals:
- automate email handling: want ability to move emails into folders based on pattern-matching rules
- fetch email from multiple providers (gmail, hushmail), browse from the same filesystem tree.
- synchronize email disposition with providers, so that using browser on their site (especially from phone) will provide an up-to-date view.
1.4. Links
- https://anarc.at/blog/2021-11-21-mbsync-vs-offlineimap/ isync vs offlineimap, plus useful example configuration.
- https://wiredspace.de/blog/mbsync/
- https://wiki.archlinux.org/title/Isync lovely isync configuration examples
2. Setup Instructions
2.1. Scaffold Maildir Tree
$ mkdir -p ~/.mail/gmail $ mkdir -p ~/.mail/hushmail
2.2. Isync Setup
Install isync into shell environment; may want to later set this up to poll periodically.
$ nix-env -i isync
Anonymized configuration file:
hushmail part:
# ~/.mbsyncrc IMAPAccount hushmail # address to connect Host imap.hushmail.com User replacewithuser@hushmail.com # if you're comfortable with plaintext: #Pass replacewithpassword # # PassCmd value encrypted with something like # $ echo replacewithpassword | gpg --recipient mygpgusername -a -o ~/.hushmail.gpg --encrypt PassCmd "gpg --no-tty --for-your-eyes-only -dq ~/.hushmail.gpg" # enable TLS SSLType IMAPS CertificateFile /etc/ssl/certs/ca-certificates.crt # remote IMAPStore hushmail-remote Account hushmail # local MaildirStore hushmail-local Subfolders Verbatim # must have trailing / Path ~/.mail/hushmail/ Inbox ~/.mail/hushmail/Inbox Channel hushmail Far :hushmail-remote: Near :hushmail-local: # include everything for now Patterns * # auto-create missing mailboxes Create Both # delete messages (enable after seeing sync work) Expunge Both # save synchronization state SyncState * # propagate mailbox deletion (enable after seeing sync work) Remove both
gmail part:
IMAPAccount gmail Host imap.gmail.com User replacewithuser@gmail.com # encrypted with something like # $ echo replacewithpassword | gpg --recipient mygpgusername -a -o ~/.gmail.gpg --encrypt PassCmd "gpg --no-tty --for-your-eyes-only -dq ~/.gmail.gpg" SSLType IMAPS CertificateFile /etc/ssl/certs/ca-certificates.crt # remote IMAPstore gmail-remote Account gmail # local MaildirStore gmail-local Subfolders Maildir++ Inbox ~/.mail/gmail Channel gmail Far :gmail-remote: Near :gmail-local: MaxMessages 15000 ExpireUnread yes # # in particular, excluding: # [Gmail]/Drafts, [Gmail]/Spam, [Gmail]/Trash # Patterns * ![Gmail]* "[Gmail]/Sent Mail" "[Gmail]/Important" "[Gmail]/Starred" "[GMail]/All Mail" #Patterns * Sync all Create Both Expunge Both SyncState * Remove both
2.3. Isync Use
sync email folders from all providers:
$ mbsync -a
or to just sync with hushmail provider
$ mbsync hushmail
To just propagate changes from gmail to local:
$ mbsync gmail --pull
Similarly, to just propagate changes to hushmail:
$ mbsync hushmail --push
2.4. Notmuch Setup
Installed notmuch
via nix flake in my project xo-nix2
.
see https://github.com/rconybea/xo-nix2/blob/mail/flake.nix
Accomplished this by adding notmuch
, emacsPackages.notmuch
to devShells.packages
:
devShells = { default = pkgs.mkShell.override { stdenv = env; } { packages = [ ... pkgs.notmuch pkgs.emacsPackages.notmuch ... ]; }; };
Installing this way ensures that emacs
, notmuch
and notmuch-emacs
versions are coordinated.
notmuch
gets setup interactively:
$ notmuch setup Your full name: Your primary email address: alice@gmail.com Additional email address [Press 'Enter' if none]: Top-level directory of your email archive:/home/alice/.mail Tags to apply to all new messages (separated by spaces) [ unread inbox]: Tags to exclude when searching messages (separated by spaces) [ junk]:
This creates empty tag database in MAILDIR=/.notmuch
(/home/alice/.mail/.notmuch
here)
Populate tag database with contents of MAILDIR:
$ notmuch new
Default notmuch-emacs
setup is almost trivial.
In ~/.emacs
:
(require 'notmuch)
(but also see M-x customize-group RET notmuch RET
)
Now can browse email from emacs with M-x notmuch
.
2.5. Outgoing Mail Setup
in ~/.emacs
:
(require 'smtpmail) (setq user-mail-address "replacewithuser@gmail.com" user-full-name "Alice Exampleton") (setq message-send-mail-function 'smtpmail-send-it) (setq smtpmail-stream-type 'starttls ;;smtpmail-default-smtp-server "smtp.hushmail.com" smtpmail-smtp-server "smtp.gmail.com" smtpmail-smtp-service 587 ) (setq message-kill-buffer-on-exit t)
For smtpmail
, We also need ~/.authinfo
to provide login credentials
machine smtp.gmail.com login replacewithuser@gmail.com port 587 password replacewithpassword
Here replacewithpassword
needs to be a "google app password".
Obtain this from gmail.com -> account settings -> security -> 2-step verification -> app passwords
Now can send email with C-x m
3. Lessons and Tradeoffs
- for email syncing, I looked at
offlineimap
andisync
. Choseisync
because it's reported to be faster, and I had a large (100k+) backlog of email to deal with. This seems to have worked out well.isync
is designed to work with multiple email providers (whatisync
calls 'channels'). gmail eventually cuts off (or maybe times out) socket connections when trying to sync a large number of messages. Settled on 15k message cap for the gmail channel
# .mbsyncrc Channel gmail ... MaxMessages 15000 ...
Scripting email-moving between
maildir
directories is non-trivial. To do this correctly you need to update embedded email identifiers, otherwise synchronization won't be able to tell the difference between copy and move. Python has a builtin library (mailbox
) that's simple to use, and takes care of this.It does come with a constraint: as far as I can tell, it expects email to be stored in the
maildir++
format, rather than regularmaildir
.In
maildir++
, folder->filesystem structure is flattened. A folderfoo/bar
will be stored in a directory.foo.bar
, so typically would have all folders in a single directory.Multiple channels muddies the filesystem picture, since different channels will map to sibling filesystem directories.
For example:
${MAILDIR} +- gmail | +- .receipts | +- .travel | .. +- hushmail +- .policy ..
I originally looked at
mu
for indexing (along withmu4e
for email reading in emacs); however that project doesn't seem to handlemaildir++
with multiple providers in separate directories like above.Fortunately,
notmuch
accomodates this, since it just needs a path under${MAILDIR}
. For example: to look at emails ingmail/.receipts
, usefolder:gmail/.receipts
in anotmuch
search.- Needed a detour to setup PGP (see gpg-setup.html), to circumvent having plaintext passwords in configuration files.