Recently, I submitted my first kernel patch. One of the lessons I learned is that kernel mail server doesn’t accept HTML content in emails. To make sure my email contains absolutely no HTML, I set up a text-based email client (Mutt + OfflineIMAP) on my laptop. This post is about my configs that make the email client as close to a desktop client (like Outlook or Mail app on Mac) as possible.

Basic Configs

Mutt is a powerful text-based email client. OfflineIMAP is a Python utility to sync mail from IMAP servers. In my setup, I use OfflineIMAP to periodically sync a local mail folder with remote outlook email server and use Mutt as an email client to read emails from the folder.

OfflineIMAP and Mutt

OfflineIMAP: Install and Config


$ brew install offline-imap


After installation, a minimal .offlineimaprc will be created for you. You only have to adapt it. For example:

accounts = Personal
pythonfile = ~/

[Account Personal]
localrepository = Local
remoterepository = Remote

[Repository Local]
type = Maildir
localfolders = ~/Maildir/Personal

[Repository Remote]
type = IMAP
remotehost =
remoteuser = <my-email-address>
remotepasseval = get_pass()
sslcacertfile = /usr/local/etc/openssl/cert.pem

Get Password from Keychain

To avoid putting my password in plain text, I wrote a python function to get password from keychain. The function is defined in ~/ This path must be specified in .offlineimaprc by adding pythonfile = ~/ in [general] section.

$ cat ~/
#! /usr/bin/env python3
from subprocess import check_output

def get_pass():
    return check_output("security find-internet-password -w -a <my-email>", shell=True).strip("\n")

This password was added to keychain by command:

$ sudo /usr/bin/security -v add-internet-password -a <my-email> -s

Run it periodically

After the config is done, run offlineimap command, all mails from remote IMAP server will be sync’d to ~/Maildir/Personal. But we are not done yet because offlineimap has to run periodically in order to keep in sync with the mail server. To do so, we can leverage launchd. I didn’t choose crontab because crontab man page on Mac says:

(Darwin note: Although cron(8) and crontab(5) are officially supported under Darwin, their functionality has been absorbed into launchd(8), which provides a more flexible way of automatically executing commands. See launchctl(1) for more information.)

A good thing about using Homebrew to install OfflineIMAP is, it creates a service for you so that you don’t have to write one yourself. To start the service, simply run:

$ brew services start offlineimap

The default interval is 300 seconds (5 minutes), which is too long compared to other desktop email clients. So I changed it to 20 seconds by modifying StartInterval in /usr/local/Cellar/offlineimap/7.2.3/homebrew.mxcl.offlineimap.plist.

Till this point, we have finished setting up OfflineIMAP with most basic configs, which are enough for a noob like me. For more details, see offlineIMAP documentation.

Matt: Install and Config


$ brew install mutt


All mutt configs are in ~/.muttrc file. For an email client, two most basic requirements are sending and receiving emails. For outgoing emails, I use SMTP. For incoming emails, I set mailbox type to Maildir so that Mutt will read emails from the specified folder to which OfflineIMAP writes.

set smtp_url = "smtp://<my-email>"
set smtp_pass = "`security find-internet-password -w -a <my-email>`"
set realname = "Hechao Li"
set from = "<my-email>"
set attribution = "%n <%a> wrote on %{%a} [%{%Y-%b-%d %H:%M:%S %Z}]:"

set mbox_type = Maildir
set folder = "~/Maildir/Personal"
set mbox = "+INBOX"
set postponed = "+Drafts"
set spoolfile = "+INBOX"
set record = "+Sent Items"
set trash = "+Deleted Items"
set timeout = 5   # Sync every 5 seconds

For details of these variables, see Mutt manual.

Till this point, we have finished setting up Mutt as a basic email client and we are able to send and receive emails.

Advanced Configs

As a person who is spoiled by GUI, I still want to use Mutt like using a desktop email client. So I spent some time figuring out how to set up the following features.


I want my mailboxes shown on the left like all other GUI email clients. Because I don’t want to hardcode all mailboxes in .muttrc, I will ask OfflineIMAP to populate them for me. Add the following config to .offlineimaprc:

enabled = yes
filename = ~/.mutt/mailboxes
header = "mailboxes "
peritem = "+%(foldername)s"
sep = " "
footer = "\n"

And add the following configs to .muttrc:

source ~/.mutt/mailboxes
set sidebar_visible = yes
set sidebar_format = "%B%*  %N" # Show number of unread messages

View HTML Email

Though mutt guarantees that I won’t send any HTML content, I can’t prevent people/systems from sending HTML emails to me. Therefore I need a way to view them. I use two tools: w3m, a text based web browser and muttils, a set of utilities for Mutt. The former can help me browse simple HTML in plain text in Mutt. And for fancier HTML emails that don’t look good in a text-based browser, I will use the latter to view them in a real web browser like Chrome.


According to Mutt manual,

In order to handle various MIME types that Mutt doesn’t have built-in support for, it parses a series of external configuration files to find an external handler.

w3m is installed by brew install w3m. And the configuration for MIME types are written in a “mailcap” file.

In ~/.mutt/mailcap:

set text/html; w3m -I %{charset} -T text/html; copiousoutput;

In .muttrc:

set mailcap_path  = ~/.mutt/mailcap
auto_view text/html
alternative_order text/plain text/enriched text/html

See this for details of above config.


Similarly, muttils is installed by brew install muttils. After installation, add a macro to .muttrc.

$ macro pager G "<pipe-message>viewhtmlmsg<enter>"

Then when a fancy HTML email is received, I can simply press “G” to view it in Google Chrome. See here for more information about macros in Mutt.

View Attachments

Similar to the way to view HTML emails, we need to add a config in ~/.mutt/mailcap. For example, to open PDF attachments in Preview:

application/pdf; open -a Preview '%s'; copiousoutput;

For now I only have a config for PDF attachments but extending it to support more document types shouldn’t be too hard.


I want to receive a notification when a new email arrives. Terminal-notifier is what I use. Again, install it via Homebrew: brew install terminal-notifier. Then add the following config to .muttrc:

set new_mail_command="terminal-notifier -title '%v' -subtitle 'New Mail' -message '%n new messages, %u unread.'"

Make it “Vimmy”

Since I am so used to vim, instead of memorizing new operations in Mutt, I can use macros to make key mappings more “vimmy”. This article has some useful bindings. In short, make friends with macros and bindings so that you can customize Mutt.

Calendar (TBD)

I want to view and respond to event invite in Mutt but I haven’t figured out a good way yet. For now, I will just use Apple calendar to accept/reject invites.