Get comfortable in (neo)mutt

What is mutt and neomutt?

Mutt is a terminal mail client. Neomutt is a fork (or a collection of patches, to be more exact) of mutt with some added features. For the rest of the post I will be talking only about neomutt, but I might refer to it as "mutt".

Why not use something like Thunderbird

I've used Thunderbird, as well as many other clients and web UIs in the past. While most of them do what they're supposed to do, a lot of them have issues I can't live with.

For example, web based interfaces, like Gmail, require a browser and use so much unnecessary JavaScript that older devices, or even new cheap devices struggle to run them. Some desktop clients, like Geary don't allow you to resize sidebars freely. It might not be a problem on large screens, but on small devices sidebars can take up a significant amount of space. On my old 10" laptop about 40% of the screen was taken just by two left sidebars, that I couldn't shrink anymore.

Mutt has no such problems. It's written in C, so it's fast and barely uses any RAM. Almost all of the screen is dedicated to showing mail subjects, and opening an email displays it in full screen.

Where to get it?

Neomutt is present in most distributions' package repositories. It's often packaged as "mutt", instead of "neomutt", so just keep that in mind. You can also build it from source, available on GitHub.

My setup

While it is possible to use mutt for everything, from getting your email with IMAP to sending it with SMTP, most people (including me) use mutt just to read mail and keep it locally with something like OfflineIMAP and send messages with MSMTP or similar. The reason for that is simple: configuring mutt to work with multiple accounts, on multiple servers is a pain in the ass and there is barely any tutorials online. This approach also has the benefit of having all your mail available even if you don't have Internet connection or the server you used goes down.

I personally have three accounts, all on different servers: my personal Gmail account, an account assigned to me by my college, and my college group's mail.

Here's how I have things configured on all my machines:

  1. A script runs every minute, launching OfflineIMAP
  2. OfflineIMAP checks for and downloads new mail
  3. If there is any new unread mail I get a desktop notification
  4. I read the mail in muttt, view attachments in different programs depending on file type and open links in a browser
  5. Writing new emails is done in mutt, but the actual sending is done by MSMTP

If you think it sounds complicated, don't worry. It's relatively simple, as long as you know how to edit text files and read instructions.

Setting everything up

Step 0: Getting your system ready

Install mutt (or neomutt if your distro treats them separately), OfflineIMAP and MSMTP. I also recommend using a password manager, especially if you plan on keeping your configuration files on a public server, like GitHub.

On my system I have a script called "getpass" LastPass CLI, but it might as well just be a bunch of if/then statements. The important thing is that if I ever decided to use something other than LastPass (which I'm currently thinking about), I don't have to look through multiple config files to change the command. I highly recommend making a script like that. Here is the content of "getpass"


#!/bin/sh
lpass show --password $1
Simple, yet does exactly what it needs to

Step 1: Configure OfflineIMAP to receive mail

Before we go into configuring OfflineIMAP, you have to understand some of the terminology used:

Now we should be ready to start. In your editor of choice open the file ~/.offlineimaprc. If you didn't use OfflineIMAP before, you most likely don't have that file. In that case just create it.

Now put the following snippet in it:


[general]
accounts = account1, account2
starttls = yes
ssl = yes
pythonfile = ~/.offlineimap.py
Let me explain what each of these lines does:
[general] starts a new section called general. You put here options that aren't specific to any acocunt.
accounts = account1, account2: A list of all the accounts you want OfflineIMAP to manage by default. I recommend putting all your accounts here, so that they're always up to date
starttls and ssl tell OfflineIMAP to use SSL when connecting to the servers pythonfile is a python script OfflineIMAP will use to call "getpass". The way this config file works, it's impossible to run external commands directly, so that's the best we can do. Here is what that ~/.offlineimap.py file look like on my machine:

import os.path
import subprocess
def mailpass(acct):
  args = ["/home/user/bin/getpass", acct]
  try:
	return subprocess.check_output(args).strip().decode('UTF-8')
  except subprocess.CalledProcessError:
	  return ""
I'm not a python expert and I just copied it from a tutorial and adapted it just a bit.

Now we have to configure an email account. A typical account definition looks like this:


[Account {account-name}]
localrepository = {account-name}-local
remoterepository = {account-name}-remote

[Repository {account-name}-remote]
auth_mechanisms = LOGIN
type = IMAP
remoteuser = {username}
remotepasseval = mailpass("{account-name}")
remotehost = {imap-server}
remoteport = {imap-server-port}
sslcacertfile = /etc/ssl/certs/ca-certificates.crt
ssl_version=tls1_2
createfolders = False

[Repository {account-name}-local]
type = Maildir
localfolders = ~/.mail/{account-name}
Obviously replace {account-name}, {username} etc. with proper values. A quick rundown of these lines: [Account {account-name}] means that whatever is in this section talks about account {account-name}.
localrepository and remoterepository decide which section use for local and remote repository. They don't have to be named that way, you can name them however you want, as long as you know what is what.
[Repository {account-name}-remote] start a section describing the remote repository
auth_mechanisms How do you authenticate with the server? 90% of the time LOGIN will work just fine.
type What type of server is that? For remote put either "Gmail" for Gmail, or "IMAP" for everything else.
remoteuser Your username. Usually it's the full email address (like example@gmail.com)
remotepasseval sets the password for the server using that script from earlier.
remotehost URL of the server. Usually something like mail.example.com or imap.example.com
remoteport What port does the server listen on? Usually 993, sometimes 143.
sslcacertfile Certificate Authority cert file. Needed to talk to remote servers
ssl_version An upstream bug causes some servers to be inaccessible with newer versions of SSL. This is a temporary workaround until that's fixed.
createfolders If a folder exists locally, but not on the server, should it be created? I recommend leaving it False, as it can cause some problems with some servers
[Repository {account-name}-local] Defines a new local repository
type What type of mailbox is it? Defines how the mail is stored on disk. Mutt works best with Maildir, so we will use that.
localfolders Where to store the mail

And that's it. Repeat it for each account you want to use. After that, run offlineimap from terminal to make sure everything works and to download mail to your disk. If everything works, move on to configuring MSMTP

Step 2: Setting MSMTP for sending mail

Config for MSMTP is located at ~/.msmtprc. Just like with OfflineIMAP, it's also split into sections, but they're a bit harder to differentiate.

First, we have to add a defaults section:


defaults
auth on
tls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
logfile /var/log/msmtpc.log
defaults starts the defaults section
auth on Use authentication
tls on Use TLS for security
tls_trust_file Who can we trus?
logfile Where to log errors?

Now we just need to add some accounts:


account {account-name}
host {server-url}
port {server-port}
from {from}
user {user}
passwordeval "/home/user/bin/getpass {account}"
account {account-name} starts an account section
host Server URL
from What will the other person see in the "From: " field?
user Your username. Usually full email address
passwordeval How to get the password
And that's it for MSMTP. Test it by sending yourself an email with msmtp --account={account-name} your@email.address. Type a message and press Ctrl+D to send it.

Step 3: Configure mutt

Now comes the time to make it all work together. As much as I love mutt, the config syntax sucks. It's kind of like if you took Vimscript used to configure Vim, and took all the good things out. But, enough about that, let's get to work.

First thing you have to do is make a new folder ~/.config/mutt. Every file I talk from now on is located there, unless specified otherwise. You will need two files, plus a separate file for each account you want to use. First file is called muttrc, second is mailboxes.muttrc and the rest are {account-name}.muttrc. The only file that must be named the way I said is muttrc, every other file can have a different name.

muttrc is your main config file. Here you will put all configuration options and keybindings. I'll share my config file later, but for now the only line that absolutely has to be there is


source mailboxes.muttrc
It reads the content of mailboxes.muttrc, which contains information about your accounts.

Let's take a look at what is inside mailboxes.muttrc:


macro index,pager <F1> '<sync-mailbox><enter-command>source ~/.config/mutt/account1.muttrc<enter><change-folder>!<enter>;<check-stats>'
macro index,pager <F2> '<sync-mailbox><enter-command>source ~/.config/mutt/account2.muttrc<enter><change-folder>!<enter>;<check-stats>'

source ~/.config/mutt/accoun1.muttrc
Although it seems to be some random symbols, they all do something. The firs two lines create new key bindings, F1 and F2. The really important part is source ~/.config/mutt/account1.muttrc. It means that whenever you press F1, mutt will read this file and as a result change to a different account. This let's you switch between accounts with ease by just pressing a single key. Modify these bindings and add new ones as needed until you have a binding for each account you want to use.

The last line in this file simply sets an account to open by default when you start mutt. Without it, you would see an error and you would have to manually select an account

The only thing left is the {account-name}.muttrc files.


set realname = "My Real Name"
set from = "mail@example.com"
set sendmail = "/usr/bin/msmtp -a {account-name}"
set folder = "~/.mail/{account-name}/"
set spoolfile = "+INBOX"

alias me "My Real Name" "mail@example.com"

set mbox_type = Maildir
set ssl_starttls = yes
set ssl_force_tls = yes

unmailboxes *

I'm gonna be honest with you here. I'm not 100% sure what some of these options do, or why are they here if they're already set somewhere else, but things break without them.
The important lines are:

And that's it. You have successfully configured OfflineIMAP, MSMTP and mutt to work together in order to do one job: deliver your email to you. If you think this was too much work, there are some automated config generators that will do most of the work for you, but where is the fun in that?

Step 4 (Optional) get a notification when new mail arrives

I said in the beginning that I have a script running every minute downloading and notifying me of any new mail. If you only want synchronisation to happen automatically, OfflineIMAP can be run as a daemon. The downside is that it needs some extra configuration and you won't get any notifications.

The script I use is:


#!/bin/zsh

# Download new mail
offlineimap

# Loop through every account in ~/.mail
for account in $(ls ~/.mail); do
	# Find all unread mail whose file is newer that the last time this script was run and count them
	newcount=$(find ~/.mail/$account/INBOX/new/ -type f -newer ~/.config/mutt/.mailsynclast 2> /dev/null | wc -l)
	# Are there any new unread mail?
	if [ "$newcount" -gt "0" ]; then
		# Send a notification
		notify-send "New Mail" "$newcount new mail in mailbox $account" &
	fi
done

# Update access time of a marker file
touch ~/.config/mutt/.mailsynclast
It requires a notification daemon running and a notify-send command.

I have it running in a while true; do loop located in my .xinitrc with 30 second sleep after each run.

My muttrc

I promised to show my muttrc, but before I do, I just want to warn you that it's a mess of copy-pasted snippets, half of which I'm not sure what they do. I'll add comments to things that I consider useful



# Read account information
source mailboxes.muttrc

bind index,pager g noop
bind index \Cf noop
set sleep_time = 0
# Sort by date in reverse order. This makes new mail appear on the top instead of bottom
set sort = 'reverse-date'
# Use neovim to write new mail
set editor = nvim
#set copy = no
set timeout = "5"
set mail_check = "10"
# This is a usefull little thing. It's what decides which program opens which attachment.
# currently it's the default file from rtv (reddit terminal viewer)
set mailcap_path = ~/.config/mutt/mailcap
# set mailcap_path = ~/.mailcap
set date_format="%d/%m/%y %I:%M"
set index_format="%2C %zs %?X?A& ? %D %-15.15F %s (%-4.4c)"
set markers = no
set mark_old = no
set mime_forward = yes
set smtp_authenticators = 'gssapi:login'
set wait_key = no
# Automatically view html mail with w3m
auto_view text/html
alternative_order text/plain text/enriched text/html
set rfc2047_parameters = yes

# Scan the mail for links and let me open them with my browser
macro index,pager \cb "|urlscan -r 'setsid qutebrowser \"{}\"'"\n

# General remappings
bind editor <space> noop
bind index G last-entry
bind index gg first-entry
bind pager j next-line
bind pager k previous-line
bind browser h goto-parent
bind browser l select-entry
bind pager,browser gg top-page
bind pager,browser G bottom-page
bind index,browser d half-down
bind index,browser u half-up
bind index D delete-message
bind index U undelete-message
bind index,browser F search
bind index,pager R group-reply
bind index \031 previous-undeleted # Mouse wheel
bind index \005 next-undeleted # Mouse wheel
bind pager \031 previous-line # Mouse wheel
bind pager \005 next-line # Mouse wheel
macro index,pager S <sync-mailbox>
bind editor        <Tab> complete-query

# View attachments properly.
bind attach <return> view-mailcap
set fast_reply           # skip to compose when replying
set fcc_attach           # save attachments with the body
unset mime_forward       # forward attachments as part of body
set forward_format = "Fwd: %s"       # format of subject when forwarding
set forward_decode                   # decode when forwarding
set forward_quote                    # include message in forwards
set reverse_name                     # reply as whomever it was to
set include                          # include message in replies

#Ctrl-R to mark all as read
macro index \Cr "T~U<enter><tag-prefix><clear-flag>N<untag-pattern>.<enter>" "mark all messages as read"

#sync email
# macro index O "<shell-escape>$HOME/.config/mutt/etc/mailsync.sh<enter>" "run offlineimap to sync all mail"
# macro index o "<shell-escape>$HOME/.config/mutt/etc/mailsync.sh -qf INBOX<enter>" "run offlineimap to sync inbox"

# Notmuch searching
macro index \Cf "<enter-command>unset wait_key<enter><shell-escape>read -p 'notmuch query: ' x; echo \$x >~/.cache/mutt_terms<enter><limit>~i \"\`notmuch search --output=messages \$(cat ~/.cache/mutt_terms) | head -n 600 | perl -le '@a=<>;chomp@a;s/\^id:// for@a;$,=\"|\";print@a'\`\"<enter>" "show only messages matching a notmuch pattern"
macro index A "<limit>all\n" "show all messages (undo limit)"

# Default index colors:
color index yellow default '.*'
color index_author red default '.*'
color index_number blue default
color index_subject cyan default '.*'

# For new mail:
color index brightyellow black "~N"
color index_author brightred black "~N"
color index_subject brightcyan black "~N"

# Header colors:
color header blue default ".*"
color header brightmagenta default "^(From)"
color header brightcyan default "^(Subject)"
color header brightwhite default "^(CC|BCC)"

mono bold bold
mono underline underline
mono indicator reverse
mono error bold
color normal default default
color indicator brightblack white
color sidebar_highlight red default
color sidebar_divider brightblack black
color sidebar_flagged red black
color sidebar_new green black
color normal brightyellow default
color error red default
color tilde black default
color message cyan default
color markers red white
color attachment white default
color search brightmagenta default
color status brightyellow black
color hdrdefault brightgreen default
color quoted green default
color quoted1 blue default
color quoted2 cyan default
color quoted3 yellow default
color quoted4 red default
color quoted5 brightred default
color signature brightgreen default
color bold black default
color underline black default
color normal default default

Like I said, a total mess, but I'm working on it