Email setup in Emacs with Mu4e on macOS
UPDATE - 16. Jan. 2023:
Recently I came across a behaviour in Mu4e that did not do what was expected. It turns out Mu4e was not selecting the proper from-address when composing emails, which I did not notice for a long time.
The short of it is that the function used to send emails did not take into account the message-send-mail-hook
, which is responsible to select the proper from-address.
To fix this adjust the following lines in the mu4e-sending section:
;; send function:
-(setq send-mail-function 'sendmail-send-it
- message-send-mail-function 'sendmail-send-it)
+(setq send-mail-function 'message-send-mail-with-sendmail
+ message-send-mail-function 'message-send-mail-with-sendmail)
Motivation
There are a bunch of instructions on setting up Mu4e on macOS online. However I found that most of them kind of require too deep a knowledge of Emacs and command-line tools for a beginner to master or troubleshoot without a proper headache. I know, because that was the case for me in the beginning.
My goal here is to provide a step by step instruction without looking too far behind the curtain. With the hope that this way the discovery and deep dive into emailing with Mu4e in Emacs starts after the joy of a working system.
Now this seems long. And to be completely honest it is. But we are trying to be as thorough as possible here, right.
The last reason for typing this down is purely selfish. I want to have a record/documentation to go back to myself. As a matter of fact this is actually true for all my posts.
NOTE: This will only cover the setup. It is not a how to use Mu4e. The usage should be reasonably easy.
Prerequisites
- A working Emacs configuration:
- I suppose no one will setup Mu4e without that.
- A working Installation of
Homebrew
: - Fortunately the installation is easy. In my mind, this is the best or maybe just the easiest way to get (open-source) software onto your Mac.
The two above things will not be covered the instructions on this page.
How this will work
Mu4e is part of the command-line indexer mu
for emails in maildir (mail directory) format. It does need other 3rd party dependencies for retrieving and for sending emails.
This post is limited to fetching emails over imap
and for sending over smtp
. Other Protocols and methods – I am looking at you Exchange – are out of scope.
However I am convinced that this should cover a vast majority of the users. I will be using a dummy example account dummy@example.com
for a generic email account and one for Gmail, dummy@gmail.com
. Since we are on a Mac we will be looking at a dummy iCloud account dummy@icloud.com
as well.
iCloud and Gmail require more password setup for access through imap. There are good instructions for “app-specific passwords” with iCloud & “app passwords” with Gmail respectively.
Preparation
making the directories
This uses the maildir system. Meaning that the messages will be stored in a directory hierarchy as separate plain text files. We will need the following directories in the user’s home directory:
~/.maildir/icloud # name used for setting up mbsync an msmtp
~/.maildir/gmail # name used for setting up mbsync an msmtp
~/.maildir/example # name used for setting up mbsync an msmtp
~/.maildir/certificates # we will store System root certificates here
storing passwords in the keychain
The easiest way to store ones password in the macOS keychain is with the security
command in your terminal. For our accounts we will setup the three items mu4e-icloud
, mu4e-gmail
and mu4e-example
.
- icloud:
security add-generic-password -s mu4e-icloud -a dummy -w
- gmail:
security add-generic-password -s mu4e-gmail -a dummy@gmail.com -w
- example:
security add-generic-password -s mu4e-example -a dummy@example.com -w
For each account you will be prompted for the password to be stored.
The names “mu4e-icloud”, “mu4e-gmail” and “mu4e-example” are entirely arbitrary.
storing trusted root certificates
The applications mbsync
and msmtp
need a way to trust email servers’s tls/ssl certificates. We will use the list of certificate Authority trusted by the macOS system.
- Open the Application
Keychain Access.app
- Select
System Roots
in the sidebar - Select all items listen here –
⌘ + a
- Export the items with
⇧ + ⌘ + e
to the file~/.maildir/certificates/root-certificates.pem
Installing dependencies
- mu:
- To index your emails in your mail directory.
- Recommended Installation:
brew install mu
- mbsync:
- To Synchronize a maildir with an imap server we will need the command
mbsync
which is part of the isync application. - Recommended Installation:
brew install isync
- To Synchronize a maildir with an imap server we will need the command
- msmtp:
- Like the name suggests, msmtp is a smtp client. According to the website, it transmits a mail to an smtp server which takes care of further delivery.
- Recommended Installation:
brew install msmtp
Setup imap sync
The settings used by mbsync
are stored in the file ~/.mbsyncrc
by default.
NOTE: Which imap servers, ports and credentials to use will be most definitely documented by your email provider.
configuration file
IMAPAccount icloud
Host imap.mail.me.com
User dummy
PassCmd "security find-generic-password -s mu4e-icloud -a dummy -w"
Port 993
SSLType IMAPS
SSLVersions TLSv1.2
AuthMechs PLAIN
SystemCertificates no
CertificateFile ~/.maildir/certificates/root-certificates.pem
IMAPStore icloud-remote
Account icloud
MaildirStore icloud-local
SubFolders Verbatim
Path ~/.maildir/icloud/
Inbox ~/.maildir/icloud/INBOX
Channel icloud
Far :icloud-remote:
Near :icloud-local:
Patterns *
Create Near
Sync All
Expunge Both
SyncState *
# =====================================================================
IMAPAccount gmail
Host imap.gmail.com
User dummy@gmail.com
PassCmd "security find-generic-password -s mu4e-gmail -a dummy -w"
Port 993
SSLType IMAPS
SSLVersions TLSv1.2
AuthMechs PLAIN
SystemCertificates no
CertificateFile ~/.maildir/certificates/root-certificates.pem
IMAPStore gmail-remote
Account gmail
MaildirStore gmail-local
SubFolders Verbatim
Path ~/.maildir/gmail/
Inbox ~/.maildir/gmail/INBOX
Channel gmail
Far :gmail-remote:
Near :gmail-local:
Patterns *
Create Near
Sync All
Expunge Both
SyncState *
# =====================================================================
IMAPAccount example
Host imap.example.com
User dummy@example.com
PassCmd "security find-generic-password -s mu4e-example -a dummy@example.com -w"
Port 993
SSLType IMAPS
AuthMechs Login
CertificateFile ~/.maildir/certificates/root-certificates.pem
IMAPStore example-remote
Account example
MaildirStore example-local
SubFolders Verbatim
Path ~/.maildir/example/
Inbox ~/.maildir/example/INBOX
Channel example
Far :example-remote:
Near :example-local:
Patterns *
Create Near
Sync All
Expunge Both
SyncState *
let me explain
Bare with me here. This part might seem a tad to much, but I would have been happy to have it, back then when. You can skip it of course if you want.
- IMAPAccount: You choose the name. But be consistent. See the sections above.
- Host: This is the imap server according to your provider.
- User: This is the login/user name according to your provider
- PassCmd: Remember we added the password in the section storing passwords in the keychain. We retrieve it withe command
security find-generic-password
. - Port: The imap port according to your provider.
- SSLType: Leave this as is in the example file.
- AuthMechs: This is the way the account authenticates with the imap server. Leave the default here.
- CertificateFile: this is the file containing the trusted root certificates of the trusted Certificate authorities. See the section storing trusted root certificates.
- IMAPStore: Value for
IMAPAccount
plus-remote
. - Account: Same as
IMAPAccount
. - MaildirStore: Value for
IMAPAccount
plus-local
. - SubFolders: Leave this as is in the example file.
- Path: We created this in the section making the directories.
- Inbox: This is
PATH
plus/INBOX
. - Channel: Same as
IMAPAccount
. - Far: The syntax with the 2
:
is important. Value forIMAPAccount
plus-remote
. - Near: The syntax with the 2
:
is important. Value forIMAPAccount
plus-local
. - Patterns: Leave this as is in the example file.
- Create: Leave this as is in the example file.
- Sync: Leave this as is in the example file.
- Expunge: Leave this as is in the example file.
- SyncState: Leave this as is in the example file.
Initial sync
Now that all the above is setup, it is time to sync our mail for the first time. This will download all our mails from the imap servers.
first step - mbsync
This is as simple as:
mbsync -aV
# -a for 'all' and
# -V for 'vorbose' - might be helpfull to see whats happening
second step - mu
Now we need to initialize our email settings to get our emails indexed. This is for mu and Mu4e to be able to search emails and more.
# initialize the email settings:
mu init -m ~/.maildir \
--my-address dummy@icloud.com \
--my-address dummy@gmail.com \
--my-address dummy@example.com
# index the emails in your .maildir:
mu index
Setup msmtp for sending
msmtp
by default uses the file ~/.msmtprc
to store the settings.
NOTE: Which smtp servers, ports and credentials to use will be most definitely documented by your email provider.
configuration file
# Set default values for all the accounts.
defaults
logfile ~/.maildir/msmtp.log
tls_trust_file ~/.maildir/certificates/root-certificates.pem
# ======================================================================
account icloud
auth on
host smtp.mail.me.com
port 465
protocol smtp
from dummy@icloud.com
user dummy
passwordeval security find-generic-password -s mu4e-icloud -a dummy -w
tls on
tls_starttls off
# ======================================================================
account gmail
auth on
host smtp.gmail.com
port 465
protocol smtp
from dummy@gmail.com
user dummy
passwordeval security find-generic-password -s mu4e-gmail -a dummy -w
tls on
tls_starttls off
# ======================================================================
account example
auth on
host smtp.example.com
port 465
protocol smtp
from dummy@example.com
user dummy@example.com
passwordeval security find-internet-password -s mu4e-example -a dummy@example.com -w
tls on
tls_starttls off
# ======================================================================
account default : gmail
let me explain
Same as before in the mbsync section. Bit much, but useful to many.
- defaults: Defaults for all accounts.
- logfile: Leave this as is in the example file.
- tls_trust_file: this is the file containing the trusted root certificates of the trusted Certificate authorities. See the section storing trusted root certificates.
- account: Same as the directory you created in making the directories.
- auth: Leave this as is in the example file.
- host: smtp server according to your provider.
- port: smtp port according to your provider.
- protocol: Leave this as is in the example file.
- from: login/user name according to your provider.
- user: login/user name according to your provider.
- passwordeval: Remember we added the password in the section storing passwords in the keychain. We retrieve it withe command
security find-generic-password
. - tls: this should be
on
. - tls_starttls: Usually this should be
off
. Check with the providers documentation if sending doesn’t work. - account default: If you are using gmail, this should be the default. Otherwise you can choose. Without going to to much details, gmail is a bit touchy.
Setup Mu4e in Emacs
Put the following settings/configuration in your init file. Should be ~/.emacs
or ~/.emacs.d/init.el
. I will divide them into sections for better understanding. ;;
is used for comments.
require packages upfront
;; load mu4e from the installation path.
;; yours might differ check with the Emacs installation
(use-package mu4e
:load-path "/usr/local/share/emacs/site-lisp/mu/mu4e/")
;; for sending mails
(require 'smtpmail)
mu4e-general-settings
;; we installed this with homebrew
(setq mu4e-mu-binary (executable-find "mu"))
;; this is the directory we created before:
(setq mu4e-maildir "~/.maildir")
;; this command is called to sync imap servers:
(setq mu4e-get-mail-command (concat (executable-find "mbsync") " -a"))
;; how often to call it in seconds:
(setq mu4e-update-interval 300)
;; save attachment to desktop by default
;; or another choice of yours:
(setq mu4e-attachment-dir "~/Desktop")
;; rename files when moving - needed for mbsync:
(setq mu4e-change-filenames-when-moving t)
;; list of your email adresses:
(setq mu4e-user-mail-address-list '("dummy@icloud.com"
"dummy@gmail.com"
"dummy@example.com"))
mu4e-favorites
;; check your ~/.maildir to see how the subdirectories are called
;; for the generic imap account:
;; e.g `ls ~/.maildir/example'
(setq mu4e-maildir-shortcuts
'(("/icloud/INBOX" . ?i)
("/icloud/Sent Messages" . ?I)
("/gmail/INBOX" . ?g)
("/gmail/[Gmail]/Sent Mail" . ?G)
("/example/INBOX" . ?e)
("/example/Sent" . ?E)))
The letters assigned here – “?x” – are arbitrary.
mu4e-bookmarks
;; the following is to show shortcuts in the main view.
(add-to-list 'mu4e-bookmarks
(make-mu4e-bookmark
:name "Inbox - iCloud"
:query "maildir:/icloud/INBOX"
:key ?i))
(add-to-list 'mu4e-bookmarks
(make-mu4e-bookmark
:name "Inbox - Gmail"
:query "maildir:/gmail/INBOX"
:key ?g))
(add-to-list 'mu4e-bookmarks
(make-mu4e-bookmark
:name "Inbox - example"
:query "maildir:/example/INBOX"
:key ?e))
The letters assigned here – “?x” – are arbitrary.
mu4e-context
This controls the account context one is in. Helpful for instance, when composing an email. You can then select the context, which sets at the same time the sender.
(setq mu4e-contexts
`(,(make-mu4e-context
:name "icloud"
:enter-func
(lambda () (mu4e-message "Enter dummy@icloud.com context"))
:leave-func
(lambda () (mu4e-message "Leave dummy@icloud.com context"))
:match-func
(lambda (msg)
(when msg
(mu4e-message-contact-field-matches msg
:to "dummy@icloud.com")))
:vars '((user-mail-address . "dummy@icloud.com" )
(user-full-name . "Dummy McDummerson")
(mu4e-drafts-folder . "/icloud/Drafts")
(mu4e-refile-folder . "/icloud/Archive")
(mu4e-sent-folder . "/icloud/Sent Messages")
(mu4e-trash-folder . "/icloud/Deleted Messages")))
,(make-mu4e-context
:name "gmail"
:enter-func
(lambda () (mu4e-message "Enter dummy@gmail.com context"))
:leave-func
(lambda () (mu4e-message "Leave dummy@gmail.com context"))
:match-func
(lambda (msg)
(when msg
(mu4e-message-contact-field-matches msg
:to "dummy@gmail.com")))
:vars '((user-mail-address . "dummy@gmail.com")
(user-full-name . "Dummy McDummerson")
(mu4e-drafts-folder . "/gmail/Drafts")
(mu4e-refile-folder . "/gmail/Archive")
(mu4e-sent-folder . "/gmail/Sent")
(mu4e-trash-folder . "/gmail/Trash")))
,(make-mu4e-context
:name "example"
:enter-func
(lambda () (mu4e-message "Enter dummy@example.de context"))
:leave-func
(lambda () (mu4e-message "Leave dummy@example.de context"))
:match-func
(lambda (msg)
(when msg
(mu4e-message-contact-field-matches msg
:to "dummy@example.de")))
:vars '((user-mail-address . "dummy@example.de")
(user-full-name . "Dummy McDummerson")
;; check your ~/.maildir to see how the subdirectories are called
;; e.g `ls ~/.maildir/example'
(mu4e-drafts-folder . "/example/Drafts")
(mu4e-refile-folder . "/example/Archive")
(mu4e-sent-folder . "/example/Sent")
(mu4e-trash-folder . "/example/Trash")))))
(setq mu4e-context-policy 'pick-first) ;; start with the first (default) context;
(setq mu4e-compose-context-policy 'ask) ;; ask for context if no context matches;
mu4e-sending
;; gpg encryptiom & decryption:
;; this can be left alone
(require 'epa-file)
(epa-file-enable)
(setq epa-pinentry-mode 'loopback)
(auth-source-forget-all-cached)
;; don't keep message compose buffers around after sending:
(setq message-kill-buffer-on-exit t)
;; send function:
(setq send-mail-function 'sendmail-send-it
message-send-mail-function 'sendmail-send-it)
;; send program:
;; this is exeranal. remember we installed it before.
(setq sendmail-program (executable-find "msmtp"))
;; select the right sender email from the context.
(setq message-sendmail-envelope-from 'header)
;; chose from account before sending
;; this is a custom function that works for me.
;; well I stole it somewhere long ago.
;; I suggest using it to make matters easy
;; of course adjust the email adresses and account descriptions
(defun timu/set-msmtp-account ()
(if (message-mail-p)
(save-excursion
(let*
((from (save-restriction
(message-narrow-to-headers)
(message-fetch-field "from")))
(account
(cond
((string-match "dummy@icloud.com" from) "icloud")
((string-match "dummy@gmail.com" from) "gmail")
((string-match "dummy@example.com" from) "example"))))
(setq message-sendmail-extra-arguments (list '"-a" account))))))
(add-hook 'message-send-mail-hook 'timu/set-msmtp-account)
;; mu4e cc & bcc
;; this is custom as well
(add-hook 'mu4e-compose-mode-hook
(defun timu/add-cc-and-bcc ()
"My Function to automatically add Cc & Bcc: headers.
This is in the mu4e compose mode."
(save-excursion (message-add-header "Cc:\n"))
(save-excursion (message-add-header "Bcc:\n"))))
;; mu4e address completion
(add-hook 'mu4e-compose-mode-hook 'company-mode)
optional
This are optional variables to set. At least the way I have set them. They are not needed for the system to work, but are a suggestion. My sane defaults sort of speak.
;; store link to message if in header view, not to header query:
(setq org-mu4e-link-query-in-headers-mode nil)
;; don't have to confirm when quitting:
(setq mu4e-confirm-quit nil)
;; number of visible headers in horizontal split view:
(setq mu4e-headers-visible-lines 20)
;; don't show threading by default:
(setq mu4e-headers-show-threads nil)
;; hide annoying "mu4e Retrieving mail..." msg in mini buffer:
(setq mu4e-hide-index-messages t)
;; customize the reply-quote-string:
(setq message-citation-line-format "%N @ %Y-%m-%d %H:%M :\n")
;; M-x find-function RET message-citation-line-format for docs:
(setq message-citation-line-function 'message-insert-formatted-citation-line)
;; by default do not show related emails:
(setq mu4e-headers-include-related nil)
;; by default do not show threads:
(setq mu4e-headers-show-threads nil)
That is it! You should now be ready to go and discover.
Conclusion
The opinions on which email client to use are many. I was a happy user of the macOS built-in Mail.app till I discovered Emacs. Even then it took me a while to get into migrating my mail workflow into Mu4e.
Now that I am quite invested and committed however, I cannot imagine myself using another client. It is true that I keep the Mail.app around as a backup, because I like to tinker with my Emacs config, but I have not needed it so far. The Mu4e system works and the advantages cannot be denied.
- Emails in plain text
- When all fails, I can read my mails in an editor.
- I can even keep my mails in a version control. Deleted mails you did not want to. Go back a few commits.
- Another workflow into Emacs
- We Emacs enthusiast know, why this is a brilliant idea. You can use all the amenities with your mails.
- Much more.
Since Mu4e is inside Emacs and adheres to the principles as well, there is nothing you cannot do with it.
- You got all manner of packages – this is just a selection – to extend the capabilities.
- You can easily write or steal Emacs-lisp code for Mu4e to make it yours. This might not be obvious from the start, but be patient and you will see.
Sources
Starting I scoured the web for any info I could find.
- Mu4e user manual
- Mu Cheatsheet
- Drowning in Email; mu4e to the Rescue
- mu4e 0.9.18: E-Mailing with Emacs now even better
- Pragmatic Emacs’s Mu4e posts
- Mike Zamansky’s YouTube videos about mu4e
- System Crafters’s YouTube series about mu4e
- All kind of posts on reddit.com/r/emacs