Finally the day comes when I decided to move another part of my workflow inside Emacs - Email. Before I jumped into the "rabbit hole" of Emacs, I never thought there are other options for handling email than the default email client provided by the OS: Outlook on Windows, Mail on macOS and Thunderbird on Linux. At the first glance, using email in Emacs seems to be a very complicated process to me for two main reasons: 1. there are just too many options such as Gnus, Rmail, NotMuch and mu4e; 2. you need some heavy configurations for either of these approaches work which is discouraging. After a bit or research, I decided to use mu4e as the interface to emails in Emacs. This blog describes how I set up email in Emacs with the help of mbsync, mu/mu4e and msmtp while handling all the passwords using GPG from an encrypted file. Before getting into the details, I would like the show how the email interface of mu4e looks like in the following figure. The email thread is from a Github issue that I followed:

./emacs-email.png

To describe the big picture of handling email locally in Emacs, I shamelessly stole the following figure showing the overall workflow from Adolfo Villafiorita’s blog post. Although there are dozens of blog posts describing how to set up email in Emacs (like this one), Adolfo's post is one of the most comprehensive ones. The other blog post which I would recommend is A Complete Guide to Email in Emacs using Mu and Mu4e from Caches to Caches who's posts are often high quality.

./mbsync-workflow.png
Credit Reading IMAP email in Emacs
  1. Emails in the servers are synced to a local folder through a sync software. There are several options for this purpose, with two of the most popular ones being offlineimap and mbsync.

  2. The local messages, which are stored in a particular format like maildir, are indexed through an indexing tool like mu or Notmuch.

  3. A user interface to interact with local messages and reading/composing new messages. Available options in Emacs Notmuch and mu4e from mu.

  4. Another step which is not shown in the above figure is the handling of email passwords. I will use GnuPG to retrieve passwords for the different programs from an encrypted file.

Configure mbsync (isync)

The package providing mbsync is actually called isync. First installed the relevant packages. On my Linux (Ubuntu/Pop OS):

1
sudo apt install isync msmtp maildir-utils openssl

The configuration for mbsync is stored in a file .mbsync in the home folder. For each email account, we need to specify the remote account information, local storage directory, channels (what folder to be synced) and groups. As always, much more detailed information can be found on the excellent Arch wiki page so I just provide below my mbsync configuration for Gmail as an example. Notice how we use the gpg -q --for-your-eyes-only --no-tty -d ~/.authinfo.gpg | awk '/machine imap.gmail.com login your_name@gmail.com/ {print $NF}' to get the password from the encrypted .authinfo.gpg instead specifying the raw password. More details on this will be discussed in the later section.

############################################
# Gmail
############################################
# ACCOUNT INFORMATION
IMAPAccount gmail
# Address to connect to
Host imap.gmail.com
User your_name@gmail.com
PassCmd "gpg -q --for-your-eyes-only --no-tty -d ~/.authinfo.gpg | awk '/machine imap.gmail.com login your_name@gmail.com/ {print $NF}'"
AuthMechs LOGIN
SSLType IMAPS
# SSLVersions SSLv3
# The following line should work. If get certificate errors, uncomment the two following lines and read the "Troubleshooting" section.
CertificateFile /etc/ssl/certs/ca-certificates.crt

# THEN WE SPECIFY THE LOCAL AND REMOTE STORAGE
# - THE REMOTE STORAGE IS WHERE WE GET THE MAIL FROM (E.G., THE
#   SPECIFICATION OF AN IMAP ACCOUNT)
# - THE LOCAL STORAGE IS WHERE WE STORE THE EMAIL ON OUR COMPUTER

# REMOTE STORAGE (USE THE IMAP ACCOUNT SPECIFIED ABOVE)
IMAPStore gmail-remote
Account gmail

# LOCAL STORAGE (CREATE DIRECTORIES with mkdir -p Maildir/gmail)
MaildirStore gmail-local
Path ~/Maildir/gmail/
Inbox ~/Maildir/gmail/INBOX

# CONNECTIONS SPECIFY LINKS BETWEEN REMOTE AND LOCAL FOLDERS
#
# CONNECTIONS ARE SPECIFIED USING PATTERNS, WHICH MATCH REMOTE MAIl
# FOLDERS. SOME COMMONLY USED PATTERS INCLUDE:
#
# 1 "*" TO MATCH EVERYTHING
# 2 "!DIR" TO EXCLUDE "DIR"
# 3 "DIR" TO MATCH DIR

Channel gmail-inbox
Master :gmail-remote:
Slave :gmail-local:
Patterns "INBOX"
Create Both
Expunge Both
SyncState *

Channel gmail-trash
Master :gmail-remote:"[Gmail]/Trash"
Slave :gmail-local:"[Gmail].Trash"
Create Both
Expunge Both
SyncState *

Channel gmail-sent
Master :gmail-remote:"[Gmail]/Sent Mail"
Slave :gmail-local:"[Gmail].Sent Mail"
Create Both
Expunge Both
SyncState *

Channel gmail-all
Master :gmail-remote:"[Gmail]/All Mail"
Slave :gmail-local:"[Gmail].All Mail"
Create Both
Expunge Both
SyncState *

Channel gmail-drafts
Master :gmail-remote:"[Gmail]/Drafts"
Slave :gmail-local:"[Gmail].Drafts"
Create Both
Expunge Both
SyncState *

# GROUPS PUT TOGETHER CHANNELS, SO THAT WE CAN INVOKE
# MBSYNC ON A GROUP TO SYNC ALL CHANNELS
#
# FOR INSTANCE: "mbsync gmail" GETS MAIL FROM
# "gmail-inbox", "gmail-sent", "gmail-trash" and
# "gmail-drafts"

Group gmail
Channel gmail-inbox
Channel gmail-sent
Channel gmail-trash
Channel gmail-all
Channel gmail-drafts

msmtp

msmtp allows delivery of emails using different SMTP servers which makes sense if you have multiple email accounts. The configuration for msmtp is stored in .msmtp in the home folder and again detailed explanation can be found on the relevant Arch wiki page. It is worth to mention that you can actually achieve the same goal by using the Emacs SMTP library (as I did initially).

Below is my msmtp configuration for the Gmail account. Note the similar way to retrieve password from the encrypted .authinfo.gpg file for the passwordeval line.

# Set default values for all following accounts.
defaults

# Use the mail submission port 587 instead of the SMTP port 25.
port 587

# Always use TLS.
tls on

# Set a list of trusted CAs for TLS. You can use a system-wide default file,
# as in this example, or download the root certificate of your CA and use that.
tls_trust_file /etc/ssl/certs/ca-certificates.crt

# Additionally, you should use the tls_crl_file command to check for revoked
# certificates, but unfortunately getting revocation lists and keeping them
# up to date is not straightforward.
#tls_crl_file ~/.tls-crls

logfile ~/.msmtp.log

############################################
# Gmail
############################################
account gmail
host smtp.gmail.com
from your_name@gmail.com
auth on
user your_name@gmail.com
passwordeval "gpg -q --for-your-eyes-only --no-tty -d ~/.authinfo.gpg | awk '/machine smtp.gmail.com login your_name@gmail.com/ {print $NF}'"
port 587

mu4e

There are more detailed documentation on how to configure mu4e from either the official documentation or other online posts. I have about 350 lines of elisp for my mu4e configuration so it does not make sense to list them all here. Below are the most core parts to get the basic email workflow working:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
(require 'mu4e)
(require 'mu4e-contrib)
(require 'smtpmail)

(setq mu4e-maildir (expand-file-name "~/Maildir"))
(setq mu4e-attachment-dir  "~/Downloads")

;; allow for updating mail using 'U' in the main view:
(setq
 mu4e-get-mail-command "mbsync -a"  ;; or fetchmail, or ...
 mu4e-update-interval 300
 mu4e-headers-auto-update t
 )

;; use msmtp
(setq message-send-mail-function 'message-send-mail-with-sendmail)
(setq sendmail-program "/usr/bin/msmtp")

(setq mu4e-contexts
      
      `(,(make-mu4e-context
	  :name "Gmail"
	  :enter-func (lambda () (mu4e-message "Switch to the Gmail context"))

	  ;; leave-func not defined
	  :match-func (lambda (msg)
			(when msg
			  (mu4e-message-contact-field-matches msg
							      :to "your_name@gmail.com")))
	  :vars '((mu4e-sent-folder       . "/gmail/[Gmail].Sent Mail")   ;; folder for sent messages
		  (mu4e-drafts-folder     . "/gmail/[Gmail].Drafts")      ;; unfinished messages
		  (mu4e-trash-folder      . "/gmail/[Gmail].Trash")       ;; trashed messages
		  (mu4e-refile-folder     . "/gmail/[Gmail].All Mail")   ;; saved messages
		  (user-mail-address	   . "your_name@gmail.com")
		  (user-full-name	   . "Your Name" )

		  ;; Shortcuts
		  (mu4e-maildir-shortcuts . (("/gmail/INBOX"               . ?i)
					     ("/gmail/[Gmail].Sent Mail"   . ?s)
					     ("/gmail/[Gmail].Drafts"      . ?d)
					     ("/gmail/[Gmail].Trash"       . ?t)
					     ("/gmail/[Gmail].All Mail"    . ?a)))

		  ;; don't save message to Sent Messages, Gmail/IMAP takes care of this
		  (mu4e-sent-messages-behavior . delete)
		  ))

Password handling with GnuPG

The easiest way is to specify password in plain text directly in the configuration files, which may be a bad example for security reasons. The second one is to specify the password in a file, e.g. .authinfo and then encrypt the file with GnuPG. When needed, a gpg decryption command can be used to retrieve the password from the encrypted file (.authinfo.gpg). First, I created a file .authinfo with the following content in the home folder (notice how they match our previous password retrieval commands in .mbsync and .msmtp).

machine imap.gmail.com login your_name@gmail.com port 993 password your_password
machine smtp.gmail.com login your_name@gmail.com port 587 password your_passward

Then encrypt the file with GnuPG to generate the encrypted file .authinfo.gpg. Now you can safely delete the .authinfo file consisting of the raw password content from your computer. If you need to modify part of the .authinfo.gpg file, just decrypt, modify and encrypt again. Sounds rather tedious, right? Again Emacs comes to the rescue. Using the Emacs Easy PG library, you can actually work on the encrypt .authinfo.gpg file directly while let the library handles the decryption/encryption. They are completely transparent to the users.

Since I want to sync my emails automatically every several minutes in mu4e, it would be ridiculous if I need to enter the passphrase each time. gpg-agent can solve the problem by remembering the keys in background so that I only need to type the passphrase once when I started my computer. Unfortunately, offlineimap does not play well with gpg-agent and mu4e and that is exactly why I settled down on mbsync finally.