Subscribe to my newsletter
Confluence discusses technology, science and society, and prompts you to think critically about your world. Dispatched fortnightly.
104 E-mail on macOS with Emacs and mu4e
Time will tell if this was worth all the trouble
After having used Emacs for some time now, primarily for note-taking, I decided at last to also try using it for my e-mails. I have always been fascinated by the thought of a text editor-based e-mail workflow, but never had the time or inclination to try it until today.
The process turned out to be tougher than I had anticipated, with few tutorials or proper guides targetted at macOS users. Consider what follows a quickfire tutorial to getting set up with your e-mails on Emacs on your macOS device. I also have some notes here—some that are overly verbose perhaps—intended specifically for my own future reference. And lastly, a disclaimer: I am no expert in Emacs, so there may be both better and safer ways of going about some of these steps. Once you get set up, though, you can always tune things to your liking.
There are eight steps to this process, only two of which might be time-consuming, but here are a few clarifications at the outset:
- For the purpose of this article, we will be using the ‘foo@bar.tld’ email giving it an account name of ‘foo’—you will need to change all occurrences of
foo
orfoo2
orfoo3
orbar
etc. with your e-mail addresses and the account names you chose for each of them - If you are setting up multiple e-mail addresses, make sure you give each a unique but identifiable ‘account name’ (which we called ‘foo’ above) and note that you will have to repeat some of the steps below for each of your emails (I will mention which steps need to be repeated)
- Nearly all of these steps are one-offs in that to continue fetching and handling your e-mails going forward, you need not repeat them (steps to actually use this with Emacs day-to-day can be found at the end of this article)
- Some e-mail account providers are fussy about password usage and IMAP transfers (gmail and iCloud come to mind, with the former messing up emails particularly with “labels”) and in most of these cases you can use app-specific passwords exclusive for your use with Emacs; check your email provider for steps to generate these app-specific passwords
Step 1
Install mu, isync and msmtp
E-mail on Emacs relies on three basic programs working in tandem: isync to sync with servers (in practice this is called mbsync), msmtp to handle sending and mu—which stands for mildir utils—which indexes emails on your system. mu4e is a client atop mu which ties it all into your Emacs.
I used Homebrew for all three installations in sequence. If you do not have Homebrew, simply copy and paste this into your terminal to install it:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
And then install our three programs for e-mail setup:
brew install mu
brew install isync
brew install msmtp
The brew formula mu
takes care of mu4e
as well, with no need to install mu4e
separately according to the dedicated manual. This was one of the mistakes I made early on. The installation instructions on the manual applies to many Linux flavours.
Step 2
Save your password(s)
Next we want to store our passwords in our system for safe and automated retrieval. We will not entertain saving passwords as plaintext, so the process of storage is a one-off and retrieval will be left to the machine.
There are multiple approaches to this step but this is the simplest one I could think of. We shall use the terminal to save our password as well as to fetch it. But if you have saved the password in other ways and can simply fetch it from the terminal, you can modify the code accordingly. You only need to save your password once per account and the fetching will always be automated. On macOS you can use Keychain Access for this purpose; or you can use any password manager that provides a CLI.
I describe here the steps for both Keychain Access and 1Password, so follow whichever you prefer. Keychain Access is built into macOS and is free so there are no extra steps. For 1Password you will need the 1Password CLI and, possibly, a subscription. The same will apply for any other app besides 1Password too, although free alternatives like Bit Warden exist.
Using keychain access
Open the terminal and run the following, making sure to change the account name foo
and e-mail address foo@bar.tld
as needed. Repeat for each e-mail address you would like to add:
security add-generic-password -s foo -a foo@bar.tld -w
There is nothing to remember with Keychain Access at this point, just that wherever you use the account names going forward, keep it consistent and case-sensitive.
Using 1Password
If you have your details entered into 1Password already, you can simply right-click on the item and grab the existing item ID via the private link. If you private link looks something like this—
https://start.1password.com/open/i?a=xxxxxxxxxxxxxxxxxxxxxxxxxx&v=oooooooooooooooooooooooooo&i=hanqrcad732yohqytjy32rkpes&h=my.1password.com
The content between the i=
and the subseqent &
is your item ID. In the example above it is hanqrcad732yohqytjy32rkpes
. If you have this for all your passwords, skip to the next step.
If you do not have these passwords saved on 1Password or prefer to create a new item just for this use case, grab your password and fill it into the following line and run it from the terminal. Also make sure to give it your own title where it says foo
for your reference down the line.
op item create title="foo" password="Enter your password here" --category="Email Account"
Run it and you will get back something like the following:
ID: hanqrcad732yohqytjy32rkpes
Title: The title you provided
Vault: Personal (oooooooooooooooooooooooooo)
Created: now
Updated: now
Favorite: false
Version: 1
Category: EMAIL_ACCOUNT
Fields:
password: [use 'op item get hanqrcad732yohqytjy32rkpes --reveal' to reveal]
Contact Information:
SMTP:
What we want from here is the very first field, the ID. Copy it somewhere safe temporarily as we will use it in later in step 4. Repeat this step for each account and note all their IDs too. This step will create a traditional entry on your 1Password app anyway, so you can right-click and get the ID as described earlier should you ever forget to note it down straight from the terminal.
Step 3
Set up your local directories and certificates
In this quick step we will make sure we have our trusted system certificates for fetching and sending e-mails to our servers as well as mailbox folders into which we will sync e-mails from our server. This is the folder set-up we need:
.mailbox
|__ foo
|__ foo2
|__ foo3
|__ certificates
Replace the foo
values with the account names you used in the previous step.
Important Where you create the .mailbox
folder can affect all subsequent steps so make sure you keep that in mind. Normally you would create it in your home directory so you can access ~/.mailbox/foo
and so on, but if you have a project folder of some sort e.g. ~/proj/.mailbox/foo
you can use that too. Just make sure to change the location path in all subsequent steps. I will continue as if my directory is in my home directory. On macOS your home directory is the one with your username.
Open Keychain Access as you would any other application—but note that Sequoia and later versions of macOS may ask to open the new Passwords app instead, but we definitely want Keychain Access. The best way to find this, if you are having trouble, is by calling up spotlight using ⌘ + Space and searching for it.
In Keychain Access go to System Roots on the left-hand sidebar, select all your certificates (just in case you end up selecting the wrong ones or skip over necessary ones) and go to File > Export items… and save it into ~/.mailbox/certificates
for later use. You will be saving a .pem
file and for this guide let us assume you called it certs.pem
when you saved it. You will use this in step 7.
If you ever change web servers or reset your trusted certificates, you would need to re-do this step, but this is a rare scenario in my experience.
Before we proceed, here is a quick summary of what we have done so far: we installed three programs, saved our passwords preparing them for later retrieval, and readied a local folder set-up to save our e-mails. Next we will prepare a bunch of files we need to actually perform the e-mail sync-and-update dance between Emacs (local) and the server.
Step 4
Set up isync
To fetch your e-mails and update your server with local changes, isync relies on data stored in a file called .mbsyncrc
in your home directory. Unfortunately there is nowhere else this directory can be (as far as I know) but since I like to keep my Emacs-related files in a specific folder myself, I can attest to the fact that simply symlinking this to the home directory works just fine. So you could keep it in some ~/source/file/path/to/.mbsyncrc
of your choice and then run ln s ~/source/file/path/to/.mbsyncrc ~
to have a ghostly copy in your home directory that simply points to the real deal.
Hat-tip to Petar Radośević from whom I took parts of this set-up file. You may want to double-check the latest version number of TLS as this goes up occasionally; it is 1.3 as of the time of this writing. The contents of .mbsyncrc
for each e-mail address is as follows:
###### Account details for foo@bar.tld
IMAPAccount foo
Host imap.bar.tld
Port 993
AuthMechs PLAIN
User foo@bar.tld
PassCmd "security find-generic-password -s foo -a foo@bar.tld -w"
TLSType IMAPS
TLSVersions +1.3
IMAPStore foo-remote
Account foo
### Local set-up
MaildirStore foo-local
Path ~/.mailbox/foo/
Inbox ~/.mailbox/foo/INBOX
SubFolders Verbatim
### Sync settings
Channel foo
Far :foo-remote:
Near :foo-local:
Patterns *
CopyArrivalDate yes
Expunge Both
Create Near
Sync All
SyncState *
Repeat this entire block of text for every e-mail account you need to add, making changes to the following fields, most of which are centred around the usual foo
and foo@bar.tld
that you have set-up so far. You might even consider a blanket search-and-replace with M–% on Emacs to speed things up.
- IMAPAccount
- Host (your e-mail provider or webmail server should have this readily available)
- Port (same as above)
- User (resist the urge to just use the part before
@
and use the whole e-mail address instead) - PassCmd (change relevant portions as in step 2 although the command is a bit different because this is to retrieve and not save your password; note also that the command mentioned above is for Keychain Access and if you used 1Password originally, you should replace this line
security ... w
with the following:op item get hanqrcad732yohqytjy32rkpes --reveal --fields password
replacing the ID after the wordget
with your own ID saved temporarily during step 2) - IMAPStore
- Account
- MaildirStore
- Path
- Inbox (you can repeat this line for your other folders viz. sent, deleted, spam etc. but only if you know their exact names; in any case we will be automating this in the next step—only the name
INBOX
is pretty much fixed) - Channel
- Far (note the enclosing
:
colons) - Near (note the enclosing
:
colons)
There are two options you may want to alter although neither is necessary:
- Patterns—like the line for
INBOX
above, this is an exception and you can leave it as it is but you can also specify the ordering e.g.* !"Sent Items" !"Junk Mail" !"Notes"
if you know your mailbox folder names exactly - Expunge—we set this to
Both
to ensure whatever you delete locally also gets deleted from the server and vice-versa, which is the ‘traditional’ IMAP behaviour people expect. You could change this to either None, Far or Near depending on whether you want to delete nothing, just the server or just local.
For your next e-mail address, simply copy and paste the same block of text (with necessary changes) below the previous one, all within your .mbsyncrc
file.
Step 5
Sync and set up mailboxes
Now if everything is correct so far you should be able to sync e-mails from your server, identify server mailboxes and replicate them locally, and store your emails on them for later. Run the mbsync
command:
mbsync -a
You can use -aV
if you want detailed logging of activities but the best way to check if this worked (assuming you did not get any error messages in the terminal straight away) is to head over to your ~/.mailbox/foo/
and see if you have a bunch of folders in there. You should.
You need only run this manually the first time and that too just to check that things are working before you move on to handle the Emacs side of things.
Step 6
Sync and index e-mail accounts
This is a two-step process, the first of which includes initialising each of your your e-mail addresses based on your settings from your .mbsyncrc
file, to let the program know you own these e-mail addresses. Following this we index all your mailboxes at once. Get started with—
mu init -m ~/.mailbox --my-address foo@bar.tld
Alternatively, if you have multiple e-mail addresses, repeat the last bit for each:
mu init -m ~/.mailbox --my-address foo@bar.tld --my-address foo2@bar2.tld --my-address foo3@bar3.tld
At the end of this, if all goes well, you will see a nice property–value table and your terminal should already be prompting you to run indexing, so go ahead and do it:
mu index
And with that your mailboxes should be ready to go.
Step 7
Set up SMTP for sending e-mails
So far we have done everything we need to fetch e-mails and sync deletions or other states e.g. moving to archives. But we still need to set up the other half of an e-mail workflow: sending out e-mails.
Somewhat like step 4, this step too involves preparing a file. This time prepare ~/.msmtprc
and just like .mbsyncrc
we need this in the home directory but you can have it elsewhere and then symlinked to the home directory using the same command as mentioned in step 4.
Here is the .msmtprc
file carrying details of one acocunt:
# Set default values for all the accounts.
defaults
logfile ~/.mailbox/msmtp.log
tls_trust_file ~/.mailbox/certificates/certs.pem
# Account details for foo@bar.tld
account foo
auth on
host smtp.bar.tld
port 465
protocol smtp
from foo@bar.tld
user foo@bar.tld
passwordeval security find-generic-password -s foo -a foo@bar.tld -w
tls on
tls_starttls off
# Default
account default : foo
For each of your e-mail accounts simply repeat the ‘Account details’ section by copying the existing one and pasting it below the next. These are all the changes you need to make:
- logfile (change to the location of your
.mailbox
folder but keep the rest intact) - tls_trust_file (change to the location of your
.mailbox
folder and note the name of the file saved from step 3 in case you need to change that as well) - account
- host (like with your
.mbsyncrc
file get this too from your webmail or server provider) - port (same as above)
- from
- user (again, some believe
foo
will do but I prefer to use the entire e-mail address just to be safe) - passwordeval (change the relevant bits in the given line if you are using KeychainAccess, else change the entire line
security ... w
with the following in case of 1Password:op item get hanqrcad732yohqytjy32rkpes --reveal --fields password
replacing the ID with the one you saved in step 2) - account default (pick your default account for sending e-mails)
Step 8
Set up your Emacs
This is the only step that has to do entirely with your Emacs set-up so, as you might expect, this is all about pasting a bunch of lines into your init.el
file. But start by opening Finder and hit ⌘ ⇧ G to figure out where mu4e is stored on your system. Finder should now show you a numbered folder stating a version number (something like 1.12.6 or greater) which you enter and navigate to share → emacs → site-lisp → mu and ensure that a folder called mu4e exists there. If not, re-run step 1 properly.
If you did find mu4e here (or wherever else you did on your system) note down the folder path. In the specific case I described just now, your path should be /opt/homebrew/Cellar/mu/A.XX.Y/share/emacs/site-lisp/mu/mu4e/
where A.XX.Y
is the version number which you can tell by the numbered folder you found earlier or by running mu --version
in the terminal.
To cut to the chase, here is the entirity of the lines of code you need to copy and paste into your init
file:
;; /path/to/mu4e/ ======= change this path to the one you just found =======
(use-package mu4e
:load-path "/opt/homebrew/Cellar/mu/A.XX.Y/share/emacs/site-lisp/mu/mu4e/")
;; SMTP
(require 'smtpmail)
;; MU
(setq mu4e-mu-binary (executable-find "mu"))
;; Keybinding ======= change if needed, leave if in doubt =======
(global-set-key "\M-m" 'mu4e)
;; Mailbox ======= change this =======
(setq mu4e-maildir "~/.mailbox")
;; MBSYNC
(setq mu4e-get-mail-command (concat (executable-find "mbsync") " -a"))
(setq mu4e-change-filenames-when-moving t)
;; MBSYNC interval in seconds
(setq mu4e-update-interval 300)
;; MAIL ATTACHMENTS ======= create folder and change this =======
(setq mu4e-attachment-dir "~/MailAttachments")
;; E-MAIL ADDRESSES ======= change this =======
(setq mu4e-user-mail-address-list '("foo@bar.tld"
"foo2@bar2.tld"
"foo3@bar3.tld"))
;; SHORTCUTS ======= change this to your liking based on your mailbox folders =======
(setq mu4e-maildir-shortcuts
'(("/foo/INBOX" . ?a)
("/foo2/INBOX" . ?b)
("/foo3/INBOX" . ?c)
))
;; CONTEXTS ======= change these, including mailbox subfolder names per your mailboxes =======
(setq mu4e-contexts
`(,(make-mu4e-context
:name "foo"
:enter-func
(lambda () (mu4e-message "Opening foo@bar.tld"))
:leave-func
(lambda () (mu4e-message "Closing foo@bar.tld"))
:match-func
(lambda (msg)
(when msg
(mu4e-message-contact-field-matches msg
:to "foo@bar.tld")))
:vars '((user-mail-address . "foo@bar.tld" )
(user-full-name . "Your Name Here")
(mu4e-drafts-folder . "/foo/Drafts")
(mu4e-refile-folder . "/foo/Archive")
(mu4e-sent-folder . "/foo/Sent Messages")
(mu4e-trash-folder . "/foo/Deleted Messages")))
,(make-mu4e-context
:name "foo2"
:enter-func
(lambda () (mu4e-message "Opening foo2@bar2.tld"))
:leave-func
(lambda () (mu4e-message "Closing foo2@bar2.tld"))
:match-func
(lambda (msg)
(when msg
(mu4e-message-contact-field-matches msg
:to "foo2@bar2.tld")))
:vars '((user-mail-address . "foo2@bar2.tld")
(user-full-name . "Your Name Here")
(mu4e-drafts-folder . "/foo2/Drafts")
(mu4e-refile-folder . "/foo2/Archive")
(mu4e-sent-folder . "/foo2/Sent Messages")
(mu4e-trash-folder . "/foo2/Deleted Messages")))
))
;; ENCRYPTION
(require 'epa-file)
;; Uncomment below if you do not have EPA already in your init ======= change this =======
;; (epa-file-enable)
(setq epa-pinentry-mode 'loopback)
(auth-source-forget-all-cached)
;; AUTO-COMPLETE (Run M-x RET company RET once) ======= install company =======
;; This is optional, so delete the lines below if you would rather not have auto-complete
(add-hook 'after-init-hook 'global-company-mode)
(add-hook 'mu4e-compose-mode-hook 'company-mode)
;; SENDING
(setq send-mail-function 'message-send-mail-with-sendmail
message-send-mail-function 'message-send-mail-with-sendmail)
(setq sendmail-program (executable-find "msmtp"))
(setq message-sendmail-envelope-from 'header)
;; POLICIES
(setq mu4e-context-policy 'pick-first)
(setq mu4e-compose-context-policy 'ask)
(setq message-kill-buffer-on-exit t)
(setq mu4e-hide-index-messages t)
(setq org-mu4e-link-query-in-headers-mode nil)
(setq mu4e-headers-visible-lines 25)
(setq mu4e-view-show-addresses t)
(setq mu4e-headers-include-related nil)
(setq mu4e-headers-show-threads nil)
(setq message-citation-line-function 'message-insert-formatted-citation-line)
(setq message-citation-line-format "%N @ %Y-%m-%d %H:%M :\n")
Alright, that was huge. I have noted lines where you might want to consider making changes with something distinct like ======= change this =======
and most changes are either self-explanatory or have to do with replacing foo
and bar
as in previous steps. This seems like a lot but really is not.
Using e-mail with Emacs
So now that we have everything set up, it is time to view our e-mails. If you retained the keybinding in step 8 above, simply hit M–m and you should have your mu4e buffer shown on Emacs with a bunch of keybindings, the inbox shortcuts you set up in step 8 and more. This view should give you a much better picture of what you want out of mu4e and you can tweak your init
file accordingly down the line.
I am not about to publicly display my inbox so here are a couple of screenshots of e-mails in action from the mu4e manual:
Follow the shortcut commands to open and view your e-mails. Once you are viewing a list of your emails, dired-like commands will still work for triaging e.g. m to move, d to delete, ? to mark unread, ! to mark read etc. For more shortcuts, consult the relevant page on the mu4e manual. Back on the main mu4e page (the one you reach with M–x mu4e or M–m) you can hit U to refresh or allow it to do so once every 5 min in the background.
What I have been enjoying about triaging my e-mails this way is the absolute simplicity of things. After all, e-mails are pieces of typed up words. Besides marketing spam and newsletters most of my e-mails are plaintext. The newsletters I subscribe to continue to display well as HTML on Emacs too while everything else is as though someone just sent me an old school LAN message. The way this fits into my existing Emacs workflow is buttery smooth.
I will be the first to admit, though, that this set-up is probably not for everyone. There is ample case to be made for using the Mail.app on macOS as it is a splendid app for its use case. I would still recommend most people simply go with Mail.app—most people, not all. If you can handle a bit of tech-savvy and spend an afternoon to set up your Emacs, you can ensure you never have to fiddle with it again, even across systems when you use it with git or traditional cloud sync. Add to this all the other benefits of Emacs and you have a system that you can ensure truly works for you.