Mutt with multiple accounts, mbsync, notmuch, GPG and sub-minute updates

Assuming you’ve clicked to read this post, I don’t need to sell you on the benefits of using mutt or a similar CLI client to handle your email load. You may be dealing with some minor issues, however, some of which might be detailed below. Take some ideas or just get a jumpstart in your own setup. Everything you see here was of course patched together from many snippets from manuals, tutorials and shared configs online.

It feels like the config has reached parity with a modern mail client in terms of featureset and speed of use. Being biased, I’d also say that it has actually surpassed the producvity and flexibility of clients such as Thunderbird and Gmail. But it does take a while to adjust and for the new workflows to ‘bed in’.

Self-hosted mail setup

  • 2 x VPS with Dovecot, Postfix, Sieve, Spamassassin configured
  • All TLS, all the time
  • SPF, DKIM in place
  • 1 x Gmail account
  • Total 4 email accounts to check

Local mutt configured to

  • Switch between accounts with F-keys, switching send-from setting at the same time
  • Poll the IMAP server every 20 seconds (without locking when not online)
  • Runs on Debian in i3wm on a 2010 netbook (Lenovo x120e)
  • Show new emails via sidebar (patched/compiled version)
  • Search all mailboxes, within mutt, as quickly as possible
  • Optionally encrypt and/or sign outgoing email

Problems solved

  • Poor email body search (very slow), having to rely on filtering by name or subject (very quick)
  • Mutt instance freezing on network connection change (wired to wireless, etc.)
  • Slight delays in usage when switching between accounts
  • Email backup if catastrophic failure at VPS/Gmail
  • Gmail-equivalent, if not better, spam filtering
  • Sub-minute account polling with mbsync
  • Passwordless keyfile for mbsync use
  • Ability to work offline

Problems yet to solve

  • Reminder to attach a file
  • Optional espeak or notify-send notifications for work-related emails
  • Encrypt all at-rest local email while still being able to search it, not using FDE (nor encfs/ecryptfs)

Notes

  • mbsync, notmuch and notmuch-mutt are all available in the Debian repos. The latter is just a perl-script that integrates notmuch search into mutt.
  • Offlineimap lost out to mbsync/isync when deciding on how to sync emails, namely over the fact that it seems that most people migrate from offlineimap to isync over time and isync has a more elegant solution to unique email IDs.
  • GPG password files are easy to make: vim mypw [enter pw on a single line]; gpg -e mypw[specify ID from gpg --list-keys]; rm mypw

So that’s the outline, here follow the files you’ll need for this setup (excluding the mailserver setup, outside the scope of this post).

Configs

.muttrc

###########################
#                    BASICS
###########################

set mbox_type=maildir

# Putting this here asks for PW once only, not every folder switch
source "gpg2 -dq /home/username/.mutt/passwordfile.gpg |"

# This puts cursor just above signature in vim... the right place, but not common practice. Use [ to jump to top of message.
set editor='vim + -c "set textwidth=72" -c "set wrap" -c "set nocp" -c "?^$"'

# Split screen to view messages - can be slow with direct imap connection on larger mails
#set pager_index_lines=20

set sort='reverse-threads'
set sort_aux='last-date-received'
auto_view text/html
# This line gets rid of iso 8859 issues with attachments
set rfc2047_parameters

set sidebar_width=30
set sidebar_visible=yes
set sidebar_delim='|'
# Sorts folders in sidebar alphabetically
#set sidebar_sort=yes
set sidebar_shortpath = yes

color sidebar_new yellow default

bind index '[' sidebar-prev
bind index ']' sidebar-next
bind index '#' sidebar-open
bind pager '[' sidebar-prev
bind pager ']' sidebar-next
bind pager '#' sidebar-open

# Remap bounce-message function to “B” and toggle sb with b
bind index B bounce-message
macro index b '<enter-command>toggle sidebar_visible<enter><refresh>'
macro pager b '<enter-command>toggle sidebar_visible<enter><redraw-screen>'

# See all mail headers in editor, cache headers and bodies
set edit_headers=yes
set header_cache = "/home/username/.mutt/cache/headers"
set message_cachedir = "/home/username/.mutt/cache/bodies"

# Prettify
color hdrdefault white black  # headers white on black
color header brightgreen black ^From:  # sender's name in green
color quoted cyan black  # quoted text in blue
color signature red black   # signature in red

# Don't wait for sendmail to finish (this runs sendmail in the background)
#set sendmail_wait=-1

# A macro to store attachments in specific folder
macro attach , "<save-entry><bol>/home/username/emailattachments/<eol>" "Save to downloads folder"

#Alias file for addresses
set alias_file="/home/username/.mutt/.muttalias"
source /home/username/.mutt/.muttalias

#Use abook in addition to aliases - opting to not use it for now, as no autocomplete or BCC autofill
# Adding with shift-a was tempremental too
#set query_command= "abook --mutt-query '%s'"
#macro index,pager A "<pipe-message>abook --add-email-quiet<return>" "Add the sender address to abook"

###########################
#                       PGP
###########################

# Removed "--passphrase-fd 0" after "--output -" from a bunch of settings: decode, decrypt, sign, clearsign so as to use gpg-agent
#Also changed from pgpewrapng to /mutt/dir/pgpewrap to make it work for this setup

source /home/luxpir/.mutt/gpg.rc
set pgp_decode_command="gpg %?p? --no-verbose --batch --output - %f"
set pgp_verify_command="gpg --no-verbose --batch --output - --verify %s %f"
set pgp_decrypt_command="gpg --no-verbose --batch --output - %f"
set pgp_sign_command="gpg --no-verbose --batch --output - --armor --detach-sign --textmode %?a?-u %a? %f"
set pgp_clearsign_command="gpg --no-verbose --batch --output - --armor --textmode --clearsign %?a?-u %a? %f"
set pgp_encrypt_only_command="/home/username/mutt-1.5.24/pgpewrap gpg --batch --quiet --no-verbose --output - --encrypt --textmode --armor --always-trust --encrypt-to 0x085F086F -- -r %r -- %f"
set pgp_encrypt_sign_command="/home/username/mutt-1.5.24/pgpewrap gpg --batch --quiet --no-verbose --textmode --output - --encrypt --sign %?a?-u %a? --armor --always-trust --encrypt-to 0x085F086F -- -r %r -- %f"
set pgp_import_command="gpg --no-verbose --import -v %f"
set pgp_export_command="gpg --no-verbose --export --armor %r"
set pgp_verify_key_command="gpg --no-verbose --batch --fingerprint --check-sigs %r"
set pgp_list_pubring_command="gpg --no-verbose --batch --with-colons --list-keys %r"
set pgp_list_secring_command="gpg --no-verbose --batch --with-colons --list-secret-keys %r"
set pgp_sign_as=0x016G0FFF [replace this]
set crypt_replyencrypt=yes
set pgp_timeout=3600
set pgp_auto_decode=yes
set pgp_use_gpg_agent = yes

# set crypt_autosign=yes # not autosigning but nice option

###########################
#                  ACCOUNTS
###########################

## DEFAULT ACCOUNT
source "~/.mutt/default"

# Switch accounts with F keys
macro index <f2> '<sync-mailbox><refresh><enter-command>source ~/.mutt/default<enter><change-folder>!<enter>'
macro index <f3> '<sync-mailbox><refresh><enter-command>source ~/.mutt/second<enter><change-folder>!<enter>'
macro index <f4> '<sync-mailbox><refresh><enter-command>source ~/.mutt/third<enter><change-folder>!<enter>'
macro index <f5> '<sync-mailbox><refresh><enter-command>source ~/.mutt/fourth<enter><change-folder>!<enter>'

#Invoke notmuch, rebuild threads and tag emails as removed from Inbox?!
macro index <F8> \
     "<enter-command>unset wait_key<enter><shell-escape>notmuch-mutt --prompt search<enter><change-folder-readonly>`echo ${XDG_CACHE_HOME:-$HOME/.cache}/notmuch/mutt/results`<enter>" \
     "notmuch: search mail"
macro index <F9> \
     "<enter-command>unset wait_key<enter><pipe-message>notmuch-mutt thread<enter><change-folder-readonly>`echo ${XDG_CACHE_HOME:-$HOME/.cache}/notmuch/mutt/results`<enter><enter-command>set wait_key<enter>" \
     "notmuch: reconstruct thread"
macro index <F6> \
     "<enter-command>unset wait_key<enter><pipe-message>notmuch-mutt tag -Inbox<enter>" \
     "notmuch: remove message from inbox"
#Not sure what this F6 option is yet - still investigating

set certificate_file="~/.mutt/.mutt_known_hosts"
#Not massively secure, but works for my self-signed cert to avoid saving it repeatedly
set ssl_verify_host = no

###########################
#                CONNECTION
###########################

#Keeping below options from live imap connection settings in case they help with sending email via smtp
set ssl_starttls=yes
set ssl_force_tls=yes
unset imap_passive
set imap_check_subscribed
#set mail_check=60
set mail_check=20
# Above change for for isync, more frequent checks ftw
set timeout=10
set net_inc=5
set imap_keepalive = 300

.mbsyncrc

Pay special attention to newlines, as they form blocks in the config - missing one can cause errors. isync is the name of the executable of mbsync, the package. A source of initial confusion.

IMAPAccount acc1
Host mail.server1.com
User info
PassCmd "gpg -d ~/.mutt/isyncpassword.gpg"
UseIMAPS yes
CertificateFile ~/.mutt/.mutt_known_hosts

IMAPStore acc1-remote
Account acc1

MaildirStore acc1-local
# The trailing "/" is important
Path ~/.mail/acc1/
Inbox ~/.mail/acc1/inbox

Channel acc1-default
Master :acc1-remote:
Slave :acc1-local:Inbox
#Patterns INBOX

Channel acc1-sent
Master :acc1-remote:"Sent"
slave  :acc1-local:Sent

Channel acc1-trash
Master :acc1-remote:"Trash"
slave  :acc1-local:Trash
Channel acc1-archive
Master :acc1-remote:"Archives"
slave  :acc1-local:All

Channel acc1-junk
Master :acc1-remote:"Junk"
slave  :acc1-local:Junk

# Automatically create missing mailboxes, both locally and on the server
Create Both
# Automatically delete messages on either side if they are found deleted on the other.
Expunge Both
# Save the synchronization state files in the relevant directory
SyncState *

Group acc1
Channel acc1-default
Channel acc1-trash
Channel acc1-archive
Channel acc1-sent
Channel acc1-junk
############################################

IMAPAccount acc2
Host mail.server2.com
User david
PassCmd "gpg -d ~/.mutt/isyncpassword.gpg"
UseIMAPS yes
CertificateFile ~/.mutt/.mutt_known_hosts

IMAPStore acc2-remote
Account acc2

MaildirStore acc2-local
# The trailing "/" is important
Path ~/.mail/acc2/
Inbox ~/.mail/acc2/inbox

Channel acc2-default
Master :acc2-remote:
Slave :acc2-local:Inbox
#Patterns INBOX

Channel acc2-sent
Master :acc2-remote:"Sent"
slave  :acc2-local:Sent

Channel acc2-trash
Master :acc2-remote:"Trash"
slave  :acc2-local:Trash
Channel acc2-archive
Master :acc2-remote:"Archives"
slave  :acc2-local:All

Channel acc2-junk
Master :acc2-remote:"Junk"
slave  :acc2-local:Junk

# Automatically create missing mailboxes, both locally and on the server
Create Both
# Automatically delete messages on either side if they are found deleted on the other.
Expunge Both
# Save the synchronization state files in the relevant directory
SyncState *

Group acc2
Channel acc2-default
Channel acc2-trash
Channel acc2-archive
Channel acc2-sent
Channel acc2-junk
############################################

IMAPAccount acc3
Host mail.server2.com
User contact
PassCmd "gpg -d ~/.mutt/isyncpassword.gpg"
UseIMAPS yes
CertificateFile ~/.mutt/.mutt_known_hosts

IMAPStore acc3-remote
Account acc3

MaildirStore acc3-local
# The trailing "/" is important
Path ~/.mail/acc3/
Inbox ~/.mail/acc3/inbox

Channel acc3-default
Master :acc3-remote:
Slave :acc3-local:Inbox
#Patterns INBOX

Channel acc3-sent
Master :acc3-remote:"Sent"
slave  :acc3-local:Sent

Channel acc3-trash
Master :acc3-remote:"Trash"
slave  :acc3-local:Trash
Channel acc3-archive
Master :acc3-remote:"Archives"
slave  :acc3-local:All

Channel acc3-junk
Master :acc3-remote:"Junk"
slave  :acc3-local:Junk

#This block shows how to handle a hierarchical folder structure
Channel acc3-non-work
Master :acc3-remote:INBOX/"offtopic"
slave  :acc3-local:offtopic

# Automatically create missing mailboxes, both locally and on the server
Create Both
# Automatically delete messages on either side if they are found deleted on the other.
Expunge Both
# Save the synchronization state files in the relevant directory
SyncState *

Group acc3
Channel acc3-default
Channel acc3-trash
Channel acc3-archive
Channel acc3-sent
Channel acc3-junk
Channel acc3-offtopic
############################################

IMAPAccount gmail
Host imap.gmail.com
User username@gmail.com
Pass applicationspecificpassword
UseIMAPS yes
# The following line should work. If get certificate errors, uncomment the two following lines
CertificateFile /etc/ssl/certs/ca-certificates.crt
#CertificateFile ~/.cert/imap.gmail.com.pem
#CertificateFile ~/.cert/Equifax_Secure_CA.pem

IMAPStore gmail-remote
Account gmail

MaildirStore gmail-local
# The trailing "/" is important
Path ~/.mail/gmail/
Inbox ~/.mail/gmail/inbox

Channel gmail-default
Master :gmail-remote:
Slave :gmail-local:Inbox
#Patterns INBOX

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

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

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

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

# Automatically create missing mailboxes, both locally and on the server
Create Both
# Automatically delete messages on either side if they are found deleted on the other.
Expunge Both
# Save the synchronization state files in the relevant directory
SyncState *

Group gmail
Channel gmail-default
Channel gmail-trash
Channel gmail-archive
Channel gmail-sent
Channel gmail-junk`

The .notmuch-config file is autogenerated when you install notmuch and run notmuch new, so there’s no point adding that here. You really just need to set that up for each email address and it takes no more than a minute.

Next is the script I use to call mbsync via cronjob every minute, the minimum resolution on most default terminals, but then keep firing it every 20 seconds until the next cron run. I should perhaps add an exit 0 to the end, but it seems to work well without for now, with little scope for blocking/stopping in such a simple script.

mbsync.sh

#!/bin/bash

## Check mbsync email 3 times per minute based on a cronjob firing this script once
killall mbsync &>/dev/null
mbsync -a -q
sleep 20
killall mbsync &>/dev/null
mbsync -a -q
sleep 20
killall mbsync &>/dev/null
mbsync -a -q`

So this script clears any blocked instances of mbsync (beware if you have any processes on your system that could clash with this name) due to network outage, runs mbsync for all accounts in quiet mode then waits 20 seconds. This should then fire on 00, 20, 40 seconds before the cron job starts it off again at 00 seconds. Adjust to suit your tastes or the time it takes to sync your mailbox. Don’t forget to run the following on the script: chmod +x mbsync.sh to make it executable.

The crontab command is: */1 * * * * ~/mbsync.sh I’m also told that * in the first time slot is equivalent to */1. ymmv.

Other than that I think it’s just a matter of having your mail servers set up how you like them, preferably as securely as possible.

Hope this helps some readers switch to self-hosted mail or even just mutt for some low-resource, high efficiency email handling. Or of course to just tweak mutt settings for the greater good of all things. Leave me comments and tweaks of your own in the comments.

Thanks for reading. I do translation from French and Swedish to English, so if that's useful to you, feel free to connect and message me on LinkedIn or Twitter.

Published by and tagged automation and productivity using 1971 words.

Next:
Previous: