Gmail is slow! Make it fast with using Neomutt, Lieer and NotMuch in 30 minutes

NOTE: This is my most popular article on my site. I’m considering putting together a Nix Flake to make a single command to get someone started with this. Would this be interesting to you? Drop me a note and say hi: jevin@quickjack.ca

Why (neo)Mutt - I’ve been a command line/vim bindings person since I was 17. It’s one of the first things I look at when considering which software I’m going to invest time in. Gmail has some very basic vim key bindings but over the years the app itself has become very slow.

I thought I would explore the various options of how get back into Mutt again.

Philosophies that guides the approach below:

  • Embracing that I will use both mutt and the Gmail interface at times so they need to be kept in sync.
  • The Gmail labeling is fantastic out of the box, I want to keep those same mailboxes locally.
  • Using the Gmail API for email sync and not the traditional IMAP/SMTP interfaces. This makes the label syncing straightforward.

A word on mutt-wizard - This is a fantastic tool but is more general purpose than lieer. If you want to use imap/smtp and don’t care about the labels, mutt-wizard will be perfect out of the box.o

How It All Works

Most email providers use imap and smtp to publish to send and receive emails. You can do this, but lack all the labels you have in gmail. If you want to go this way, mutt-wizard is a fantastic tool to do so.

Gmail is special because you can interface with it using an API. lieer is a tool that interfaces with that API and syncs your local email state with the gmail state. IMAP does not sync Gmail’s labels well, they are accessed best via the API How it works:

  • It uses the notmuch database for indexing and tagging
  • it leaves all your messages in a single maildir
  • when sending a message, it looks for new sent messages then sends them

Only tricky thing is setting up notmuch, lieer and mutt to work together.

  • notmuch is a database tuned for email. It will index to search email and organize by labels
  • lieer (the gmi binary) syncs the email and labels between notmuch and the remote gmail account. Gmail has a unique way of managing the labels so lieer is useful.
  • When sending emails, mutt puts it in the outbox, lieer adds the special gmail tag sent (TODO: Is that right?) and sends it to Gmail via the API.

For Mutt, we aren’t going to be searching through maildir folders, we will use notmuch as a database to search. It notmuch we can create virtual mailboxes by searching for tags. Like tag:personal, tag:promotions etc. This tag syncing isn’t automatic with mutt-wizard, so we use lieer which does the tag mapping between notmuch and gmail’s proprietary API.

The folders will look like:

  • inbox: (tag:inbox -tag:promotions -tag:social) OR (tag:inbox and tag:flagged)
  • promotions: tag:promotions
  • sent: tag:sent
  • archive: !tag:inbox (removing the inbox tag is what the archive is)

Data Flow

Pulling email from Gmail:

Syncing changes back to Gmail:

Sending email:

Setting it up

Notmuch

Lieer expects notmuch to be setup first, so let’s bootstrap it.

  1. notmuch setup. All default except the following: Tags to apply to all new messages to only be empty since you want to mirror exactly what is on gmail. This setting would add new tag to EVERY email that you sync, even if you’ve already read it on Gmail.i Not what you want.
  2. You have to manually edit the generated ~/.notmuch-config file to ignore the json files: Replace with ignore line with: ignore=/.*[.](json|lock|bak)$/
  3. mkdir ~/mail
  4. cd ~/mail
  5. notmuch new (to create the blank database)

Lieer

  1. Go into the mail dir
  2. gmi init your.email@gmail.com
  3. By default, Lieer ignores the category tags (promotions, social etc). We want to sync those so we can create folders for those locally, like on Gmail. So we don’t want to ignore any of them: gmi set --ignore-tags-remote ""
  4. Lieer only knows how to map the default labels on Gmail to local labels (personal, promotions, social etc), if you have additional custom labels, that will cause the sync to die. You can either map these manually or just drop them as part of the sync: gmi set --drop-non-existing-labels
  5. Do that sync: gmi pull. This will probably take awhile. If it dies just to gmi pull --resume

You can check it’s working by doing and making sure they match up with your gmail account:

  • notmuch search tag:inbox
  • notmuch search tag:promotions

Neomutt

Neomutt has a number of useful patches to the original mutt project. For our purposes, we have notmuch support built in.

Here are some of the key parts you need in your .muttrc file:

Setting up your mailboxes

Where your notmuch root dir is and set a limit:

set nm_db_limit = 5000
set nm_default_url = "notmuch:///home/jevin/mail_quickjack"
set mbox_type = Maildir

Neomutt has this concept of “virtual mailboxes” which are basically queries we make to the notmuch database that it parses in a mutt friendly way

set spoolfile = "Inbox"
virtual-mailboxes "Inbox" "notmuch://?query=(tag:inbox -tag:promotions -tag:social) OR (tag:inbox and tag:flagged)"
virtual-mailboxes "Archive" "notmuch://?query=not tag:inbox and not tag:spam"
virtual-mailboxes "Personal" "notmuch://?query=tag:personal"
virtual-mailboxes "Flagged" "notmuch://?query=tag:flagged"
virtual-mailboxes "Promotions" "notmuch://?query=tag:promotions"
virtual-mailboxes "Social" "notmuch://?query=tag:social"
virtual-mailboxes "Sent" "notmuch://?query=tag:sent"

Sending email

Lieer has a very simple sendmail compliant email interface. It can do most of what we would need it to do.

set sendmail = "gmi send -t -C ~/mail"

Syncing

It’s not automatic to send emails so we need to have a hotkey to do so. Why not the letter o?:

macro index o "<shell-escape>cd ~/mail; gmi sync<enter>" "run lieer to sync email@gmail.com"

Better formatting

Personally, I like to see the relative age (yesterday, Saturday or a date if it’s a long time ago):

set index_format='%4C %Z %<[y?%<[m?%<[d?%[%l:%M%p ]&%[%a %d ]>&%[%b %d ]>&%[%m/%y ]> %-15.15L %s %g'

Finding email

You can do manual searching in the command line using notmuch search "something cool" but it would be great to have that in neomutt. Good news: We can!!

macro index \Cf "<vfolder-from-query>" "show only messages matching a notmuch pattern"

That’s it!

You should be good to go with your fancy new (old school) mutt setup. There are a ton of customization you can do but this should get you started.

Bonus

Gmail Address book

You can reuse your Google Contacts through the API to populate your “To” and “CC” fields through a tool called GooBook

  • Install it and setup your permissions on Google (Instructions

Add these to your .muttrc:

  • When you’re reading an email, add the users in the From field to Google Contacts: macro index,pager a "<pipe-message>goobook add<return>" "add the sender address to Google contacts"
  • Tab completion for sending: set query_command="goobook query %s"

Open URLs in Emails

Super convenient. Just install urlscan first.

macro index,pager \cb "<pipe-message> urlscan<Enter>" "call urlscan to extract URLs out of a message"
macro attach,compose \cb "<pipe-entry> urlscan<Enter>" "call urlscan to extract URLs out of a message"