My Email Setup

For the past few months, I have used the following email setup:

Incoming mail → mbsync (MRA) → mu (email indexer) → mu4e (MUA) → msmtp (SMTP client) → Outgoing mail

Summary

  • mbsync synchronises the local maildirs via IMAP with the email accounts themselves. I found it to be faster than OfflineIMAP.
  • mu indexes messages and makes them easier to search.
  • mu4e is the Emacs-based email client included with mu.
  • msmtp sends outgoing messages over SMTP.

Setup

Installation

First, install the relevant tools. Under Debian, one would install the following packages:

# apt install isync mu4e msmtp

Preparation

Next, create the directories that will contain your maildirs. Change the path and account names to match your preferences:

$ mkdir -p ~/Mail/{Main,Personal,Work}

I also recommend creating your signature blocks at this point. This tutorial uses files in ~/doc/signatures as an example. See the message-signature-file variables in the below mu4e config for examples.

Configuring mbsync

Write your ~/.mbsyncrc. Here is an example for 3 accounts:

IMAPAccount Main
Host imap.example.net
User foo@example.net
Pass hunter2
SSLType IMAPS
CertificateFile /etc/ssl/certs/ca-certificates.crt

IMAPStore Main-remote
Account Main

MaildirStore Main-local
Path ~/Mail/Main/
Inbox ~/Mail/Main/inbox

Channel Main-default
Master :Main-remote:
Slave :Main-local:Inbox

Channel Main-sent
Master :Main-remote:"[Gmail]/Sent Mail"
slave :Main-local:Sent

Channel Main-trash
Master :Main-remote:"[Gmail]/Trash"
slave :Main-local:Trash

Channel Main-archive
Master :Main-remote:"[Gmail]/All Mail"
slave :Main-local:All

Channel Main-junk
Master :Main-remote:"[Gmail]/Spam"
slave :Main-local:Junk

Channel Main-drafts
Master :Main-remote:"[Gmail]/Drafts"
slave :Main-local:Drafts

Create Both
Expunge Both
SyncState *

Group Main
Channel Main-default
Channel Main-sent
Channel Main-trash
Channel Main-archive
Channel Main-junk

IMAPAccount Personal
Host imap.example.net
User bar@example.net
Pass hunter2
SSLType IMAPS
CertificateFile /etc/ssl/certs/ca-certificates.crt

IMAPStore Personal-remote
Account Personal

MaildirStore Personal-local
Path ~/Mail/Personal/
Inbox ~/Mail/Personal/inbox

Channel Personal-default
Master :Personal-remote:
Slave :Personal-local:Inbox

Channel Personal-sent
Master :Personal-remote:"[Gmail]/Sent Mail"
slave :Personal-local:Sent

Channel Personal-trash
Master :Personal-remote:"[Gmail]/Trash"
slave :Personal-local:Trash

Channel Personal-archive
Master :Personal-remote:"[Gmail]/All Mail"
slave :Personal-local:All

Channel Personal-junk
Master :Personal-remote:"[Gmail]/Spam"
slave :Personal-local:Junk

Channel Personal-sent
Master :Personal-remote:"[Gmail]/Sent Mail"
slave :Personal-local:Sent

Create Both
Expunge Both
SyncState *

Group Personal
Channel Personal-default
Channel Personal-sent
Channel Personal-trash
Channel Personal-archive
Channel Personal-junk

IMAPAccount Work
Host imap.example.net
User baz@example.net
Pass hunter2
SSLType IMAPS
CertificateFile /etc/ssl/certs/ca-certificates.crt

IMAPStore Work-remote
Account Work

MaildirStore Work-local
Path ~/Mail/Work/
Inbox ~/Mail/Work/inbox

Channel Work-default
Master :Work-remote:
Slave :Work-local:Inbox

Channel Work-sent
Master :Work-remote:"[Gmail]/Sent Mail"
slave :Work-local:Sent

Channel Work-trash
Master :Work-remote:"[Gmail]/Trash"
slave :Work-local:Trash

Channel Work-archive
Master :Work-remote:"[Gmail]/All Mail"
slave :Work-local:All

Channel Work-junk
Master :Work-remote:"[Gmail]/Spam"
slave :Work-local:Junk

Channel Work-drafts
Master :Work-remote:"[Gmail]/Drafts"
slave :Work-local:Drafts

Create Both
Expunge Both
SyncState *

Group Work
Channel Work-default
Channel Work-sent
Channel Work-trash
Channel Work-archive
Channel Work-junk

The location of CertificateFile may vary based on your system.

Finally, make ~/.mbsyncrc readable and writable only by the owner, for privacy:

$ chmod 600 ~/.mbsyncrc

Configuring msmtp

~/.msmtprc should look something like this:

defaults
auth on
tls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
logfile ~/.msmtp.log

# Main
account Main
host smtp.example.net
port 587
from foo@example.net
user foo@example.net
password hunter2

# Personal
account Personal
host smtp.example.net
port 587
from bar@example.net
user bar@example.net
password hunter2

# Work
account Work
host smtp.example.net
port 587
from baz@example.net
user baz@example.net
password hunter2

account default : Main

As with mbsync’s CertificateFile, the location of tls_trust_file may vary based on your system.

As with ~/.mbsyncrc above, you should make ~/.msmtprc readable and writable only by the owner. This is especially important with msmtp, as it will refuse to run otherwise.

$ chmod 600 ~/.msmtprc
msmtpqueue

msmtpqueue is a small collection of scripts to queue outgoing messages and then send them later. Under Debian, it is available in /usr/share/doc/msmtp/examples/msmtpqueue. I copied the scripts to my personal ~/local/bin directory and then edited them for my own use. See /usr/share/doc/msmtp/examples/msmtpqueue/README for more information.

There is also msmtpq, which I have not tried.

Configuring mu4e

mu4e is configured in your Emacs init file. Here is an example:

(require 'mu4e)

;; Make mu4e Emacs's default email client
(setq read-mail-command 'mu4e
      mail-user-agent 'mu4e-user-agent)

;; Where the messages are
(setq mu4e-maildir "~/Mail")

;; How to get mail
(setq mu4e-get-mail-command "mbsync -a"
      mu4e-update-interval 300)

;; How to send mail
;; (Be sure to point sendmail-program to the proper path for msmtp-enqueue.sh!)
(setq message-send-mail-function 'message-send-mail-with-sendmail
      sendmail-program "/home/user/bin/msmtp-enqueue.sh")

;; Gmail needs this
(setq mu4e-sent-messages-behavior 'delete)

;; Display To: header in headers view
(setq mu4e-headers-fields '((:human-date . 12)
                            (:flags . 6)
                            (:from . 15)
                            (:to . 12)
                            (:subject)))

;; Bookmarks
(setq mu4e-bookmarks
      `( ,(make-mu4e-bookmark
           :name "Unread messages"
           :query "flag:unread AND NOT \"maildir:/Main/Junk\" AND NOT \"maildir:/Personal/Junk\" AND NOT \"maildir:/Work/Junk\""
           :key ?u)
         ,(make-mu4e-bookmark
           :name "Today's messages"
           :query "date:today..now AND NOT \"maildir:/Main/Junk\" AND NOT \"maildir:/Personal/Junk\" AND NOT \"maildir:/Work/Junk\""
           :key ?t)
         ,(make-mu4e-bookmark
           :name "Last 7 days"
           :query "date:7d..now AND NOT \"maildir:/Main/Junk\" AND NOT \"maildir:/Personal/Junk\" AND NOT \"maildir:/Work/Junk\""
           :key ?w)
         ,(make-mu4e-bookmark
           :name "Inboxes"
           :query "\"maildir:/Main/INBOX\" OR \"maildir:/Personal/INBOX\" OR \"maildir:/Work/INBOX\""
           :key ?i)
         ,(make-mu4e-bookmark
           :name "Sent messages"
           :query "\"maildir:/Main/Sent\" OR \"maildir:/Personal/Sent\" OR \"maildir:/Work/Sent\""
           :key ?s)
         ,(make-mu4e-bookmark
           :name "Spam"
           :query "\"maildir:/Main/Junk\" OR \"maildir:/Personal/Junk\" OR \"maildir:/Work/Junk\""
           :key ?j)))

For multiple email accounts, we will use contexts. If you publish your Emacs config, e.g., in version control, be aware that this section will contain passwords and other personally identifiable information.

(require 'mu4e-context)

(setq mu4e-contexts
      `( ,(make-mu4e-context
           :name "Main"
           :enter-func (lambda () (mu4e-message "Entering Main context"))
           :leave-func (lambda () (mu4e-message "Leaving Main context"))
           :match-func (lambda (msg)
                         (when msg
                           (mu4e-message-contact-field-matches msg :to "foo@example.net")))
           :vars '((user-email-address . "foo@example.net")
                   (user-mail-address . "foo@example.net")
                   (user-full-name . "Someone")
                   (mu4e-sent-folder . "/Main/Sent")
                   (mu4e-drafts-folder . "/Main/Drafts")
                   (mu4e-trash-folder . "/Main/Trash")
                   (mu4e-refile-folder . "/Main/All")
                   (message-sendmail-extra-arguments . "-a Main")
                   (mail-host-address . "example.net")
                   (mu4e-compose-signature . (with-temp-buffer (insert-file-contents "~/doc/signatures/Main") (buffer-string)))
                   (message-signature-file . "~/doc/signatures/Main")))
         ,(make-mu4e-context
           :name "Personal"
           :enter-func (lambda () (mu4e-message "Switch to the Personal context"))
           :match-func (lambda (msg)
                         (when msg
                           (mu4e-message-contact-field-matches msg :to "bar@example.net")))
           :vars '((user-email-address . "bar@example.net")
                   (user-mail-address . "bar@example.net")
                   (user-full-name . "John Smith")
                   (mu4e-sent-folder . "/Personal/Sent")
                   (mu4e-drafts-folder . "/Personal/Drafts")
                   (mu4e-trash-folder . "/Personal/Trash")
                   (mu4e-refile-folder . "/Personal/All")
                   (message-sendmail-extra-arguments . "-a Personal")
                   (mail-host-address . "example.net")
                   (mu4e-compose-signature . (with-temp-buffer (insert-file-contents "~/doc/signatures/Personal") (buffer-string)))
                   (message-signature-file . "~/doc/signatures/Personal")))
         ,(make-mu4e-context
           :name "Work"
           :enter-func (lambda () (mu4e-message "Switch to the Work context"))
           :match-func (lambda (msg)
                         (when msg
                           (mu4e-message-contact-field-matches msg :to "baz@example.net")))
           :vars '((user-email-address . "baz@example.net")
                   (user-mail-address . "baz@example.net")
                   (user-full-name . "John Smith")
                   (mu4e-sent-folder . "/Work/Sent")
                   (mu4e-drafts-folder . "/Work/Drafts")
                   (mu4e-trash-folder . "/Work/Trash")
                   (mu4e-refile-folder . "/Work/All")
                   (message-sendmail-extra-arguments . "-a Work")
                   (mail-host-address . "example.net")
                   (mu4e-compose-signature . (with-temp-buffer (insert-file-contents "~/doc/signatures/Work") (buffer-string)))
                   (message-signature-file . "~/doc/signatures/Work")))))

(setq mu4e-context-policy 'pick-first
      mu4e-compose-context-policy 'ask)

Initialisation

Synchronise the maildirs and then index the messages:

$ mbsync -a
$ mu index --maildir="$HOME/Mail"

cronjobs

You can have cron synchronise your maildirs and send pending messages automatically. Put this shell script in ~/bin or a similar directory, name it get-send-mail (or similar), and make it executable with chmod +x:

#!/bin/sh

# Check if we are online; replace with something more appropriate for your setup
ping -c 1 -w 2 example.net > /dev/null

if [ "$?" -eq 0 ]; then
	mbsync -aq
	/home/user/bin/msmtp-runqueue.sh > /dev/null
fi

Be sure to point to the proper path for msmtp-runqueue.sh. You can now put something like this in your crontab:

@reboot /home/user/bin/get-send-mail
*/5 * * * * /home/user/bin/get-send-mail

It should run at boot and every 5 minutes.

Usage

Simply restart Emacs, and then start mu4e with M-x mu4e. For more information, see the mu4e manual: (info "(mu4e)")