E-mail in Emacs with mu4e on OS X

By Calum MacRae

January 25, 2015

I've been using the brilliant mu4e for my e-mail for a little while now, and couldn't not write about it. I'm enjoying e-mail for the first time!

So, I'm actually enthusiastic about E-mail now…

Having my e-mail in Emacs means I actually want to check it, because using mu4e is such an enjoyable experience. I always have an mu4e buffer open. As is likely the case with a lot of people, depending on your profession I suppose, I get a lot more work e-mail than personal. Since making the switch to mu4e at work, I really do feel I'm staying on top of tasks and communication a lot better.

As other Emacs users who're reading will understand; just the very fact that it's in Emacs, which means common Emacs key-bindings, makes it favorable almost instantly over other clients. Combine this with a powerful indexing back-end (simply mu), finding the content you're looking for is made flawlessly easy. Conducting the usual e-mail tasks (sending, forwarding, CC & BCC) is straight forward, dealing with attachments and HTML formatted e-mails is also a cinch.

Getting mu4e set up

mu4e is a collection of front-end elisp for the mu utility. Here's a brief description of mu taken from djcb's (the author) site:

mu is a tool for dealing with e-mail messages stored in the Maildir-format, on Unix-like systems. mu's main purpose is to help you to find the messages you need, quickly; in addition, it allows you to view messages, extract attachments, create new maildirs, … See the mu cheatsheet for some examples.

As noted above, mu is designed to work on Maildirs. Getting your e-mail in a Maildir format can be done in a few ways, it really depends on the protocol/framework being used on your mail server - maybe you're already using Maildir-format! In both the case of personal & work e-mail, I operate over IMAP. There are two de facto utilities you can use to retrieve your e-mail via IMAP: offlineimap & mbsync. I use OS X at home, and Fedora at work - in both cases, I opted for offlineimap, as it was available both through Fedora's yum repos, and we host a binary package for it in our Darwin pkgsrc repo for Save OS X.

Retrieval of e-mail using offlineimap

For anyone using our repo for packages on OS X (more details here), it's as easy as sudo pkgin -y in offlineimap :) For other OS's, it should be available via your package manager. It should build from source fine too, if you want to take that route.

Once you've got it installed, you can set your config up in ~/.offlineimaprc The syntax is pretty self explanatory, here's mine:

[general]
ui = TTY.TTYUI
accounts = GMail
pythonfile=~/.offlineimap.py
fsync = False

[Account GMail]
localrepository = GMail-Local
remoterepository = GMail-Remote
status_backend = sqlite

[Repository GMail-Local]
type = Maildir
localfolders = ~/.mail/gmail
nametrans = lambda folder: {'drafts':   'Drafts',
                            'sent':     '[Google Mail]/Sent Mail',
                            'trash':    '[Google Mail]/Bin',
                            'archive':  '[Google Mail]/All Mail',
                            'personal': 'Personal',
                            'linkedin': 'Personal/LinkedIn',
                            'shopping': 'Personal/Shopping',
                            'amazon':   'Personal/Shopping/Amazon',
                            'ebay':     'Personal/Shopping/Ebay',
                            'paypal':   'Personal/Shopping/PayPal',
                            'twitter':  'Personal/Twitter',
                            }.get(folder, folder)

[Repository GMail-Remote]
maxconnections = 1
type = Gmail
remoteuser = <redacted>@gmail.com
remotepasseval = get_keychain_pass(account="<redacted>@gmail.com", server="imap.gmail.com")
realdelete = no
ssl = yes
sslcacertfile = /usr/pkg/share/ncat/ca-bundle.crt
nametrans = lambda folder: {'Drafts':                   'drafts',
                            '[Google Mail]/Sent Mail':  'sent',
                            '[Google Mail]/Bin':        'trash',
                            '[Google Mail]/All Mail':   'archive',
                            'Personal':                 'personal',
                            'Personal/LinkedIn':        'linkedin',
                            'Personal/Shopping':        'shopping',
                            'Personal/Shopping/Amazon': 'amazon',
                            'Personal/Shopping/Ebay':   'ebay',
                            'Personal/Shopping/PayPal': 'paypal',
                            'Personal/Twitter':         'twitter',
                            }.get(folder, folder)
folderfilter = lambda folder: folder not in ['[Google Mail]/Bin',
                                             '[Google Mail]/Important',
                                             '[Google Mail]/Spam',
                                             ]

You'll probably have noticed pythonfile=~/.offlineimap.py. This is a really nice feature provided by offlineimap, it allows you to specify an external Python script to call to retrieve information (such as credentials).

The offlineimap.py script used here is for retrieving credentials from the OS X keychain. See here for several examples of retrieving credentials from other various sources, such as the Gnome keyring, or PGP encrypted files, etc.

I found the offlineimap.py script floating around in a few places on the web, though, each instance seemed to contain static data, such as the user's username in the script… There's nothing really wrong with that, but, hey it's Python, why not get that information in a more portable manner? So, I chucked in the user and keychain params to make it truly portable:

#! /usr/bin/env python
import getpass, re, subprocess
from os.path import expanduser, join
def get_keychain_pass(account=None, server=None):
    params = {
        'security': '/usr/bin/security',
        'command': 'find-internet-password',
        'user': getpass.getuser(),
        'account': account,
        'server': server,
        'keychain': join(expanduser('~'), 'library/Keychains/login.keychain'),
    }
    command = "sudo -u %(user)s %(security)s -v %(command)s -g -a %(account)s -s %(server)s %(keychain)s" % params
    output = subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT)
    outtext = [l for l in output.splitlines()
               if l.startswith('password: ')][0]

    return re.match(r'password: "(.*)"', outtext).group(1)

Note: This is specifically for the OS X keychain, it can be modified in several ways to interact with other sources, as mentioned above

So, once you've got offlineimap installed, your config and credential retrieval method in place, you can kick off your first sync by just running offlineimap in the shell. This could take a while, depending on the amount of e-mail you've got - future runs of offlineimap can be run with the -q flag, for quick retrieval - I'll be noting how this is tied in with mu4e later on.

Once offlineimap is done running, take a look in your localfolders directory (set to ~/.mail/gmail in the above example), you should see some familiar directories:

$ ls ~/.mail/gmail/
INBOX		archive		ebay		paypal		sent		trash
amazon		drafts		linkedin	personal	shopping	twitter

Provided everything is in order, you're all set with your Maildir! Next up, getting mu.

Installing mu

When I first set up mu, I just built from source. Easy enough, just grab the code from djcb's Github, you'd need clang or gmake to compile, along with build and run deps:

  • pkg-config
  • automake / autoconf / autoreconf
  • sqlite3
  • glib2
  • gmime
  • xapian

All of which are available through the Save OS X repo :)

The above works fine, and is currently how I tackled it on Fedora at work. As for OS X, it turned out there was a pkgsrc port for it in wip. Rather than dirty our 2014Q4 repo, I figured I'd just roll my own. That way, if there's anything else I want from wip, or if I want to tinker with some ports, I can just chuck binary distributions into my own personal repo.

So, I deployed a zone under SmartOS, got nginx up and running, and placed a build of mu (which I updated to the more recent 0.9.11 release) there. In order to use the package in my repo, you'll first need to import my PGP key: 1F77 49AB 744A 7E15, as I sign my packages. Then, you can either opt to add my repo (http://pkgsrc.cmacr.ae/Darwin/wip/x86_64/All) to your /usr/pkg/etc/pkgin/repositories.conf, or, provided you're all set up with Save OS X, simply run: sudo pkg_add http://pkgsrc.cmacr.ae/Darwin/wip/x86_64/All/mu-0.9.11.tgz

I haven't added any of mu's dependencies to my repo, so you'll need to have the deps installed, or have our main repo (http://pkgsrc.saveosx.org/Darwin/2014Q4/x86_64/All/) set up - easily done: Save OS X - Download & Install mu will be available in our main repo eventually though!

Setting up Emacs to use mu4e

The binary package on my repo mentioned above is built with mu4e support, so, will also install the necessary elisp so you can use the beautiful front-end in Emacs.

If you opted to install from source, I believe mu4e support is a default compile time option - a quick note on this: if you're having trouble getting it to play nice with your site-lisp directory, when you run the configure script, prepend it by defining the EMACS variable, like so: EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs ./configure This should place the elisp in /usr/local/share/emacs/site-lisp/mu4e.

If you've opted to use my binary package for mu, elisp is distributed to the pkgsrc prefix, so ends up in /usr/pkg/share/emacs/site-lisp/mu4e. My binary package has Emacs 24 as a dependency, so, upon installation will install the emacs24-24.4 package from our main repo. This shouldn't be a problem, as when using Save OS X, PATH evaluation is set up so /usr/pkg/{bin,sbin}/ are evaluated after other binary paths on your system. That means, if you have Emacs installed elsewhere that you run in the shell, provided its binary is somewhere in the PATH before the one installed from our repo, that one will be used.

Minor tangent…

Recent ports of Emacs in pkgsrc have the nextstep build option…. which actually builds a Emacs.app ;) Right now, you might have noticed http://pkgsrc.cmacr.ae/Darwin/wip/x86_64/editors/emacs24-24.3.50.20140101nb8.tgz sat in my personal repo. This is a binary distribution of Emacs 24 that you can install with pkgin (or pkg_add) that will install the Cocoa version of Emacs on OS X. So sudo pkgin -y in emacs24-24.3.50.20140101nb would get you /usr/pkg/Applications/Emacs.app, which Spotlight actually picks up! That is so awesome to me, and I plan on working on a more recent build of Emacs to place in my repo.

Okay, back on track…

Once you've gotten mu installed, you'll need to add the following somewhere in your Emacs config:

(add-to-list 'load-path "/usr/pkg/share/emacs/site-lisp/mu4e")
(require 'mu4e)
(setq mu4e-mu-binary "/usr/pkg/bin/mu")

Of course, substituting the above paths depending on how you installed. If you installed from source, replace /usr/pkg in the above with /usr/local (or whatever path you used with the --prefix option).

The above config is the bare minimum needed to use mu4e, but likely not enough to get the real benefits of its functionality. Here's part of my mail.el, where I place all my e-mail related config:

;; -------------------------------------
;; MAIL
;; -------------------------------------

;; mu4e
(add-to-list 'load-path "/usr/pkg/share/emacs/site-lisp/mu4e")
(require 'mu4e)
(setq mu4e-mu-binary "/usr/pkg/bin/mu")
(setq mu4e-maildir "~/.mail/gmail")
(setq mu4e-view-show-images t)
(setq mu4e-html2text-command "w3m -dump -T text/html")
(setq mu4e-view-prefer-html t)
(setq mu4e-use-fancy-chars t)
(setq mu4e-headers-skip-duplicates t)
(setq mu4e-get-mail-command "offlineimap -q")
(setq mu4e-update-interval 300)
(setq mu4e-attachment-dir  "~/downloads")
(setq mu4e-sent-messages-behavior 'delete)
(setq message-kill-buffer-on-exit t)
(setq mu4e-hide-index-messages t)
(add-hook 'mu4e-compose-mode-hook 'flyspell-mode)
(setq
 user-mail-address "<redacted>@gmail.com"
 user-full-name  "Calum MacRae"
 mu4e-compose-signature
 (concat
  "Kind Regards,\n"
  "Calum MacRae\n"))

Notable configuration from cmacrae's mail.el

Prettifying things / dealing with the horrible world of HTML e-mail

(setq mu4e-view-show-images t)
(setq mu4e-html2text-command "w3m -dump -T text/html")
(setq mu4e-view-prefer-html t)
(setq mu4e-use-fancy-chars t)

The above config is hopefully pretty self explanatory, but basically:

  • Show images inline
  • Use w3m to dump HTML to formatted text
  • Prefer HTML (because it seems every fucking client decides to do everything in HTML nowadays…)
  • Use fancy characters… because who doesn't want those? ;)

Note: w3m is available from the Save OS X repo

Getting rid of duplicates

(setq mu4e-headers-skip-duplicates t)

Rather curious default behavior, but, mu4e shows duplicate e-mails by default. I suppose this makes sense really, it shouldn't hide anything from the user by default, but is provided as a simple option.

Keeping the Maildir up to date

(setq mu4e-get-mail-command "offlineimap -q")
(setq mu4e-update-interval 300)

Pretty straight forward; the command to retrieve e-mail - using offlineimap's -q flag for quick retrieval - and the interval at which to run said command (every 5 minutes here).

Keeping stuff clean

(setq mu4e-sent-messages-behavior 'delete)
(setq message-kill-buffer-on-exit t)
(setq mu4e-hide-index-messages t)

The first line here is important for use with G-Mail. G-Mail will keep track of your sent messages for you, so, this is just to tell mu to delete sent messages. I don't use this option at work, as the Exchange server there doesn't exhibit this behavior. The second line is to ensure that any message buffers left lying around are cleaned up if mu4e is exited. The last line is to hide update notifications in the mini-buffer… it can get pretty annoying seeing it every 5 minutes.

Spell checking!

(add-hook 'mu4e-compose-mode-hook 'flyspell-mode)

Because we're only human

Note: ispell and it's associated language packs ( ispell-british for me) are available in Save OS X repo, for use with flyspell-mode

Identification

(setq
 user-mail-address "<redacted>@gmail.com"
 user-full-name  "Calum MacRae"
 mu4e-compose-signature
 (concat
  "Kind Regards,\n"
  "Calum MacRae\n"))

Again, pretty self explanatory - sender e-mail address, name, and signature.

Sending e-mail

I opted to use the brilliant smtpmail (comes with Emacs) for sending e-mail. There are other methods, but I haven't explored them.

Here's the smtpmail section from my mail.el:

;; smtpmail
(require 'smtpmail)
(require 'starttls)
(setq message-send-mail-function 'smtpmail-send-it
      smtpmail-stream-type 'starttls
      smtpmail-smtp-service 587
      smtpmail-default-smtp-server "smtp.gmail.com"
      smtpmail-smtp-server "smtp.gmail.com"
      smtpmail-smtp-user "<redacted>@gmail.com")
(setq starttls-extra-arguments '("--x509cafile" "/usr/pkg/share/ncat/ca-bundle.crt"))

I can't imagine I need to comment on all of the above configuration, as it should hopefully be quite clear it's simply SMTP configuration for use with smtp.gmail.com However, I will touch on the subject of TLS being used here.

In order for the above configuration to work, gnutls will need to be installed, also available from Save OS X's repos…. do I need to keep repeating myself? We have so many packages! I use the awesome exec-path-from-shell, so don't need to tell Emacs where to find the gnutls-cli binary installed from the Save OS X repo. However, you can use (setq ssl-program-name "/path/to/gnutls-cli") to tell Emacs where it is (this would be /usr/pkg/bin/gnutls-cli if you've installed from our repo).

You'll probably also have noticed the (setq starttls-extra-arguments '("--x509cafile" "/usr/pkg/share/ncat/ca-bundle.crt")) line. This is needed so that gnutls-cli has a list of x509 certs it knows it can trust. The /usr/pkg/share/ncat/ca-bundle.crt is conveniently supplied with the nmap package in our repo, a util I often use. If you don't use nmap, or simply don't want to install it just for the .crt, you can grab it as a flat file from Mozilla, place it somewhere on your filesystem, and set it to that path.

Upon sending e-mails with the above config, unless you've specified a password in ~/.netrc (not something I do), Emacs will prompt you for your password. It will only do this upon the first connection per session, so if you don't close Emacs, you'll only need to do this once. There are other means of retrieving the password, like placing it in a PGP encrypted file, or grabbing it from a keychain, but I haven't explored these; the retrieval of credentials from OS X's keychain using auth-source.el doesn't work right now, so I just enter my password :)

Last, but by certainly no means least, PGP!

As with almost everything in Emacs, setting up PGP (using GnuPG) integration with mu4e is super easy!

;; gpg
(add-hook 'mu4e-compose-mode-hook 'epa-mail-mode)
(add-hook 'mu4e-view-mode-hook 'epa-mail-mode)

Yep… just two lines, that's all you need. The above will then just hook into whatever distribution of GnuPG you're using, and call it when necessary.

Working with PGP encrypted e-mail then becomes as easy as a few key-bindings. When composing an e-mail, C-c C-e s to sign your message, then C-c C-e e to encrypt. When receiving a PGP encrypted e-mail: C-c C-e v to verify the signature, and C-c C-e d to decrypt.

Phew!

This ended up being a bit longer than I expected. It's funny, looking back at how much I've written would probably give the impression that set-up is complex… it really, really isn't. Once your config is in place, and hopefully I've helped provide some skeleton configuration, it's smooth sailing and just pure enjoyment.

I'm glad I took the time to write about mu4e, it really is brilliant. The author, djcb, did such an awesome job on it, it's easily one of my favourite components of my Emacs config. He's also written sauron, an Emacs event log, with trigger actions! Really cool, I've been using it for watching IRC & Jabber mentions, but plan on throwing in e-mail alerts from mu4e for important stuff, which I'll definitely post about.

I hope I've convinced some of you to try mu4e out! Questions and feedback are, as always, very welcome :)

Posted on:
January 25, 2015
Length:
13 minute read, 2586 words
Tags:
emacs macos email
See Also:
Nightly Emacs Flake
A Nix overlay for Emacs 27 with the 'emacs-mac' patches
Emacs: Making NeoTree work with Perspectives