Multi-account, GPG-secured mutt config ๐
I keep having to reinvent this every few years, and I always stitch it together from assorted sources, mostly because Google sort of shifts around now and then. So:
- Given a Gmail account with IMAP access turned on
- Given a Fastmail account using IMAP
- Given mutt, with your configuration in
~/.mutt
and withmuttrc
andmacros
files. - Given a working gpg config you can use to encrypt/decrypt
There are all sorts of ways to handle mutt config for assorted providers. The examples here are working right now, in early 2024. They probably have bits of cruft and lint because my config has been a work in progress since some time in the late 20th century.
Overview ๐
You’re making profiles to do this: One for each of your accounts that will hold account specific config information. If you currently have a monolith config in mutt, you can lift a lot of stuff out of it and move it into a profile, then source the profile in your main muttrc
.
You’re also going to make and encrypt a credential file for each account. Some people do this all in one file and use account hooks to make sure imap_user
, imap_password
and smtp_password
are set correcctly depending on the account you’re operating in. I chose to make a file for each account.
You’re going to make macros that source the profiles when you want to switch between them.
0. Pre-config with Gmail and Fastmail ๐
I’m not going to go into a ton of detail here:
- Gmail needs to have less secure app access turned on. Find it in your account settings. If you’re doing this for a work account, it may be your admin hasn’t enabled this. Have fun fighting city hall, in that case.
- If you have a GSuite admin, they need to have enabled all IMAP clients, not just OAuth ones.
- If you have 2FA turned on with Google, you will need to enable an application password.
For Fastmail:
- You need to have an app password set up for mutt.
Settings -> Privacy and Security -> Integrations -> App passwords
1. The profile files ๐
Make profile files for each of your accounts. I name them workplace.profile
, fastmail.profile
, etc. It doesn’t matter there’s no required convention. It’s a good idea to use the first one as the template for the second one.
This is an example of my Fastmail profile. Note line 6:
source "gpg -d ~/.mutt/passwords.gpg |"
That’s where your credentials will come from. I’ll show that file next.
# -*- muttrc -*-
# Mutt sender profile : personal/default
unset folder
set smtp_authenticators = 'gssapi:login' # fastmail needs this
set imap_authenticators = ''
source "gpg -d ~/.mutt/passwords.gpg |"
set spoolfile = "imaps://imap.fastmail.com"
set folder = "imaps://imap.fastmail.com/INBOX"
set postponed="+Drafts"
set hostname="yourdomain.com"
set signature= "~/.mutt/personal.sig"
set from= "Bob Jones <[email protected]>"
set realname = "Bob Jones"
set smtp_url = "smtps://[email protected]@smtp.fastmail.com:465" # use your fastmail username, not your email address
set imap_user = "[email protected]" # use your fastmail username here, too
# set the status to show which profile I'm using
set status_format= "-%r-Fastmail: %f [Msgs:%?M?%M/?%m%?n? New:%n?%?o? Old:%o?%?d? Del:%d?%?F? Flag:%F?%?t? Tag:%t?%?p? Post:%p?%?b? Inc:%b?%?l? %l?]---(%s/%S)-%>-(%P)---\n"
unmy_hdr *
my_hdr From: Bob Jones <[email protected]>
my_hdr Organization: yourdomain.com
my_hdr Sender: Bob Jones <[email protected]>
my_hdr Return-Path: <[email protected]>
# clear the existing mailboxes list
unmailboxes *
# load up mailboxes appropriate to this profile
mailboxes + "=Spam"
mailboxes + "=disposable"
mailboxes + "=Newsletters"
mailboxes + "=Sent"
mailboxes + "=Archive"
2. Make credentials files ๐
For each account, you need to make a file for your credentials.
set [email protected]
set imap_pass="klatu barada nikto"
set smtp_pass="klatu barada nikto"
Name it whatever. passwords-accountname
works.
Once you’ve created the file, encrypt it with gpg:
gpg -r [email protected] -e passwords-fastmail
Test it:
gpg -d passwords-fastmail.gpg
Then shred the plaintext original:
shred -u passwords-fastmail
Make sure that your profile (from the previous step) is sourcing the gpg file in line 6 of my example, e.g.
source "gpg -d ~/.mutt/passwords-fastmail.gpg |"
3. Do a quick mid-config check ๐
Might as well test it now. You can do that by sourcing one of your profiles in your muttrc
:
source ~/.mutt/fastmail.profile
When you run mutt the first time in this login session, you should get a gpg prompt for your credentials so mutt can decrypt your password file and use it to log in.
If it’s working, now’s the time to make your second profile and credentials files using the above steps since it’ll be good to know what they’re all called for the next step, which is making macros.
4. Make macros ๐
I keep my macros in their own file under ~/.mutt
just to keep things modular. You can put these in your main muttrc
. Whatever you prefer. If you have a separate file, make sure to source it in muttrc
:
source ~/.mutt/macros
Now add something like this for each account:
macro index .cf '<sync-mailbox><enter-command>source ~/.mutt/fastmail.profile<enter><change-folder>!<enter>'
macro index .cg '<sync-mailbox><enter-command>source ~/.mutt/google.profile<enter><change-folder>!<enter>'
That just does one last sync, then sources your profile, then changes folders to the inbox of that profile.
Restart mutt. From the index, if all is working correctly, the macro .cf
will source your fastmail.profile
and the macro .cg
will source your google.profile
file (both of which also source/decrypt their respective credential files).
5. In conclusion ๐
Once it’s all wired up and running, you should be able to switch back and forth between accounts with just a few seconds of latency as the inbox syncs on exit and the new inbox syncs on login.
The pleasures of mutt ๐
I went on a mutt revival kick early last year. It remains a land of contrasts. I never end up sticking to it 100 percent of the time but instead prefer to use it as a quick triage tool: It’s easy to make macros and keybindings that speed up inbox processing. Sometimes it’s easier to just bail out to the web mail interface, but during the day it’s helpful to just burn through the inbox never taking my hands off the keyboard.
mutt scoring and color treatments ๐
One last thing, I guess, since I’m documenting stuff. One of the reasons I like mutt for triage so much is my ability to add a little visual treatment to messages based on their scores. That makes it easy to see what in my inbox has more priority.
I’ve got this little script in my ~/.mutt
:
#!/usr/bin/env ruby
require 'mail'
require 'tempfile'
# Wants a +/- integer, e.g. +20
score = ARGV.first
score_file = "#{Dir.home}/.mutt/scored"
msg = Tempfile.new('msg')
msg.write($stdin.read)
mail = Mail.read(msg)
from = mail.from.first
File.open(score_file, "a") { |f| f.write "score ~f#{from} #{score}\n"}
msg.close
msg.unlink
And I’ve got these macros in my ~/.mutt/macros
file:
# Score messages
macro index,browser .sp "<pipe-entry>~/.mutt/mailscore.rb +5\n<enter-command>source ~/.mutt/scored<enter>" # score sender +5
macro index,browser .sP "<pipe-entry>~/.mutt/mailscore.rb +20\n<enter-command>source ~/.mutt/scored<enter>" # score sender +20
macro index,browser .sm "<pipe-entry>~/.mutt/mailscore.rb -5\n<enter-command>source ~/.mutt/scored<enter>" # score sender -5
macro index,browser .sM "<pipe-entry>~/.mutt/mailscore.rb -20\n<enter-command>source ~/.mutt/scored<enter>" # score sender -20
And I’ve got a few lines in my ~/.mutt/colors
file:
color index cyan default "~n 0-2 !~p"
color index magenta default "~n <5"
color index brightyellow default "~n >15"
color index brightred default "~n >19"
#
The macros pipe a given message into the script, the script extracts the sender, and the script writes a line into my ~/.mutt/scored
file. Then the ~/.mutt/colors
file (which you need to source in muttrc
) assigns colors to certain scores. I have a few other rules in ~/.mutt/scores
, as well:
# Date-based scoring penalties -- older things fall down
score ~d>3d -1
score ~d>7d -3
score ~d>14d -10
score "~O" +10 # old = +10 so I don't miss it
score "~F" +20 # flagged = +20 so it stays in the interesting view for a while, even if old
score "!~p ~d>7d" -10 # not for me directly, getting old, let it fade away
score "!~l" +2 # to a known list, give it a bump