A few weeks ago, I set up reading an email account over IMAP in Emacs, and over the weekend I installed an SSD and set up Nix home-manager. Let's put these together use home-manager to configure scripts for downloading emails from my GMail account, save them to the new SSD, and make them viewable in Emacs.

Most of this follows the GMail instructions here, although some things seem to have changed. Let's start by setting up lieer:

home.packages = with pkgs; [
  # mail

Then we activate the change using home-manager switch. Ok, did that do anything?

❯ which gmi

So far so good. Next, we're supposed to set up notmuch, so go back into home.nix:

home.packages = with pkgs; [
  # mail

Looking at these other instructions specifically about home-manager, and looking at the home-manager options for notmuch it seems we can use nix to define a configuration file for notmuch, without using notmuch init. Let's try that:

programs.notmuch = {
  enable = true;
  hooks = {
    preNew = "cd /scratch/Maildir/gmail && gmi sync";
  new.ignore = ["/.*[.](json|lock|bak)$/"];
  new.tags = ["new"];
  maildir.synchronizeFlags = true;
  extraConfig.database = {
    path = "/scratch/Maildir/gmail";

Confusingly, this creates a file at ~/.config/notmuch/notmuchrc instead of at ~/.notmuch-config, as notmuch expects by default, so we can use the home.file technique again to link it to the right place. (Could alternatively set the NOTMUCH_CONFIG environment variable, but Emacs didn't seem to detect the presence of that variable. The notmuch.nix file that defines all these things also seems to try to set the NOTMUCH_CONFIG variable, but again doesn't seem to actually get set.)

home.file.notmuchrc = {
  source = "/home/shaun/.config/notmuch/notmuchrc";
  target = "./.notmuch.config";

Now, the plan is to store the email on the new SSD, which is mounted at /scratch, so:

mkdir -p /scratch/Maildir/gmail

There's also a bunch of shared email configuration parameters, which I assume lieer and notmuch both consult:

accounts.email = {
  maildirBasePath = "/scratch/Maildir";
  accounts.gmail = {
    address = "?????@gmail.com";
    flavor = "gmail.com";
    maildir.path = "/gmail/";
    lieer.enable = true;
    notmuch.enable = true;
    realName = "Shaun Lee";
    passwordCommand = "pass email/?????-gmail";
    userName = "?????@gmail.com";
    primary = true;

I've set up pass to manage my passwords, and saved an app password for GMail into pass. That involved generating a GPG key using:

gpg --full-generate-key

and then saving the password using:

pass insert email/?????-gmaii

Now we can add the gmail account and initialize lieer:

cd /scratch/Maildir/gmail
gmi init ?????@gmail.com

All of that should have worked fine, but in practice, I got an error when running notmuch new that libnotmuch.so couldn't be found. It turns out this was installed at ~/.nix-profile/lib/libnotmuch.so, but ~/.nix-profile/lib/ isn't on the LD_LIBRARY_PATH. Searching for "environment variable" in the home-manager options documentation revealed a home.sessionVariables setting that seemed like a reasonable solution:

home.sessionVariables = {
  LD_LIBRARY_PATH = "/home/shaun/.nix-profile/lib $LD_LIBRARY_PATH";

Edit: I've removed the above section and it seems to work fine.

The programs.notmuch section up above includes hooks section, but I think my setup is a bit different than what is expected by notmuch.nix, so I just added the equivalent commands to a shell script at /scratch/Maildir/gmail/.notmuch/hooks/pre-new

cd /scratch/Maildir/gmail/
gmi sync

So now we can get mail using notmuch new.

Now let's finish it off with a some Emacs configuration. This was partially set up before, when I was trying out notmuch for reading other mail:

(use-package notmuch
  :ensure t
  :commands (notmuch)
  (add-hook 'notmuch-hello-mode-hook
            (lambda ()
              (display-line-numbers-mode 0)))
  (notmuch-fcc-dirs "~/mail/fastmail/Sent")
  (notmuch-crypto-process-mime t) ; Automatically check signatures
  (notmuch-saved-searches '((:name "inbox" :query "tag:inbox" :key "i"
                                   :count-query "tag:inbox and tag:unread"
                                   :sort-order newest-first)
                            (:name "unread" :query "tag:unread" :key "u"
                                   :sort-order newest-first)
                            (:name "flagged" :query "tag:flagged" :key "f"
                                   :sort-order newest-first)
                            (:name "sent" :query "tag:sent" :key "t"
                                   :sort-order newest-first)
                            (:name "drafts" :query "tag:draft" :key "d"
                                   :sort-order newest-first)
                            (:name "all mail" :query "*" :key "a"
                                   :sort-order newest-first))))

The biggest difference is that I have so much old mail that I don't want to scroll to the bottom to see the recent mail, so i've customized the standard searches to show the newest-first.

Ok, next time, port the mbsync, mu4e, and msmtp configurations over to home-manager.

Edit: asynchronous polling in notmuch

By default, pressing G in a notmuch mode runs notmuch-poll-and-refresh-this-buffer, which runs notmuch new synchronously. This locks up Emacs while it's retrieving mail and updating the database, which is unfortunate. Can't this be done asynchronously?

Borrowing from this Reddit post, apparently it can be done, but I don't care to watch the log of messages, so I've adapted the code there to remove the lines to display the new *notmuch new* buffer. It also turns out that notmuch-lib.el provides a notmuch-start-notmuch function that starts and returns an asynchronous notmuch command, so we don't need start-process either. I'm not sure why this isn't the default.

So I've added this to the :config section of the use-package section for notmuch:

  (defun spl/notmuch-poll-and-refresh ()
    "Asynchronously poll with `notmuch new' and then refresh notmuch buffers.

Borrowed from: https://www.reddit.com/r/emacs/comments/gwucim/another_notmuch_config_question/"
    (with-current-buffer (get-buffer-create "*notmuch new*")
      (notmuch-start-notmuch  "notmuch new" (current-buffer)
                              (lambda (p e)
                                (when (zerop (process-exit-status p))

and also added a keybinding:

:bind (:map
       ("G" . spl/notmuch-poll-and-refresh))