erroneous thoughts

· · · whilst the great ocean of truth lay all undiscovered before me · · · /Newton/

Mutt, the email client that sucks less

Table of Contents

Introduction

This is my attempt to systematise my knowledge of mutt. It will describe the scenarios I use it with, problems, advantages and drawbacks, and my solutions to overcoming those problems. So it will be a continuously evolving document, although it is still very far from reaching a “stable” phase…

TODO

  • IMAP locally
  • IMAP frontend
  • sort order (my flavour of threads)
  • my configuration
  • automated scheduled retrieval of mail, when needed

And thus begins the great todo.

Secure password storage

Mutt requires the user to set up a slew of configuration files (more on this later), and some of that set up involves writing passwords in those configuration files… in plaintext form! This cannot be. Even if today sharing our personal computing devices is ever more rare, it is still a huge security risk. So lets follow a different path.

The solution basically consists in storing all passwords in an external encrypted file, and invoking mutt through a wrapper script the decrypts that file, and then invokes mutt proper. First, create in a secure environment, a file name ~./passwords. In it, write your passwords (and usernames, if you wish), like so:

1
2
3
4
ACCOUNT1_USERNAME="johndoe"
ACCOUNT1_PASSWORD="someverylongpass"
ACCOUNT2_USERNAME="janedoe"
ACCOUNT2_PASSWORD="someverylongbutdifferentpass"

Then encrypt it and shred the original like so:

1
2
$ gpg2 --output ~/.passwords --cipher-algo AES256 --symmetric ~/passwords
$ shred -xu ~/passwords

Then create a script named mutt.sh with the following contents:

1
2
3
4
5
6
7
8
9
s=1
while [ $s -ne 0 ]
do
  pwds=`gpg2 --decrypt ~/.passwords`
  s=$?
done
eval "$pwds"

mutt

What it does is to ask for the passphrase for the encrypted repeatedly until the correct one is provided, and then it decrypts and file and runs mutt. The required passwords will be in “script-level” environment variables, that mutt has access to. But in order to make it use them, we must set up the files like so:

1
mailuser="$ACCOUNT1_USERNAME"

More on this later…

The one disadvantage of this method is that when you have to edit the password file, e.g. to add data for a new account, you need to get to your safe environment, decrypt the file like shown below, edit it as per your needs, and re-do the above gpg2 and shred steps. But this should not happen very often, so it shouldn’t become too much of a hassle.

1
gpg2 --decrypt ~/.passwords > ~/passwords

Composing email

“Crash-proof” writing location

When composing an email, mutt’s default location for storing the temporary file your editor will write to is /tmp. This is usually a fine choice, except if your computer happens to crash amidst you writing the email… not cool. The good news is that there is an easy fix (adapt the location as suitable):

1
set tmpdir = "~/.mutt/tmp/"

Now after the crash, you’ll still have to compose a new email in mutt, but hopefully you will not have to do the writing all over again, because there will be a file in that location with the text you had written up until the crash. Of course that assumes your editor is capable doing such “last minute” saves—I use vim, and it most definitely is.

NB: after recovering the text, you will have to manually delete the old temporary file, because mutt is no longer tracking it (which means it won’t be automagically deleted after sending the email).

Common Mutt shortcuts

Instead of just dumping a whole list of shortcuts at once, I’ll add to this list as I find myself needing them. So without further ado:

  • (index) J/K: go to down/up messages.
  • (pager) J/K: idem
  • (index) <Ctrl-p>/<Ctrl-n>: go to previous/next message. TODO: describe the contraption for a “preview” of the indexer in the pager.
  • (pager) <Ctrl-p>/<Ctrl-n>: idem
  • (index) Tab: go the next new (not unread) message. TODO: detail why/how I don’t use unread.
  • (index) -: collapse thread (more on threads later)
  • (index) _: uncollapse thread
  • (index) <Esc>r: mark thread as read (whether it is collapsed or not)
  • (index) <Ctrl>r: mark message as read

My configuration

Here I list tweaks I’ve made to mutt’s standard shortcuts.

  • You must never run the risk of sending an email accidentally! Because you cannot undo it! The solution is to capital Y, instead of just y, to send an email. Here’s the config (to be added to muttrc):
1
2
bind compose y noop
bind compose Y send-message

Indexing and searching

While mutt has some elementary search capabilities (including search the email body with the ~B search flag), these are hardly ideal—in particular, body search becomes very slow has the number of emails in the INBOX grows up.

Enter notmuch. It is a very fast indexer and retriever of email. Interfacing it with mutt is done with notmuch-mutt. Both are available in ArchLinux’s official repositories. Then run notmuch setup in order to create the ~/.notmuch-config file. Among other things, it will ask you for the path to your mail archive. That’s the path that contains your email accounts; in my case it’s ~/Mail. Now you perform the initial indexing, running notmuch new. It might take a few minutes. Also, you will to re-index every time new mail arrives. For this, just stick the following in your “Account” section of ~/.offlineimaprc:

1
postsynchook = notmuch new 

Lastly, stick the following in your muttrc:

1
2
3
4
5
6
7
8
9
10
11
12
macro index,pager S \
"<enter-command>unset wait_key<enter>\
<shell-escape>notmuch-mutt -r --prompt search<enter>\
<change-folder-readonly>`echo ${XDG_CACHE_HOME:-$HOME/.cache}/notmuch/mutt/results`<enter>" \
"search mail (using notmuch)"

macro index,pager T \
"<enter-command>unset wait_key<enter>\
<enter-command>unignore message-id<enter>\
<pipe-message>notmuch-mutt thread<enter>\
<change-folder-readonly>`echo ${XDG_CACHE_HOME:-$HOME/.cache}/notmuch/mutt/results`<enter>" \
"notmuch: reconstruct thread"

S is for searching; and for bonus, something I find most useful: when you on a given message (whether on the pager or the indexer), pressing T will display that message’s thread, including the emails you sent! You’re welcome!

Searching for addresses

I use abook, a small text-based address book made specifically to be used with mutt. However, in my day to day mutt usage, I want to compose emails not so much to my “contacts”, but to people who have already emailed my (or to whom I have previously emailed). To achieve this, I use notmuch’s database and a small C program to search it when needed: notmuch-addrlookup-c.

Download, compile (it’s a single C file plus Makefile), and put it in ~/.mutt/scripts. Then, in muttrc, add the following:

1
2
3
4
5
6
set query_command="echo ; ~/.mutt/scripts/notmuch-addrlookup $(printf '%s') | perl -pe's/^(.+?)(\<.+\>)$/$2    $1/' | tac"
set query_format="%t %-25.24n %a %e"
bind editor <Tab> complete-query      # Tab-complete To: or CC: (or :Bcc) fields
bind editor \ct complete              # same but use alias instead of query (also works for files)
macro generic,index,pager \ca "<shell-escape>abook<return>" "launch abook"
macro index,pager A ":set pipe_decode<enter> <pipe-message>abook --add-email<return>" "add the sender address to abook"

Nota bene: in the first line, in the Perl regexp, in $2 $1, there is actual TAB character between the two variables. This is required by format in which mutt expects the query results to be returned, as explained further below. Beware when copying the snippet to your own configuration.

The last three lines deal with abook and mutt’s alias—I won’t explain them; google is your friend. The first three deal with notmuch. The last of them tells mutt to complete address queries with $query_command; the second sets the format for the results of that completion (again, google is thy friend). The first is the one that actually calls the query program. According to mutt’s fine documentation, the query script should output a first line with a summary of the results, and only after that the results themselves. notmuch-addrlookup-c does not do this, and just outputs the results instead, which means mutt might swallow the first of these. To simulate that extra line is the function of the echo call. Next, if the string to search consists of more than one word, it has to be wrapped in quotes—hence the printf call. In order for the formatting rules of $query_format to be applied to the results, these must follow the proper format (ibid.), which is <email><tab><user name>—and notmuch-addrlookup-c does not do this (after all, it’s not thought up to be used with mutt…). But no worries: Perl comes to the rescue, and the regexp reformats each result line into the proper order. Lastly, notmuch-addrlookup-c seems to return the results with the most frequently used in the bottom which, at least in mutt’s case, makes little sense. The reverse cat shell command tac fixes this last itch.

Printing emails

My setup to print mails used to be this one, using paps and ps2pdf. It worked well, but had a glaring fault: the produced PDF contained the email’s text… as an image! Which means the text was not either selectable or searchable—not good.

So I have updated it, using the unoconv tool, which is a converter between the formats supported by Libre Office (the package in the Arch repos is eponymous). The script to be placed in ~/.mutt/scripts/mutt_print.sh has the following contents:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/usr/bin/env sh

INPUT="$1" PDIR="$HOME/.mutt/tmp/" OPEN_PDF=okular
TMP_PRINT_FILE="$HOME/.mutt/tmp/tmp_file_for_mutt_print.txt"

# check to make sure that paps and ps2pdf are both installed
if ! command -v unoconv >/dev/null ; then
    echo "ERROR: unoconv must be installed!" 1>&2
    exit 1
fi

# create temp dir if it does not exist
if [ ! -d "$PDIR" ]; then
    mkdir -p "$PDIR" 2>/dev/null
    if [ $? -ne 0 ]; then
        echo "Unable to make directory '$PDIR'" 1>&2
        exit 2
    fi
fi

tmpfile="`mktemp $PDIR/mutt_XXXXXXXX.pdf`"
cat $INPUT > $TMP_PRINT_FILE 
unoconv -o $tmpfile -f pdf $TMP_PRINT_FILE
$OPEN_PDF $tmpfile >/dev/null 2>&1 &
sleep 1

# clean up: clear the txt temp file, and remove the PDF 
# (let the user decide whether to save it)
> $TMP_PRINT_FILE
rm $tmpfile

You need to create an empty file for the temporary plaintext content of the email in $HOME/.mutt/tmp/tmp_file_for_mutt_print.txt.

In line 3, I use okular as PDF viewer; change according to what you prefer.

Finally, in ~/.mutt/muttrc add:

1
set print_command="$HOME/.mutt/scripts/mutt_print.sh"

Now, either in the pager or the indexer, to print a given mail, press p, confirm (y), and okular should open up presenting you your email message. The font is no longer Inconsolata, but the result is more than acceptable.

Mutt patches

Gpg-agent

Since version 2.1, gpg-agent no longer uses the GPG_AGENT_INFO environment variable. This causes mutt to ask the passphrase twice: once itself, and then again, when execution falls back to the agent. To fix this, apply the patch given here.

My patches

Rename attachment

Link. Synopsis: originally, mutt renamed attachments by renaming the actual underlying file. I provide a more useful implementation.