Mail with Gnus

Posted on September 12, 2023

After two years with Mu4e, I have moved over to Gnus as my mail client. I've started using RSS to consume news, and the move was mainly motivated by the appeal of having news and email in the same place. On the whole, Gnus is probably easier to get going than Mu4e. It doesn't require using Mbsync or some other process to manage a local copy of your mail and there's no need for an external program to index your mail. Gnus takes care of everything with its nnimap select method. Do note that you can configure Gnus to operate on a local copy of your mail using Gnus' nnmaildir select method, which will also be discussed.

There are a few things both approaches have in common, so we'll start there, with the first bit of jargon I casually glossed over: select methods. Select methods as I understand them are essentially servers Gnus gets articles from, Gnus has a default select method, and secondary select methods. The utility of a default select method was lost on me, so I just set it to what I understand to be Gnus' equivalent of nothing:

(setq gnus-select-method '(nnnil ""))

One thing I liked about Mu4e was support for multiple mail provider "contexts", and being able to jump between them without having to think about it. Gnus sort of has this in the form of "posting styles". My understanding is that Mu4e contexts are more powerful, but as a rube who just needs to delete swaths of spam and send something every now and again I don't care about anything beyond "please send mail from whatever account is tied to the group1 I'm currently perusing without me having to think about it". If you're the same way, then setting up posting styles is simple:

(setq gnus-posting-styles '((".*" (address "My Name <my.name@mailbox.org>")
                             ("X-Message-SMTP-Method" "smtp smtp.mailbox.org 587 my.name@mailbox.org"))
                            ("gmail" (address "My Name <my.name@gmail.com>")
                             ("X-Message-SMTP-Method" "smtp smtp.gmail.com 587 my.name@gmail.com"))))

I'm going to be honest, at time of writing I completely forgot what the posting styles match on. It turns out it's the group name, If you're unfamiliar with regular expressions, then the first match (the ".*" bit) will look strange, but it is effectively a default posting style that doesn't care what the group is. If I want my posting style to be gmail and all that comes with it, the group just has to have gmail somewhere in it. My Gmail groups all look like this:

nnimap+gmail:INBOX

Everything before the colon is the same for every folder Gnus decides to keep track of. If I wanted to be more specific, I would change the expression from "gmail" to "^nnimap\+gmail:", but it works for now.

There are a few things you can add to posting styles2; If one email account were personal and one were work, I would probably use different signatures for each. The posting style form (using Mailbox as an example) would then look like this:

(".*" (address "My Name <my.name@mailbox.org>")
 ("X-Message-SMTP-Method" "smtp smtp.mailbox.org 587 my.name@mailbox.org")
 (signature "Your esteemed Colleague, Me"))

I always just sign things "Your super pal" though, so I set one signature for everything:

(setq mail-signature "Your super pal,\nMe")

The last thing I liked about Mu4e was its ability to synchronize message flags with Gmail/Mailbox. Gnus can thankfully do that, too:

(setq gnus-agent-synchronize-flags t)

I wasn't planning on it but I guess I can make a few points about actually using the package. Normally in Gnus you subscribe to things, but with mail Gnus does that automatically. I really only do about 2.5 things in Gnus: I expire messages (pressing E with the message selected), I mark them as important (pressing !), or sometimes I expire an entire thread of messages (pressing T E). That's it.

Like I said, my requirements are minimal, so that about covers the configuration common to both nnimap and nnmaildir approaches. Time to tackle the specifics.

The "nnimap" Approach

I'm starting with nnimap since this is what I'm using. I know that I personally tend to ignore most of the text on articles I'm reading and focus on the code, so to preempt that eventuality I am providing the code that works best first. Let's start with those select methods, shall we?

(setq gnus-secondary-select-methods '((nnimap "gmail"
                                              (nnimap-address "imap.gmail.com")
                                              (nnimap-server-port "imaps")
                                              (nnimap-stream ssl)
                                              (nnir-search-engine imap)
                                              (nnmail-expiry-target "nnimap+gmail:[Gmail]/Trash"))
                                      (nnimap "mailbox"
                                              (nnimap-address "imap.mailbox.org")
                                              (nnimap-server-port "imaps")
                                              (nnimap-stream ssl)
                                              (nnir-search-engine imap)
                                              (nnmail-expiry-target "nnimap+mailbox:Trash"))))

One of the first things I try to figure out when using Emacs as a mail client is how to move messages to trash instead of hard deleting them all the time. The setting nnmail-expiry-target does the job. It's so easy here (foreshadowing). The rest should be common to most email providers and is mostly standard for contemporary email exchange: use imaps, not imap (the s is important) and encrypt the stream with ssl. I know I don't like broadcasting anything I don't have to.

The last call-out isn't visible from the configuration: Gnus will automatically check your ~.authinfo or ~/.authinfo.gpg file for login credentials. It will even prompt you to save them for next time if they're not already there. There is a big caveat to this, though, which is that Gnus doesn't check if you're using ~/.authinfo or ~/.authinfo.gpg (please use the latter). If you decide to let Gnus save your credentials, it will do so in~/.authinfo, so be sure to move them out of there. If you want to put it in yourself, make sure it follows this format:

machine gmail login my.name@gmail.com password my_secret_api_key port imaps
machine mailbox login my.name@mailbox.org password my_user_password port imaps

I spent too long figuring out that Gnus keys off the imaps port.

But with that, we're done! Now let's look at the nnmaildir way.

The "nnmaildir" Approach

Before I start, I really wanted this approach to work. There's something I like about having a local copy of my mail. Plus, Gnus loads noticeably faster in every view I use: the group view where my mailboxes are listed, the summary view that shows all the messages in a selected mailbox, and in the article view that displays a specific message. This means it's significantly faster to churn through all that garbage email when it's local, which is what I spend most of my time doing! Unfortunately, there was one thing keeping me from doing it this way, but I'll let the suspense build for a bit before I talk about it and talk about getting set up instead.

I set myself up with Mbsync to manage my local mail, which I have gone into as much detail as I would ever care to in a previous post3. From there, setting up the select methods is simple:

(setq gnus-secondary-select-methods '((nnmaildir "gmail"
                                                 (directory "/var/mail/gmail/"))
                                      (nnmaildir "mailbox"
                                                 (directory "/var/mail/mailbox/"))))

This is much simpler than nnimap since all the server info is handled by Mbsync. All nnmaildir is doing is pulling stuff out of a directory and maybe moving it once in a while, or deleting it when you would rather it do something else. Keep in mind that Gnus won't be able to sync your mail because it has no information on the backend that takes care of that. To address this, I wrote myself a little function that I run before opening Gnus (and sometimes after closing it):

(defun ab/sync-mail ()
  "Runs 'mbsync' in an asynchronous process to sync local email
from configured imap servers."
  (interactive)
  (let ((default-directory "~"))
    (async-shell-command "mbsync -a")))

And yes, I do run this command every time I'm about to open Gnus. And maybe this should have been the nail in the coffin, but it wasn't. The true nail is tragic because it's purely a matter of taste: I couldn't get Gnus to send mail to the trash directory in a way that was consistent with how it normally expires mail. The nnmaildir select method will not, under any circumstances I could devise, move a message to the trash folder without immediately deleting it from the file system.

Could I just ignore Gnus' message expiry system? Yes.

Could I just manually move messages to the trash folder with a single command built in to Gnus? Yes

Could I easily make a keybinding for this command and make it muscle memory? Yes.

Would this be better and safer than relying on Gnus to interface with an IMAP server in a way I expect? Probably.

But I like Gnus expiry system, it's weirdly part of the experience for me. In case you were wondering or probably forgot: yes, this is the disappointing denouement to that foreshadowing earlier. I am fixated on marking mail as expired and letting Gnus take care of the rest. I tried setting the expiry action from 'delete to the trash group hoping Gnus would know to put expired messages there (as suggested in the documentation), but no luck. I tried setting the expiry action to a function that moves messages to the trash group as a side effect, but the messages were immediately deleted so who knows if they got moved or not. I combed through the code looking for a hook to attach to but found nothing. So hurt was my pride that I am now hiding behind principle instead of accepting that mail can work a little different from my news feeds.

Some day, I may accept that I am doing myself more harm than good by relying on Gnus to do what I expect under ambiguously defined circumstances, and maybe then I'll revisit nnmaildir and give it the chance it deserves. Until then, if someone could post something somewhere detailing how to do this, it would expedite the move substantially. For all my hubris, I can accept the shame of not reading documentation carefully enough or missing some blog post from a decade ago going over this very scenario.

Footnotes

1In Gnus vernacular and within the context of mail, a group is basically a mailbox (e.g. inbox, trash, drafts, etc.).
2"Posting Styles (Gnus Manual)," The GNU Operating System and the Free Software Movement, accessed September 10, 2023. https://www.gnu.org/software/emacs/manual/html_node/gnus/Posting-Styles.html.
3Andrew Burch, "Emacs as a Mail Client," Nothing Is Simple, accessed September 10, 2023. https://nothingissimple.ablatedsprocket.com/posts/emacs-as-a-mail-client.html.