You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

624 lines
27 KiB

5 years ago
---
title: My Email Setup using Mutt and Vim
date: 2020-05-04
description: I describe my email setup. It mainly consists of Mutt and Vim although there are various supporting tools as well
tags:
- Mutt
- Vim
---
My email system is based around my email client of choice, [Mutt](http://www.mutt.org/); or more specifically [NeoMutt](https://neomutt.org/), a fork of Mutt with several of the most common patches applied. I will refer to Mutt throughout this blog although, unless otherwise stated, I am referring to NeoMutt.
In this blog post I will document how Mutt and the many surrounding cogs interlock, to get my emails the way I like them. My goals for setting up my email were as follows:
* **Exchange** - Many companies use Exchange. This post will cater for that situation
5 years ago
* **Offline** - It is fairly common for me to work in remote locations, often resulting in the need to check my emails before I am connected to the internet.
* **Powerful filtering** - I get a lot of emails and keeping them organised can be a lot of work. The more this can be automated, the better.
* **Terminal based** - I spend a lot of time in the terminal. If email can be done in the terminal, I would like to.
* **Vim** - I use Vim extensively for almost all writing tasks, whether it is programming or letters. It would be ideal if emails could be included on that list.
With Mutt, and some supporting tools, I fulfil all of the above requirements.
Below are the tools I use. I will detail how I use all of them in this blog post.
* [Neomutt](https://neomutt.org/) - Email client
* [NeoVim](https://neovim.io/) - Email editor
* [Mbsync](http://isync.sourceforge.net/mbsync.html) - Email downloader
* [Notmuch](https://notmuchmail.org/) - Email indexer (for searching)
* [Msmtp](https://marlam.de/msmtp/) - Email sender
* [Davmail](http://davmail.sourceforge.net/) - Bridge for MS Exchange
* [ImapFilter](https://github.com/lefcha/imapfilter) - Email filterer
* [Abook](http://abook.sourceforge.net/) - Contact manager
If you want to follow along, and understand how everything works, I suggest you get yourself a beer, this will probably take a couple of hours to set up first time.
Install the above tools, on Arch that would be:
```bash
sudo pacman -S neomutt isync notmuch msmtp abook
yay -S davmail imapfilter
```
I will try to describe the options necessary to get a working system, although this isn't a walk through of all of my configuration choices or a guide to using Mutt. To do so would make an already long blog post ridiculously long. However, feel free to peruse [my dotfiles](https://git.jonathanh.co.uk/jab2870/Dotfiles). I try to document options in the configuration file.
## Connecting to Exchange - Davmail
Davmail is only required if you need to interface with a MS Exchange server that does not allow IMAP / SMTP connections. If this isn't a requirement for you, [skip this step](#downloading-emails---mbsync).
To start with, install and create a configuration file in `~/.config/davmail/davmail.properties`.
The first option I set is to ensure that Davmail will only accept connections from localhost.
```ini
# Don't allow remote connections
davmail.allowRemote=false
```
For the purposes of this blog post, I will be setting it up so the various clients can connect to Davmail without an encrypted connection. That would mean, if a malicious actor or process was already on your computer, they could potentially steal credentials. If this isn't acceptable to you, it would be worth consulting [Davmail's documentation](http://davmail.sourceforge.net/sslsetup.html) about setting up an SSL.
```ini
# Don't require client to use ssl when connecting
davmail.ssl.nosecurecaldav=false
davmail.ssl.nosecureimap=false
davmail.ssl.nosecureldap=false
davmail.ssl.nosecuresmtp=false
```
By default, Davmail listens on ports over 1000 so that it doesn't need extra permissions to run. If you want to change the ports it listens on, you can do so with the following options:
```ini
# Ports to listen on
davmail.caldavPort=1080
davmail.imapPort=1143
davmail.ldapPort=1389
davmail.smtpPort=1025
```
Although Davmail doesn't store any credentials for your Exchange server, you do need to give it a URL to connect to (OWA or EWS). I also find it useful to provide a default domain.
```ini
# Exchange details
davmail.mode=Auto
davmail.defaultDomain=<PUT YOUR EXCHANGE DOMAIN HERE>
davmail.url=<PUT YOUR EXCHANGE URL HERE>
```
Finally, we have a couple of other preference options that should be relatively self explanatory.
```ini
# Don't start GUI
davmail.server=true
# Delete messages immediately on IMAP STORE \Deleted flag
davmail.imapAutoExpunge=true
# When a message is sent, put it in the sent folder
davmail.smtpSaveInSent=false
# Send keepalive character during large folder and messages download
davmail.enableKeepAlive=true
```
Start the Davmail server with `davmail ~/.config/davmail/davmail.properties`. You could put this in a start-up script although I quite like running it manually.
For more information on configuring Davmail, see their [official documentation](http://davmail.sourceforge.net/serversetup.html).
## Downloading Emails - Mbsync
Mbsync is the tool I use to download all my emails to a local directory. It manages a two way sync between a local MailDir directory and in IMAP server. In my case, the IMAP server is on localhost and provided by Davmail.
The default configuration file for Mbsync is `~/.mbsyncrc`. I like to keep my home directory clean so I instead keep the file in `$XDG_CONFIG_HOME/isync/mbsyncrc`. To make things easy for myself, I have the following alias:
```bash
alias mbsync="mbsync -c \"$XDG_CONFIG_HOME/isync/mbsyncrc\""
```
To start with, we create an IMAPAccount, which is, as the name suggests, the details for an IMAP account.
```ini
IMAPAccount work
# Address to connect to
Host 127.0.0.1
Port 1143
User USERNAME
PassCmd "pass work/email"
SSLType None
AuthMechs LOGIN
```
In my case, this is provided by Davmail, and as such, the host and port are both non-standard. If you were to use this with a normal IMAP account, you would want to change them. You would also want to use a form of encryption. Mbsync supports both STARTTLS and IMAPS.
The `PassCmd` option will run a command in order to retrieve a password, this means it isn't necessary to store your password in plain text in a configuration file. I am using [Pass](https://www.passwordstore.org/) although there are many other tools that would work. Most password managers, including [Lastpass](https://github.com/lastpass/lastpass-cli), provide a command line interface.
If you are happy with the risk (or lazy), you could replace the `PasCmd` option with `Pass` and just give your password.
Next we create a store for our account. A store in Mbsync is simply one of the locations being synced. In this example, I call it work-remote.
```ini
IMAPStore work-remote
Account work
```
Then, we create another store for our local MailDir copy.
MailDir is a standard format for storing emails that Mutt is able to read and interface with.
```ini
MaildirStore work-local
# Copy folder hierarchy
Subfolders Verbatim
# The trailing "/" is important
Path ~/.mail/work/
Inbox ~/.mail/work/Inbox
```
The `Subfolders` option specifies how a hierarchical folder structure should be represented. I've never had to use anything other than `Verbatim`.
The `Path` and `Inbox` options specify where the MailDir and Inbox folders should be stored.
Lastly, we create a channel. This is what joins the two stores together in a Master-Slave relationship.
```ini
Channel work
Master :work-remote:
Slave :work-local:
# Include everything
Patterns *
# Automatically create missing mailboxes, both locally and on the server
Create Both
# Save the synchronization state files in the relevant directory
SyncState *
```
The `Patterns` option can be used to include / exclude certain folders. I prefer to keep things simple and sync everything.
That should be enough to clone your mailbox. Simply run
```bash
mbsync work
```
This will download your whole mailbox, including attachments. Be prepared for it to take a while.
## Viewing Emails - Mutt
Mutt is an extremely powerful and configurable email manager.
To start with, you will want to create the file `~/.config/mutt/muttrc`.
In there, you will want to tell Mutt about the folder that Mbsync has just created; where it is and where some of the important folders are in there.
```ini
# Folder with emails
set folder = "~/.mail/work"
# Type of mailbox
set mbox_type = Maildir
# Directory to poll for new mail
set spoolfile = +Inbox
# Directory to save sent messages into
set record = +Sent
# Sets the drafts folder
set postponed = +Drafts
# File that headers will be cached
set header_cache = ~/.cache/mutt
```
With just the configuration above, you should be able to open Mutt and read your emails. However, you won't have a very nice time of it. There are some settings that you will probably want.
```ini
# Sort by threads
set sort = threads
# Sort threads by last date recieved - newest first
set sort_aux = reverse-last-date-received
# Show date in year/month/day hour:minute format
set date_format="%y/%m/%d %I:%M%p"
```
This obviously isn't a full list of options, but it should give you an email client that is in some way similar to other clients you will have used.
### HTML Email
Unfortunately, most people don't use plain text emails. This means that we need to tell Mutt how to handle HTML and multipart messages. This starts with a `mailcap` file.
```ini
# Mailcap file is used to tell mutt how to open different types of file
set mailcap_path = "~/.config/mutt/mailcap"
```
This tells Mutt what to do when it comes across different types of mime type. The following should put you in good stead for opening HTML emails:
```mailcap
text/html; $BROWSER %s
text/html; w3m -I %{charset} -T text/html -dump; copiousoutput;
```
The syntax for this file is quite simple. The line is delimited by semi-colons. Field 1 is the mime type, field 2 is the command to run, field 3 and higher are optional flags.
You will see that on the second line, the `copiousoutput` flag is passed which means the command is non-interactive and, as such, Mutt can simply echo the output in its normal pager. Mutt will prefer options with this flag, if trying to view an email using `auto_view`.
Note, I am using the text based web browser w3m, you may have to install it or use an alternative such as lynx. Additionally, you will need to have the `$BROWSER` environment variable set or specify the browser command you want to use in its place.
```ini
# Tells Mutt to automatically view files with these mime types
auto_view text/html
# Order to try and show multipart emails
alternative_order text/plain text/enriched text/html
```
The lines above tell Mutt to try and automatically view HTML emails, although it should prefer plain text.
In my experience, this works for the majority of emails. However, for maybe 5% of emails, this isn't enough. For those emails that are image heavy, or use colour to distinguish different sections, you can open the email in your normal browser. To do that, you will need to push `v` when viewing the email which will take you to the attachment view:
```
I 1 <no description> [multipa/alternativ, 7bit, 4.5K]
I 2 ├─><no description> [text/plain, 7bit, us-ascii, 0.6K]
I 3 └─><no description> [text/html, 7bit, us-ascii, 3.7K]
```
By selecting the text/html option and pushing enter, it will be opened in your browser.
## Sending emails
The last essential piece to any email system is sending emails.
Neomutt has built in SMTP support, so for basic email needs all you need is:
```ini
# Use an external command to get the password
set my_smtp_pass = `pass show work/email`
# Set the smtp url
set smtp_url="smtp://USERNAME:$my_smtp_pass@127.0.0.1:1025"
```
If you want to include your password directly in the configuration file, you can omit the `my_smtp_pass` variable and simply provide your password like so:
```ini
set smtp_url="smtp://USERNAME:PASSWORD@127.0.0.1:1025"
```
This supports both `smtp` and `smtps`, simply by changing the protocol in the URL. If you need to use `startls`, you can do so with the following:
```ini
set ssl_starttls = yes
```
### Composing Emails
One of the biggest advantages (for me) of Mutt is the ability to use Vim for composing my emails. Whether you use Vim or not, you probably have a favourite text editor.
If you do any programming, you are likely to have spent a non-trivial amount of time configuring your editor. As a result, it makes a lot of sense to use that editor for as much as you can.
To use Vim, set the following:
```ini
# Use nvim but don't force text width (looks terible if read on a phone)
set editor = "nvim +':set textwidth=0'"
```
Note that I set my `textwidth` to 0 so Vim doesn't try to hard wrap the lines. I do this because if you hard wrap, the text will look terrible on narrow devices (such as phones).
If you don't like Vim, you can use almost any text editor you like, the only catch is that the editor might need to be set to only return once you close it. This is often done with a `wait` flag, although you will need to consult you editor's documentation. See a couple of examples below:
```ini
# Use Sublime Text to compose email.
# -w stops sublime returning until you close
set editor = "subl -w"
# Use VS Code to compose email.
# -w stops Code returning until you close
set editor = "code -w"
# Emacs
set editor = "emacs"
# Nano
set editor = "nano"
```
### HTML Emails
If you do need to send HTML emails, please spare a thought for your recipient; it is not just terminal email client users that could suffer. According to the [National Eye Institute](https://www.nei.nih.gov/learn-about-eye-health/eye-conditions-and-diseases/color-blindness), one in twelve men are colour blind; so please don't use only colour to distinguish items. Additionally, many people suffer from vision-loss blindness, who are likely to use screen readers or braille displays. Complex layouts or heavy use of images are going to give these people a poor experience.
That being said, HTML emails can be used for aesthetic purposes, whilst still providing a plain text version for those who want it. That is the method I suggest adopting.
Firstly, you need to set up msmtp which is an SMTP client used to send emails. This is done with a configuration file in `~/.config/msmtp/config`. It consists of 1 or more blocks like this:
```ini
# Work
account work
host localhost
port 1025
tls off
tls_starttls off
auth on
user USER
passwordeval "pass show work/email"
from jonathan.hodgson@example.com
```
In the msmtp file, you can replace `passwordeval` with `password` if you wish not to use a password manager.
Next I use a wrapper script called `send-from-mutt` which is used to determine which account to send from and whether or not to convert it to an html multipart email.
```bash
#!/bin/sh
# Put the message, send to stdin, in a variable
message="$(cat -)"
# Look at the first argument,
# Use it to determine the account to use
# If not set, assume work
# All remaining arguments should be recipient addresses which should be passed to msmtp
case "$(echo "$1" | tr '[A-Z]' '[a-z]')" in
"work") account="work"; shift ;;
"home") account="home"; shift ;;
*) account="work"; ;;
esac
cleanHeaders(){
# In the headers, delete any lines starting with markdown
cat - | sed '0,/^$/{/^markdown/Id;}'
}
echo "$message" | sed '/^$/q' | grep -q -i 'markdown: true' \
&& echo "$message" | cleanHeaders | convertToHtmlMultipart | msmtp --file="$config" --account="$account" "$@" \
|| echo "$message" | cleanHeaders | msmtp --file="$config" --account="$account" "$@"
```
What is important is that I can put a fake header in my email:
```
markdown: true
```
Which will cause a conversion from markdown to HTML.
I also use [this script](https://git.jonathanh.co.uk/jab2870/Dotfiles/src/branch/master/bin/.bin/emails/convertToHtmlMultipart) to do the Markdown -> HTML conversion. Credit should go to [Francisco Lopes](https://github.com/oblitum) from whom I took it. If you wish to use this, you will need to have Pandoc installed.
The final thing to do is to make Mutt use the script and add a way to change that fake header. This can be accomplished quite easily by adding the following to the `muttrc` file.
```ini
# Use my msmtp / markdown wrapper script to send emails using the work account
set sendmail = "send-from-mutt work"
# Puts email headers in Vim
set edit_headers=yes
# Adds a header that is used to determine whether my send script should convert the markdown to html
my_hdr Markdown: false
```
Now, when you compose an email, you will get something like this in Vim
```
From: Jonathan Hodgson <jonathan.hodgson@example.com>
To: your-recipient@example.com
Cc:
Bcc:
Subject: Your Subject
Reply-To:
Markdown: false
Your Message Here
```
Now, all you would need to do is change the Markdown header from false to true in order to make an HTML email.
Make sure that you leave an empty line between the headers and your message.
## Contacts - Abook
Contacts are an important part of any email system. I use [Abook](http://abook.sourceforge.net/) which was designed for use with Mutt. Simply install it and run `abook`. You will have a terminal interface in which you can add / remove / view your contacts.
```ini
##############
# Contacts #
##############
# When looking for contacts, use this command
set query_command= "abook --mutt-query '%s'"
# Add current sender to address book
macro index,pager a "<pipe-message>abook --add-email-quiet<return>" "Add this sender to Abook"
# Auto-complete email addresses by pushing tab
bind editor <Tab> complete-query
```
The settings above make it easy to interface with Abook in Mutt. When Mutt prompts you to enter an email address, start typing a name or email address, push tab and you will be able to select the address you want.
If viewing an email, push `a` in order to add the sender to your address book.
Unfortunately, Abook cannot sync with LDAP, which is what Davmail exposes when trying to get Exchange contacts. However, the address book is a very simple plain text format that looks like this:
```ini
# abook addressbook file
[format]
program=abook
version=0.6.1
[0]
name=Bob Bobbington
email=bob.bobbington@example.com
workphone=01234 567890
[1]
name=Ed Eddington
email=ed.eddington@example.com
manager=Bob Bobbington
workphone=01234 456789
```
So, I wrote a little shell script that would make LDAP queries and put them into the format above.
```bash
id=0
for i in {a..z}; do
while read line; do
entry=$(echo "$line" | tr '§' '\n' | egrep '^(cn|mail|title|manager|mobile):' | sed 's/mail/email/' | sed 's/cn/name/' | sed 's/mobile/workphone/' | sed 's/: /=/')
[ $(echo "$entry" | sed '/^$/d' | wc -l) -gt 0 ] && echo -e "[$id]\n$entry\n" && id=$((id + 1))
done <<<"$( ldapsearch -H 'ldap://localhost:1389/' -D 'DOMAIN\USERNAME' -w "$(pass show work/email)" -b "ou=people" "mail=$i*" | awk -v RS="\n\n" -v ORS="\n" '{gsub("\n","§",$0); print $0}' )"
done
```
I won't go into too much detail about how this works because you will almost certainly need to change some of the substitutions. One point to note is that it makes a separate LDAP query for each letter. I.e. everyone starting with an A, then a B. This is because each query seemed to be limited to 100 results. I don't know if this is to do with Davmail or Exchange but seeing as I only need to run this script when I need to update the address book, I thought it would probably be okay to loop through each letter.
## Search
### Normal Search
The built in search in Mutt is quite powerful. For Vim / Less style searching, you can push the `/` key and enter a search term. Pushing enter will take you to the first instance, you can then jump again using the `n` key. To make it more like Less, you might want to add the following mapping which makes `Shift+N` jump backwards.
```ini
# Search back
bind index N search-opposite
```
Note that, by default, `Shift+N` is used for marking a message as unread. If this is something you use, you may wish to map it to something different.
The search terms can be quite complex. For example:
```
'~s "mutt" ~f ("Bob +Bobbington"|"Ed +Eddington")'
```
Will match emails from either Bob Bobbington or Ed Eddington that contain the word mutt in the subject.
For more examples of what you can do, consult the [official documentation](https://neomutt.org/guide/advancedusage.html#3-%C2%A0patterns-searching-limiting-and-tagging).
### Limiting Search
A limiting search is more like a filter in other email clients. Rather than jumping between matches, it will show you a list containing only emails that match the search term. You can use the same powerful searching features that can be used in the normal search. To perform a limit search, use the `l` key. I find these searches particularly useful when searching for an email by date:
```
~d 15/1/2020*2w ~f "boss@example.com"
```
This will show me emails from my boss 2 weeks either side of 15^th^ January
### Faster, Full Body Searches - Notmuch
One limitation of both of the previous types of search is that, by default, they won't search through the body of emails. You can force it but it is slow, particularly if you have a large mailbox.
To perform more complex searches, I use [Notmuch](https://notmuchmail.org/) which indexes emails and provides much faster, full body searches.
The homepage claims that Notmuch is still fast when dealing with the order of "millions of messages". I can't say that I have been able to test that claim, although for all of my mailboxes, it is fast!
Notmuch looks in the environment variable `NOTMUCH_CONFIG` for its configuration file, defaulting to `~/.neomutt-config`. Again, I like my home directory to stay clean so I simply set the environment variable to `~/.config/notmuch/config`.
After setting the environment variable, you can just run `notmuch setup` and it will ask you for various details.
After running the setup command, you can simply run `notmuch new` in order to index your emails. Depending on the size of your mailbox, this could take a while.
Once it is done, you can test it out on the command line. The search options here are even more powerful, for a full list type `notmuch help search-terms`. As an example though, the following will show me all messages that contain the word exam in the body that have been sent / received in the past 2 weeks.
5 years ago
```bash
notmuch search 'body:exam date:-2weeks..now'
```
To make this work nicely in Mutt, add the following to your configuration file:
```ini
######################
# NotMuch Settings #
######################
# All the notmuch settings are documented here: https://neomutt.org/feature/notmuch
# Points to the notmuch directory
set nm_default_url = "notmuch://$HOME/.mail/work"
# Makes notmuch return threads rather than messages
set nm_query_type = "threads"
# Binding for notmuch search
bind index \\ vfolder-from-query
```
This will allow pushing the `\` key in the index view to perform a notmuch search. It will work in a similar way to Mutt's built in limiting search in that you will be presented with a list of emails that match your search.
Another nice thing that Notmuch gives us is the ability to create Virtual mailboxes. These are similar to "Smart" mailboxes in other email clients.
```ini
virtual-mailboxes "Today's Email" "notmuch://?query=date:today"
```
## Filtering - ImapFilter
The last, but still important, part of my setup is filtering. Of course, if you are happy to do it manually, you can do so with Mutt, but who has time for that?
Again, it doesn't respect the `XDG_CONFIG_HOME` variable so I set an alias for it:
```bash
alias imapfilter="imapfilter -c \"$XDG_CONFIG_HOME/imapfilter/config.lua\""
```
This should give you a hint about why I like it. It is configured in LUA, which is a Turing complete programming language.
That means your filtering functions can be as complex as you like.
There are a couple of options that I set at the beginning of the file:
```lua
-- According to the IMAP specification, when trying to write a message
-- to a non-existent mailbox, the server must send a hint to the client,
-- whether it should create the mailbox and try again or not. However
-- some IMAP servers don't follow the specification and don't send the
-- correct response code to the client. By enabling this option the
-- client tries to create the mailbox, despite of the server's response.
-- This variable takes a boolean as a value. Default is “false”.
options.create = true
-- By enabling this option new mailboxes that were automatically created,
-- get auto subscribed
options.subscribe = true
-- How long to wait for servers response.
options.timeout = 120
```
Full details can be found in the man page [imapfilter_config(5)](https://linux.die.net/man/5/imapfilter_config).
Next, you will want to add details of your accounts. Note that ImapFilter has the ability to interface with multiple accounts and (if you wanted it to) move emails between accounts. As with everything else in this post, I will be only be setting it up with one account.
```lua
-- Gets password from pass
status, password = pipe_from('pass show work/email')
-- Setup an imap account called work
work = IMAP {
server = "localhost",
port = 1143,
username = "USERNAME",
password = password
-- ssl = auto
}
```
Again, since I am using Davmail, I will be connecting to localhost and won't be encrypting the connection. If you plan on connecting to an IMAP server that is not on localhost, you will almost certainly want to include the ssl option.
Now to start filtering. With this, you are limited only by your programming ability and your imagination, here is a simple example to get you started:
```lua
-- This function takes a table of email addresses
-- and flags messages from them in the inbox.
function flagSenders(senders)
for _, v in pairs(senders) do
messages = work["Inbox"]:contain_from(v)
messages:mark_flagged()
end
end
flagSenders {
"bob.bobbington@example.com",
"ed.eddington@example.com"
}
```
## Conclusion
I am very happy with this setup. This blog explains how the different pieces interlock, although doesn't explain each and every configuration choice I have made.
If you are interested in my full configuration, you can find them all in [my dotfiles repository](https://git.jonathanh.co.uk/jab2870/Dotfiles). For specific configuration:
* [Mutt Configuration](https://git.jonathanh.co.uk/jab2870/Dotfiles/src/branch/master/mutt/.config/mutt)
* [Davmail Configuration](https://git.jonathanh.co.uk/jab2870/Dotfiles/src/branch/master/davmail/.config/davmail/davmail.properties)
* [Mbsync Configuration](https://git.jonathanh.co.uk/jab2870/Dotfiles/src/branch/master/isync/.config/isync/mbsyncrc)
* [Msmtp Configuration](https://git.jonathanh.co.uk/jab2870/Dotfiles/src/branch/master/msmtp/.config/msmtp/config)
* [ImapFilter Configuration](https://git.jonathanh.co.uk/jab2870/Dotfiles/src/branch/master/imapfilter/.config/imapfilter/config.lua)
* [Abook Configuration](https://git.jonathanh.co.uk/jab2870/Dotfiles/src/branch/master/abook/.config/abook/abookrc)
* [Vim Configuration](https://git.jonathanh.co.uk/jab2870/vim)
* [Email Specific Scripts](https://git.jonathanh.co.uk/jab2870/Dotfiles/src/branch/master/bin/.bin/emails)