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.

Table of Contents

So far, I ended up using the following tools:

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.

1 Module

Let’s start by defining the module, the usual Nix way.

# Generated from an org file 💃
# See : https://sbr.pm/technical/mail-setup.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 = mkOption {
      default = false;
      description = "Enable mails configurations";
      type = types.bool;
    };
    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 {

2 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 indexing
    • msmtp to send a mail, using the account’s smtp server
    • astroid 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";
          };
        };
      };
      notmuch.enable = true;
      astroid.enable = true;
      msmtp.enable = true;
    };
    "perso" = {
      primary = true;
      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";
          };
        };
      };
      notmuch.enable = true;
      astroid.enable = true;
      msmtp.enable = true;
    };
  };
};

3 Services

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 the notmuch 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} tag -Inbox -- not folder:redhat/Inbox and not folder:perso/Inbox and tag:Inbox
      ${pkgs.libnotify}/bin/notify-send "Mails synced 📬"
      '';
      executable = true;
    };
    

4 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;
programs.msmtp.enable = true;

4.1 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"
  '';
};

4.2 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";
  };
};

5 Some commands

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.

  • msmtp wraps $HOME/.nix-profile/bin/msmtp by making 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;
    };
    
  • msync is an helper to run quickly mbsync 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;
    } ;
    

6 Close the module

  };
}

7 References

Emacs 24.3.50.3 (Org mode 8.0.3)

Vincent Demeester. Last Updated 2019-06-21 Fri 19:35 (exported 2019-08-31 Sat 16:41).