I recently spent some time configuring Doom Emacs to be my email client. It took a lot of trial and error, but I finally have a working configuration. Why did I do this? Because the Emacs bug has bitten me, and the fever has taken over. Let’s go over my configuration and talk through some pain points I encountered.
Emacs for the Uninitiated
Excuse the hyperbole, but Emacs can do everything. At its core is a Lisp interpreter that one can script to do most computing tasks. And, over its 40+ year lifespan, Emacs has grown. It’s now configurable to integrate with the most popular development tools and environments. I’ve been using it for many things these days, from project management to switching my music to the next track. I’ve even used it to control my Twitch stream. Yes, it’s that capable.
It appeals to me not because it can do everything. Rather, I’m impressed with the consistent approach across all these applications. The ability to configure workflows across all my computing use cases has been game-changing.
Batteries Included
Out of the box, Emacs is very capable, but both learning and configuring are quite a time investment. I started my journey with a pre-configured framework, for lack of a better word, called Doom Emacs. Doom provides a curated experience with pre-configured packages. giving newcomers and the time-restrained a head start.
Even with the running head start, I found setting up email to be less than easy. The Doom documentation, while great in some respects, is lacking in this area. A pain point in my acclimation to the Emacs environment: it’s quite fragmented. Quite a bit of Googling showed me learning resources close to my specific needs. But there was nothing 100% in the hand-holding territory.
The Steps
Enough preamble. Let’s walk through the setup and configuration of mbsync, mu, and mu4e with multiple Gmail SMTP/IMAP accounts. It seemed to me that Gmail presented more challenges than configuring other providers. Gmail presents SMTP in a different way than most. Conventionally, one would sync folders between one’s provider and a local email store. Gmail has “labels” disguised as SMTP folders.
Dependencies
First, let’s grab our dependencies, of which there are a few. This post assumes you’re running some flavor of Linux or MacOS and know how to use its package manager. (It also assumes an installation of Doom Emacs itself, which is outside the scope of this particular post.)
Grab yourself a copy of mbsync
and mu
. These are for syncing our mailboxes and their content. Here’s some convenient hand-holding.
# For Debian / Ubuntu flavors
sudo apt install mbsync
sudo apt install mu
#For Arch / Manjaro flavors
sudo pacman -S mbsync
sudo pacman -S mu
#For MacOS users
brew install isync
brew install mu
Configure
Now it’s time to configure mbsync
. Create a file called .mbsyncrc
and place it in your $HOME
directory. I’ll include the content of my .mbsyncrc
below. Make sure to replace every instance of the email address/account nicknames with your own.
# ACCOUNT INFORMATION
Create Both
Expunge Both
SyncState *
IMAPAccount youraddress@gmail
Host imap.gmail.com
User [email protected]
# You can also provide a plain text passwork like so:
# Password yourpasswordwithoutquotes
PassCmd "gpg --quiet --for-your-eyes-only --no-tty --decrypt \~/Mail\/[email protected]"
AuthMechs LOGIN
SSLType IMAPS
CertificateFile ~/.ssh/mu4e/cert.pem
IMAPStore account1@gmail-remote
Account youraddress@gmail
MaildirStore youraddress@gmail-local
Path ~/Mail/youraddress@gmail/
Inbox ~/Mail/youraddress@gmail/INBOX
Channel youraddress@gmail-inbox
Far :youraddress@gmail-remote:
Near :youraddress@gmail-local:
Patterns INBOX
Channel youraddress@gmail-sent
Far :youraddress@gmail-remote:"[Gmail]/Sent Mail"
Near :youraddress@gmail-local:"[Gmail].Sent Mail"
Channel youraddress@gmail-trash
Far :youraddress@gmail-remote:"[Gmail]/Trash"
Near :youraddress@gmail-local:"[Gmail].Trash"
Channel youraddress@gmail-archive
Far :youraddress@gmail-remote:"[Gmail]/All Mail"
Near :youraddress@gmail-local:"[Gmail].All Mail"
Channel youraddress@gmail-drafts
Far :youraddress@gmail-remote:"[Gmail]/Drafts"
Near :youraddress@gmail-local:"[Gmail].Drafts"
Group youraddress@gmail
Channel youraddress@gmail-inbox
Channel youraddress@gmail-trash
Channel youraddress@gmail-archive
Channel youraddress@gmail-sent
Channel youraddress@gmail-drafts
#############################################.
For brevity, I’ve only included one account above. However, the provided config should work for as many accounts as you need so long as you give them unique names. Be sure to replace the content with your own addresses, etc.
Directory
Next, you’ll need to create a directory for your mailboxes to live.
mkdir -p ~/Mail/youraddress@gmail
If you’ve had your account for 10+ years like I have, this may take a while. Keep in mind that this will pull in every email you ever sent or received. Mine was 5 gigs. If this is an issue, you may consider omitting the “Sent Mail” and “All Mail” folders.
Within Gmail, be sure to enable IMAP under the setting page’s Forwarding and IMAP/SMTP tab.
Now it’s time to fetch our mailboxes. Run the following command:
mbsync --all
Now go make some coffee, since this might take several minutes. Once your emails have finished downloading, run the following command:
mu init --maildir ~/Mail --my-address [email protected] --my-address [email protected] # for each email address youre adding.
Now tell mu to index your mailboxes with:
mu index
Now that we have the groundwork finished, we can finally start to configure mu4e
, our Emacs email client.
(require 'mu4e)
;; list of your email adresses:
(setq mu4e-personal-addresses '("[email protected]"
"[email protected]"))
(setq mu4e-contexts
`(,(make-mu4e-context
:name "Gmail" ;; Give it a unique name. I recommend they start with a different letter than the second one.
:enter-func (lambda () (mu4e-message "Entering gmail context"))
:leave-func (lambda () (mu4e-message "Leaving gmail context"))
:match-func (lambda (msg)
(when msg
(string= (mu4e-message-field msg :maildir) "/address1@gmail")))
:vars '((user-mail-address . "[email protected]")
(user-full-name . "Your Name Here")
(mu4e-drafts-folder . "/address1@gmail/[Gmail].Drafts")
(mu4e-refile-folder . "/address1@gmail/[Gmail].All Mail")
(mu4e-sent-folder . "/address1@gmail/[Gmail].Sent Mail")
(mu4e-trash-folder . "/address1@gmail/[Gmail].Trash")
;; SMTP configuration
(starttls-use-gnutls . t)
(smtpmail-starttls-credentials . '(("smtp.gmail.com" 587 nil nil)))
(smtpmail-smtp-user . "[email protected]")
(smtpmail-auth-credentials .
'(("smtp.gmail.com" 587 "[email protected]" nil)))
(smtpmail-default-smtp-server . "smtp.gmail.com")
(smtpmail-smtp-server . "smtp.gmail.com")
(smtpmail-smtp-service . 587)))
,(make-mu4e-context
:name "Business Address" ;; Or any other name you like.
:enter-func (lambda () (mu4e-message "Entering cablecar context"))
:leave-func (lambda () (mu4e-message "Leaving cablecar context"))
:match-func (lambda (msg)
(when msg
(string= (mu4e-message-field msg :maildir) "/address2@gmail")))
:vars '((user-mail-address . "[email protected]")
(user-full-name . "Your Name Here")
(mu4e-drafts-folder . "/address2@gmail/[Gmail].Drafts")
(mu4e-refile-folder . "/address2@gmail/[Gmail].All Mail")
(mu4e-sent-folder . "/address2@gmail/[Gmail].Sent Mail")
(mu4e-trash-folder . "/address2@gmail/[Gmail].Trash")
;; SMTP configuration
(starttls-use-gnutls . t)
(smtpmail-starttls-credentials . '(("smtp.gmail.com" 587 nil nil)))
(smtpmail-smtp-user . "[email protected]")
(smtpmail-auth-credentials .
'(("smtp.gmail.com" 587 "[email protected]" nil)))
(smtpmail-default-smtp-server . "smtp.gmail.com")
(smtpmail-smtp-server . "smtp.gmail.com")
(smtpmail-smtp-service . 587)))
))
(setq mu4e-maildir-shortcuts '((:maildir "/address2@gmail/INBOX" :key ?i)
(:maildir "/address2@gmail/[Gmail].Sent Mail" :key ?s)
(:maildir "/address2@gmail/[Gmail].Drafts" :key ?d)
(:maildir "/address2@gmail/[Gmail].Trash" :key ?t)
(:maildir "/address2@gmail/[Gmail].All Mail" :key ?a)
(:maildir "/address1@gmail/INBOX" :key ?I)
(:maildir "/address1@gmail/[Gmail].Sent Mail" :key ?S)
(:maildir "/address1@gmail/[Gmail].Drafts" :key ?D)
(:maildir "/address1@gmail/[Gmail].Trash" :key ?T)
(:maildir "/address1@gmail/[Gmail].All Mail" :key ?A)))
And with that, you can try to fetch your email with mu4e. In Doom, that’s a keystroke of SPC o m b u , which will land you in the “Today” box and fetch any fresh messages.
Note that above, when we configure our contexts, we give them unique names. In Doom Emacs, you can switch contexts with the ; key and then enter the first letter of the name of the context.
Configuring Doom Emacs as an Email Client: Wrapping Up
I hope this post will save you a good bit of the time and frustration I experienced. My biggest piece of advice would be to follow the mailbox naming conventions laid out here. Naming them something else will cause the creation of those Mailbox names inside Gmail, duplicating their contents. It took me several tries to get that right, wasting a lot of time and bandwidth in the meantime.