Reading GMail in Emacs
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 lieer ];
Then we activate the change using home-manager switch
. Ok, did that do anything?
❯ which gmi /home/shaun/.nix-profile/bin/gmi
So far so good. Next, we're supposed to set up notmuch
, so go back into home.nix
:
home.packages = with pkgs; [ # mail lieer notmuch ];
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
#!/bin/bash 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) :config (add-hook 'notmuch-hello-mode-hook (lambda () (display-line-numbers-mode 0))) :custom (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/" (interactive) (with-current-buffer (get-buffer-create "*notmuch new*") (erase-buffer) (notmuch-start-notmuch "notmuch new" (current-buffer) (lambda (p e) (when (zerop (process-exit-status p)) (notmuch-refresh-all-buffers))) "new")))
and also added a keybinding:
:bind (:map notmuch-common-keymap ("G" . spl/notmuch-poll-and-refresh))