My email setup
A very opiniated mail setup
This article presents my opinionated email setup, client side. By opinionated I mean that
it requires quite some stuff (like nixpkgs
) and is cli/emacs/… oriented.
I used to read my mails only through the web interface of my mail provider (GMail for the most part), or through my phone. As I’m trying to use my phone less, at least for work, and as I wanted to not have a gmail tab always opened on my browser, I decided to configure an email client on my laptops/desktops.
So far, I ended up using the following tools:
mbsync
to sync mails between server and laptop/desktop.msmtp
to send mails.notmuch
to index, and tag mails.emacs
withgnus
andnotmuch
for reading/sending mails.astroid
for cases where I want another GUI than emacs.
Something a bit special here is that I also use home-manager
… and home-manager
has
modules for those tools, so we are going to use thoses.
Module
Let’s start by defining the module, the usual Nix way.
# Generated from an org file 💃 # See : https://sbr.pm/technical/configurations/mails.html { config, lib, pkgs, ... }: with lib; let cfg = config.profiles.mails; in {
Let’s now define options. As of now, except enable
(to activate or not the module) I
don’t have any options in mind.
options = { profiles.mails = { enable = mkEnableOption "Enable mails configuration"; sync = mkEnableOption "Enable sync mail service"; frequency = mkOption { default = "*:0/30"; description = "Frequency at which the mail should be checked"; type = types.str; }; }; };
Finally, create the configuration.
config = mkIf cfg.enable (mkMerge [
{
Base settings
Accounts
The next step is to actually define the accounts we want use and where we want to store email, amongst other need.
- We want to store mails in
desktop/mails/{account}
. - We don’t want to input password each and every time so we’re using an encrypted file (symmetric encryption using GnuPG with a passphrase file).
- We’re gonna enable diverse modules on each account
mbsync
to sync the mail with some setupts (like specific rules for GMail specific folders)notmuch
for email indexingmsmtp
to send a mail, using the account’s smtp serverastroid
for a GUI
accounts.email = { maildirBasePath = "desktop/mails"; accounts = { "redhat" = { address = "vdemeest@redhat.com"; userName = "vdemeest@redhat.com"; realName = "Vincent Demeester"; passwordCommand = "${pkgs.gnupg}/bin/gpg -q --for-your-eyes-only --no-tty --exit-on-status-write-error --batch --passphrase-file ${config.home.homeDirectory}/sync/rh.pass -d ${config.home.homeDirectory}/desktop/documents/rh.pass.gpg"; imap.host = "imap.gmail.com"; smtp.host = "smtp.gmail.com"; mbsync = { enable = true; create = "both"; expunge = "both"; patterns = ["*" "![Gmail]*" "[Gmail]/Sent Mail" "[Gmail]/Starred" "[Gmail]/All Mail"]; extraConfig = { channel = { Sync = "All"; }; account = { Timeout = 120; PipelineDepth = 1; }; }; }; notmuch.enable = cfg.sync; astroid.enable = cfg.sync; msmtp.enable = true; }; "perso" = { address = "vinc.demeester@gmail.com"; userName = "vinc.demeester@gmail.com"; realName = "Vincent Demeester"; passwordCommand = "${pkgs.gnupg}/bin/gpg -q --for-your-eyes-only --no-tty --exit-on-status-write-error --batch --passphrase-file ${config.home.homeDirectory}/sync/perso.pass -d ${config.home.homeDirectory}/desktop/documents/perso.pass.gpg"; imap.host = "imap.gmail.com"; smtp.host = "smtp.gmail.com"; mbsync = { enable = true; create = "both"; expunge = "both"; patterns = ["*" "![Gmail]*" "[Gmail]/Sent Mail" "[Gmail]/Starred" "[Gmail]/All Mail"]; extraConfig = { channel = { Sync = "All"; }; account = { Timeout = 120; PipelineDepth = 1; }; }; }; notmuch.enable = cfg.sync; astroid.enable = cfg.sync; msmtp.enable = true; }; "prv" = { primary = true; address = "vincent@demeester.fr"; userName = "vincent@demeester.fr"; realName = "Vincent Demeester"; passwordCommand = "${pkgs.gnupg}/bin/gpg -q --for-your-eyes-only --no-tty --exit-on-status-write-error --batch --passphrase-file ${config.home.homeDirectory}/sync/prv.pass -d ${config.home.homeDirectory}/desktop/documents/prv.pass.gpg"; imap.host = "mail.gandi.net"; smtp.host = "mail.gandi.net"; mbsync = { enable = true; create = "both"; expunge = "both"; patterns = ["*"]; extraConfig = { channel = { Sync = "All"; }; account = { Timeout = 120; PipelineDepth = 1; }; }; }; notmuch.enable = cfg.sync; astroid.enable = cfg.sync; msmtp.enable = true; }; }; };
To create the pasword files:
- create
~/desktop/documents/{account}.pass.gpg
file, you need to create a~/desktop/documents/prv.pass
file with the actual password. - create
~/sync/{account}.pass
with a passphrase (long, complex, whatever…) encrypt
~/desktop/documents/{account}.pass.gpg
with the following commandgpg --batch --yes --symmetric --passphrase-file ~/sync/{account}.pass --encrypt {account.pass}
- remove
~/desktop/documents/{account}.pass
msmtp
wrapper
As I have multiple accounts, I need to be able to send mails from those multiple accounts
too. For this we will use msmtp
. We will $HOME/.nix-profile/bin/msmtp
to make sure it
uses --read-envolep-from
. This means it will look at what FROM
header is set in the
e-mail and use the correct account accordingly.
home.file."bin/msmtp" = { text = '' #!${pkgs.stdenv.shell} ${pkgs.libnotify}/bin/notify-send "Sending mail ✉️" ${pkgs.msmtp}/bin/msmtp --read-envelope-from $@ ''; executable = true; };
We also want to make sure we enable msmtp
.
programs.msmtp.enable = true;
And that should be all for the base settings, so let’s close that part
}
Syncing
I may not want to sync and index mails on all computers. In practice, I only do that on one computer and I sync these mails with the others.
(mkIf cfg.sync {
Service
Now that all the configuration are defined (and generated once we run home-manager
),
we’re going to enable the mbsync
service to synchronize email at the given frequency.
services.mbsync = { enable = true; preExec = "${config.xdg.configHome}/mbsync/preExec"; postExec = "${config.xdg.configHome}/mbsync/postExec"; frequency = cfg.frequency; };
We also setup preExec
and postExec
hooks on the service to be able to run commands
before and after actually running mbsync
.
preExec
has two main purpose :- Create the accounts mail folder — this is only useful for the first run ever, but it is required.
- Move mails on the right folders
- from Inbox to elsewhere (All mails, …)
- (in the future) to the right folders (from the tags)
xdg.configFile."mbsync/preExec" = { text = '' #!${pkgs.stdenv.shell} export NOTMUCH_CONFIG=${config.xdg.configHome}/notmuch/notmuchrc export NMBGIT=${config.xdg.dataHome}/notmuch/nmbug ${pkgs.coreutils}/bin/mkdir -p ${config.home.homeDirectory}/desktop/mails/redhat ${config.home.homeDirectory}/desktop/mails/perso ${pkgs.afew}/bin/afew -C ${config.xdg.configHome}/notmuch/notmuchrc -m -v ''; executable = true; };
postExec
will index the new emails in thenotmuch
database and tag mail accordingly (to their folders and other rules in place).
xdg.configFile."mbsync/postExec" = { text = '' #!${pkgs.stdenv.shell} export NOTMUCH_CONFIG=${config.xdg.configHome}/notmuch/notmuchrc export NMBGIT=${config.xdg.dataHome}/notmuch/nmbug ${pkgs.notmuch}/bin/notmuch new ${pkgs.afew}/bin/afew -C ${config.xdg.configHome}/notmuch/notmuchrc --tag --new -v # Remove inbox (lower-case) ${pkgs.notmuch}/bin/notmuch tag -inbox -- tag:inbox # Remove Inbox tagged message that are not in an Inbox ${pkgs.notmuch}/bin/notmuch tag -Inbox -- not folder:redhat/Inbox and not folder:perso/Inbox and tag:Inbox ${pkgs.libnotify}/bin/notify-send "Mails synced 📬" ''; executable = true; };
Finally, let’s define custom commands to simplify my mail usage. Those should be nix
package in the near future — as of now, it is a bit ugly as I’m creating binaries inside
$HOME/bin
instead of relying of Nix.
msync
is an helper to run quicklymbsync
systemd service from anywhere
home.file."bin/msync" = { text = '' #!${pkgs.stdenv.shell} ${pkgs.libnotify}/bin/notify-send "Syncing mails 📫️" systemctl --user start mbsync ''; executable = true; };
Programs
Additionally we can enable some programs and customize their behavior. Let’s enable
programs.mbsync
, which has for effect to put mbsync
binary in PATH
so that the user
(us) can call it. Same goes for programs.msmtp
and programs.notmuch
.
programs.mbsync.enable = true; programs.notmuch.enable = true;
Afew
afew
is “an initial tagging script for notmuch mail”. We’re going to define some extra
configuration to enable some filters and MailMover
rules.
Note: This should go away at some point as these rules are not dynamic enough for my usage.
programs.afew = { enable = true; extraConfig = '' [SpamFilter] [KillThreadsFilter] [ListMailsFilter] [ArchiveSentMailsFilter] [FolderNameFilter] maildir_separator = / [MailMover] folders = perso/Inbox redhat/Inbox rename = true perso/Inbox = 'NOT tag:Inbox':"perso/[Gmail]/All Mail" redhat/Inbox = 'NOT tag:Inbox':"redhat/[Gmail]/All Mail" ''; };
Astroid
astroid
is a “graphical threads-with-tags style, lightweight and fast, e-mail client for
Notmuch”. My main e-mail client is emacs
with the notmuch
mode, but sometimes I want a
GUI, mainly to see wanky HTML mails that would not render correctly some times.
programs.astroid = { enable = true; externalEditor = "emacsclient -c"; extraConfig = { startup.queries.inbox = "tag:Inbox"; startup.queries.inbox_perso = "folder:perso/Inbox"; startup.queries.inbox_redhat = "folder:redhat/Inbox"; }; };
And that’s all for the sync part, so let’s close it
})
Close the module
]); }
References
- My personal Email setup - Notmuch, mbsync, postfix and dovecot
- Notmuch, offlineimap and Sieve setup - anarcat
- https://github.com/kzar/davemail
- Handling Email with Emacs – malb::blog
- Emacs as email client with offlineimap and mu4e on OS X
- A Complete Guide to Email in Emacs using Mu and Mu4e
- emacstips
- notmuch + emacs + offlineimap configuration procedure
- isync - ArchWiki
- email - Emacs and Multiple SMTP servers - Super User
- Better Email with mu4e | NaN
- My personal mail setup
- initial tagging and afew - Foobacca
- Handling Email with Emacs – malb::blog
- How I email, 2016 edition
- Notmuch of mail a setup Part 2 - notmuch and Emacs | Assorted Nerdery
- Checking email with gmailieer + notmuch + Emacs | John’s Blog
- One year with Notmuch