Managing Multiple Email Accounts with mu4e and mbsync

If you find this guide helpful, please consider supporting System Crafters via the links on the How to Help page!

How mu4e manages e-mail accounts

  • mu4e is a frontend for me mu mail indexer
  • mu just scans mail folders and indexes them
  • You tell mu4e about your mail folders and what searches you want to use
  • You can also tell mu4e how to change its configuration for different “contexts”

Laying the ground work

Since we will have two accounts under ~/Mail now, we need to move the existing mail under a subfolder.

But first, we need to stop mu4e from syncing! Run M-x mu4e-quit to stop the background sync timer.

Now we can move the Gmail folders into ~/Mail/Gmail:

cd ~
mv Mail Gmail
mkdir Mail
mv Gmail Mail

We also need to update our .mbsyncrc to use the new folders:

Path ~/Mail/Gmail/
Inbox ~/Mail/Gmail/Inbox

Test with mbsync to make sure everything syncs correctly:

mbsync -a

Lastly, reindex with mu:

mu index --maildir=~/Mail [email protected]

Setting up our first context

We will update our configuration to configure the Gmail account using mu4e-contexts:

(use-package mu4e
  :ensure nil
  :config

  ;; This is set to 't' to avoid mail syncing issues when using mbsync
  (setq mu4e-change-filenames-when-moving t)

  ;; Refresh mail using isync every 10 minutes
  (setq mu4e-update-interval (* 10 60))
  (setq mu4e-get-mail-command "mbsync -a")
  (setq mu4e-maildir "~/Mail")

  (setq mu4e-contexts
        (list
         ;; Work account
         (make-mu4e-context
          :name "Work"
          :match-func
            (lambda (msg)
              (when msg
                (string-prefix-p "/Gmail" (mu4e-message-field msg :maildir))))
          :vars '((user-mail-address . "[email protected]")
                  (user-full-name    . "System Crafters Gmail")
                  (mu4e-drafts-folder  . "/Gmail/[Gmail]/Drafts")
                  (mu4e-sent-folder  . "/Gmail/[Gmail]/Sent Mail")
                  (mu4e-refile-folder  . "/Gmail/[Gmail]/All Mail")
                  (mu4e-trash-folder  . "/Gmail/[Gmail]/Trash")))))

  (setq mu4e-maildir-shortcuts
        '(("/Gmail/Inbox"             . ?i)
          ("/Gmail/[Gmail]/Sent Mail" . ?s)
          ("/Gmail/[Gmail]/Trash"     . ?t)
          ("/Gmail/[Gmail]/Drafts"    . ?d)
          ("/Gmail/[Gmail]/All Mail"  . ?a))))

Docs: https://www.djcbsoftware.nl/code/mu/mu4e/Contexts.html Example: https://www.djcbsoftware.nl/code/mu/mu4e/Contexts-example.html

Try out the new context by evaluating the new configuration and run M-x mu4e.

You can change how the default context is picked by setting mu4e-context-policy to one of the following values:

  • ask-if-none - Ask the first time you enter the view (default)
  • pick-first - Pick the first context in the mu4e-contexts list
  • always-ask - Always ask when entering the main view

Syncing the second account

We’ll be using a Fastmail account (https://ref.fm/u7703101) for this example. It requires an App Password to connect to the IMAP endpoint:

https://www.fastmail.com/settings/security/devicekeys/new

Now we can add the IMAP configuration for the Fastmail account to our .mbsyncrc file:

... Gmail configuration from previous video ...

IMAPAccount fastmail
Host imap.fastmail.com
Port 993
User [email protected]
PassCmd "cat ~/.oh-no-another-insecure-password"
SSLType IMAPS
SSLVersions TLSv1.2
CertificateFile /etc/ssl/certs/ca-certificates.crt

IMAPStore fastmail-remote
Account fastmail

MaildirStore fastmail-local
Path ~/Mail/Fastmail/
Inbox ~/Mail/Fastmail/INBOX/
Trash ~/Mail/Fastmail/Trash/
SubFolders Verbatim

# With mbsync 1.4.0 and later: Use 'Far' instead of 'Master', and
# 'Near' instead of 'Slave'.
Channel fastmail
Master :fastmail-remote:
Slave :fastmail-local:
Patterns *
Expunge None
CopyArrivalDate yes
Sync All
Create Both
SyncState *

Now you can sync the new account:

mkdir ~/Mail/Fastmail
mbsync -a

NOTE: It’s possible you will see an error like this -

mu: mu_store_new_writable: xapian error 'Unable to get write lock on /home/daviwil/.mu/xapian: already locked' (11)

Just kill the running mu process and run mu index again:

pkill mu
# run mu index again

Adding the second account to mu4e

Now we can add a new context for the account to mu4e-contexts:

(setq mu4e-contexts
      (list
       ;; Work account
       (make-mu4e-context
        :name "Work"
        :match-func
          (lambda (msg)
            (when msg
              (string-prefix-p "/Gmail" (mu4e-message-field msg :maildir))))
        :vars '((user-mail-address . "[email protected]")
                (user-full-name    . "System Crafters Gmail")
                (mu4e-drafts-folder  . "/Gmail/[Gmail]/Drafts")
                (mu4e-sent-folder  . "/Gmail/[Gmail]/Sent Mail")
                (mu4e-refile-folder  . "/Gmail/[Gmail]/All Mail")
                (mu4e-trash-folder  . "/Gmail/[Gmail]/Trash")))

       ;; Personal account
       (make-mu4e-context
        :name "Personal"
        :match-func
          (lambda (msg)
            (when msg
              (string-prefix-p "/Fastmail" (mu4e-message-field msg :maildir))))
        :vars '((user-mail-address . "[email protected]")
                (user-full-name    . "System Crafters Fastmail")
                (mu4e-drafts-folder  . "/Fastmail/Drafts")
                (mu4e-sent-folder  . "/Fastmail/Sent")
                (mu4e-refile-folder  . "/Fastmail/Archive")
                (mu4e-trash-folder  . "/Fastmail/Trash")))))

After evaluating this configuration, we can launch mu4e again and switch contexts using the ; (semicolon) character.

How to use contexts

Examples:

  • Compose new mail in a context
  • Archive a message in a context (show which folder it goes to)
  • Reply to a message in a merged search

You can create bookmarks to show merged views of folders across accounts:

In mu4e 1.2.0:

(add-to-list 'mu4e-bookmarks '("m:/Fastmail/INBOX or m:/Gmail/Inbox" "All Inboxes" ?i))

As of mu4e 1.3.7:

(add-to-list 'mu4e-bookmarks
             '(:name "All Inboxes" :query "m:/Fastmail/INBOX or m:/Gmail/Inbox" :key ?i))

This is your e-mail client to build!

What’s next?

  • Composing e-mails
  • Displaying unread mail count and notifications
  • Even more uses for contexts and search queries
  • Org Mode integration

Complete Configuration

Here’s the complete configuration for this episode:

Emacs.org

(use-package mu4e
  :ensure nil
  :config

  ;; This is set to 't' to avoid mail syncing issues when using mbsync
  (setq mu4e-change-filenames-when-moving t)

  ;; Refresh mail using isync every 10 minutes
  (setq mu4e-update-interval (* 10 60))
  (setq mu4e-get-mail-command "mbsync -a")
  (setq mu4e-maildir "~/Mail")

  (setq mu4e-contexts
        (list
         ;; Work account
         (make-mu4e-context
          :name "Work"
          :match-func
            (lambda (msg)
              (when msg
                (string-prefix-p "/Gmail" (mu4e-message-field msg :maildir))))
          :vars '((user-mail-address . "[email protected]")
                  (user-full-name    . "System Crafters Gmail")
                  (mu4e-drafts-folder  . "/Gmail/[Gmail]/Drafts")
                  (mu4e-sent-folder  . "/Gmail/[Gmail]/Sent Mail")
                  (mu4e-refile-folder  . "/Gmail/[Gmail]/All Mail")
                  (mu4e-trash-folder  . "/Gmail/[Gmail]/Trash")))

         ;; Personal account
         (make-mu4e-context
          :name "Personal"
          :match-func
            (lambda (msg)
              (when msg
                (string-prefix-p "/Fastmail" (mu4e-message-field msg :maildir))))
          :vars '((user-mail-address . "[email protected]")
                  (user-full-name    . "System Crafters Fastmail")
                  (mu4e-drafts-folder  . "/Fastmail/Drafts")
                  (mu4e-sent-folder  . "/Fastmail/Sent")
                  (mu4e-refile-folder  . "/Fastmail/Archive")
                  (mu4e-trash-folder  . "/Fastmail/Trash")))))

  (setq mu4e-maildir-shortcuts
      '(("/Inbox"             . ?i)
        ("/Gmail/[Gmail]/Sent Mail" . ?s)
        ("/Gmail/[Gmail]/Trash"     . ?t)
        ("/Gmail/[Gmail]/Drafts"    . ?d)
        ("/Gmail/[Gmail]/All Mail"  . ?a))))

~/.mbsyncrc

IMAPAccount gmail
Host imap.gmail.com
User [email protected]
PassCmd "cat ~/.oh-no-insecure-password"
SSLType IMAPS
CertificateFile /etc/ssl/certs/ca-certificates.crt

IMAPStore gmail-remote
Account gmail

MaildirStore gmail-local
Subfolders Verbatim
Path ~/Mail/Gmail/
Inbox ~/Mail/Gmail/Inbox

# For mbsync 1.4.0 and later: Use 'Far' instead of 'Master', and
# 'Near' instead of 'Slave'.
Channel gmail
Master :gmail-remote:
Slave :gmail-local:
Patterns * ![Gmail]* "[Gmail]/Sent Mail" "[Gmail]/Starred" "[Gmail]/All Mail" "[Gmail]/Trash"
Create Both
SyncState *

IMAPAccount fastmail
Host imap.fastmail.com
Port 993
User [email protected]
PassCmd "cat ~/.oh-no-another-insecure-password"
SSLType IMAPS
SSLVersions TLSv1.2
CertificateFile /etc/ssl/certs/ca-certificates.crt

IMAPStore fastmail-remote
Account fastmail

MaildirStore fastmail-local
Path ~/Mail/Fastmail/
Inbox ~/Mail/Fastmail/INBOX/
Trash ~/Mail/Fastmail/Trash/
SubFolders Verbatim

# With mbsync 1.4.0 and later: Use 'Far' instead of 'Master', and
# 'Near' instead of 'Slave'.
Channel fastmail
Master :fastmail-remote:
Slave :fastmail-local:
Patterns *
Expunge None
CopyArrivalDate yes
Sync All
Create Both
SyncState *
Subscribe to the System Crafters Newsletter!
Stay up to date with the latest System Crafters news and updates! Read the Newsletter page for more information.
Name (optional)
Email Address