Rmail is a usable Emacs mail client

Tuesday, 2 June 2020

Recently, I’ve been seeing a few more articles on how to set up mu4e, an Emacs client to Emacs. I tried using it a few years back, and dispute the initial effort, I didn’t like it too much.

A while later, I used Gnus for about two years. It had its issues, but I found it more comfortable to work with than most other mail software (Thunderbird and Claws Mail1).

Earlier this year I started experimenting with Rmail. It is seen by some to be the standard Emacs mail reader, in the sense in which ed is the standard Unix text editor.

Nevertheless, I was pleasingly surprised, and have been using it since. In this text I want to give an outline of how it works, how to set it up in case you’re curious, and what it’s limitations are.

What is Rmail?

Those interested in managing and reading Email in Emacs, choose one of the above mentioned:

There are others, such as Wanderlust and Mew, but since I have never really used them I won’t risk making any wrong statements.

Rmail is a lot simpler, basically it’s a (special) major mode for mbox files, the old Unix mailbox format. Whenever you have a mbox file, you can run rmail-mode, and you’ll be able to read the mailbox. Usually you’d invoke rmail though, which opens your “mail inbox”, but more on that below.

I won’t be going into the keybindings, but there aren’t too many and the mnemonics are easy to comprehend (as opposed to other MUAs).

How I use Rmail

I have no idea how Rmail was intended to be used. Probably whatever RMS is doing. Therefore, I will just be going into whatever I configured for my workflow (two addresses, offline access, “inbox zero”, notifications in the mode line), hoping that it’s general enough to be useful for anyone who would like to try this out.

External Tools

Two programmes I use are central to the entire workflow I’m going to describe. Neither is necessary, but I prefer it. These are the well known msmtp and less famous mpop, both by Martin Lambers (marlam). The former is used for sending mail, and the latter for receiving it.

The configuration format is quite straightforward, and they share a lot of syntax. For example, my .mpoprc looks something like this:

defaults
tls on
tls_starttls off
tls_trust_file /etc/ssl/certs/ca-certificates.crt

account personal
host pop.homemail.net
user funny-me@homemail.net
delivery mbox ~/mail/in/personal.mbox
keep on
passwordeval authinfo login=homemail

account job
host pop.jobnet.org
user serious-me@jobnet.net
delivery mbox ~/mail/in/job.mbox
keep on
passwordeval authinfo login=jobnet

where the tls_trust_file is necessary because of Debian, and the passwordeval line calls authinfo, a little script I wrote to extract passwords from a authinfo.gpg file. You might just as well use gpg2 directly.

Either way, the configuration isn’t too important, and the manual page is quite readable. The important thing with mpop is that it runs regularly outside of Emacs (either using cron or systemd timers), and saves incoming mail in a mbox file, in a specially designating directory (in the case above, ~/mail/in).

Configuring Rmail

My configuration is (as of writing) not public, so I’ll be quoting and commenting excerpts that are relevant.

When calling rmail (not rmail-mode), we want out mailbox to open. By default, Rmail would use the local mail spool (mostly cron messages on my system), which is not so interesting. So my configuration starts like follows3:

(setc rmail-primary-inbox-list
      '("~/mail/in/personal.mbox"
        "~/mail/in/job.mbox")
      rmail-file-name "~/mail/inbox.mbox")

This says that Rmail should check the two “inboxes” from above, i.e. where my mail arrives, and if it finds something move it to ~/mail/inbox.mbox.

When this is done, Rmail immediately presents me with the first message. This is one of the reasons I like it, no loading screens, no splash screens or intermediary interfaces. Messages first, lists or options come afterwards. But one thing bugs me, there are so many headers, many of which I don’t care about. Do I have to see the raw Autocrypt header? I don’t think so, so I filter the headers by default to a minimum:

(setc rmail-displayed-headers "^\\(?:Cc\\|Date\\|From\\|Subject\\|To\\):")

A central part of my reading-workflow is re-archiving. When a message lands in ~/mail/inbox.mbox, I keep it there for only as long as it’s relevant, then I move it to another mailbox in ~/mail/old (using rmail-outout, bound to “o”). Everything else is just directly deleted. This I configure like so:

(setc rmail-default-file "~/mail/old/"
      rmail-delete-after-output t)

Finally, a few changes regarding MIME/attachments:

  1. I don’t want to read HTML mail
  2. I want downloads to land in ~/dl, my download directory.

Both is easily changed:

(setc rmail-mime-attachment-dirs-alist '(("" "~/dl"))
      rmail-mime-prefer-html nil)

The first line could be more fancy, but I just want everything (hence the empty regular expression) to land in ~/dl/. Consider reading the docstring if you prefer to automatically place certain file types in other places4.

Sending mail with message

My mail-sending setup is actually more complicated than what I need to read mail. The default

Part of the reason is that I now have to interface with an external tool (msmtp). This might be familiar to anyone who has configured other mail clients in Emacs, since msmtp is often recommended, especially if one has multiple addresses. Therefore I won’t have too much to add on the basics:

(setc sendmail-program "msmtp"
      message-send-mail-function 'message-send-mail-with-sendmail
      message-sendmail-extra-arguments '("--read-envelope-from")
      message-sendmail-envelope-from 'header
      message-sendmail-f-is-evil t)

Basically this is what you need for msmtp to work as you would want it. It’s necessary and boring, and the understanding it is left as an exercise to the reader.

Here a few other changes I have applied:

The final change is to configure a “sent mail” directory, by using the “FCC” header. This is interpreted by email to “send” a carbon copy to a file (in ~/mail/out), and will be deleted afterwards. The easiest way to do this that I know of is like this:

(defun local/add-fcc ()
  "Generate a FCC header for the current month."
  (format "FCC: ~/mail/out/%s.mbox"
    (downcase (format-time-string "%Y-%h"))))

(setc message-default-headers #'local/add-fcc)

The great thing is that this needs no background process, such as with Gnus. Even a simple “C-x m” will save a copy.

I don’t have complicated searching needs. Since I manually archive mail, I can usually remember where to look for a particular message. But whenever that’s not the case, I can fall back onto search. For this I use mairix, mainly because Emacs has support for it built in by default.

Mairix needs an external configuration, by default it’s located in ~/.mairixrc. In our example, it would look like this:

base=~/mail
mbox=inbox.mbox:out/...:old/...
mformat=mbox
database=~/mail/.mairix.db

This will look for messages in ~/mail/inbox.mbox, all files in ~/mail/out/ and ~/mail/old.

On the Emacs side, not much more is required:

(setc mairix-file-path "~/mail/"
      mairix-search-file "search.mbox")

Now calling mairix-search would generate and open a mbox archive of all messages matching the query.

Contacts

I used to use BBDB, but because like with most things I didn’t dig in too deep, I realised that the “mailrc” format totally suffices my needs. This is basically a file of this form:

alias bge bug-gnu-emacs@gnu.org
alias boss "Mr Bossard <boss@jobnet.org>"
alias department bob hans li richard
alias bob "Bob Colleque <bob@jobnet.org>"
...

(an alias for the Emacs mailing list, an alias for my boss, and an alias for the group consisting of “bob”, “hans”, “li” and “richard” — all of which are further aliases).

I could keep the mailrc file in my home directory, but I prefer to have all mail related stuff in one place, so I change the location where Emacs looks for it:

(setc mail-personal-alias-file "~/etc/mail/aliases")

The only thing left is to activate the mailrc subsystem:

(add-hook 'message-setup-hook #'mail-abbrevs-setup)

Now I can write boss in the “To” field of a message, and it will be expanded before sending it. This also works when resending a message!

New Mail indicator

With a cronjob running in the background, I don’t want to be paranoid and keep checking my inbox every 5 minutes. Instead I use display-time-mode to check for Mail, in ~/mail/in. The basic setup doesn’t need anything more than

(setc display-time-mail-directory "~/mail/in"
      display-time-mode t) ;activate the minor mode

Limitations

Currently, there’s the main annoyance with this workflow is when working on multiple devices. mpop can be configured to keep or delete mail after it has fetched it from the server, so you either end up with a fragmented inbox or re-read ever message you receive.

Further, synchronising between devices might be tricky. I use syncthing (which is why I wanted to keep all mail-related files in one subdirectory). This is one of the downsides of using mbox compared to maildir, and there’s not much one can do besides being careful.

Finally, Rmail is a bit sloppy by modern standards. I think the source code could be cleaner, it’s window management could be more respectful and MIME handling should be improved (I’d like to be able to apply patches directly from a message, but all I can change is where to store it; I’d like to open PDFs in a buffer, but all I can do is download them, etc.). When I get around to it, my hope is that I could fix a few of these complaints, but this isn’t a promise!

Either way, I hope to have shown that Rmail is a worthwhile MUA and depending on your Email needs, it might be enough. The documentation on the internet might be sparse, but ultimately you don’t have to change much for it to work well. I’ve used a few older file formats, tools and protocols, but my hope is that — just like with Emacs — if it worked 30 years ago — it’s going to work in another 30.


  1. Though if I had to use a graphical client, I’d probably choose Claws Mail.↩︎

  2. But it doesn’t have to for the bare minimum to work, eg. an online-only IMAP/SMTP setup.↩︎

  3. setc is a macro I use to set user options. Its syntax is the same as setq, just that it invokes customize-set-variable for the sake of forwards compatibility. Using setq shouldn’t change anything, but in case you want to try setc out, here’s my definition:

    (defmacro setc (&rest args)
     "Call `customize-set-variable' like `setq'."
     (let (body)
       (while args
         (unless (cdr args)
       (error "Not enough arguments"))
         (push `(customize-set-variable
             ',(pop args) ,(pop args)
             "   Set by `setc'")
           body))
       (cons 'progn (nreverse body))))
    ↩︎
  4. “Read the docstring” should be understood as a general recommendation for every variable I have and will introduce. Never just copy code into your configuration. This is a presentation, not a template.↩︎