Terminal Based Email (Work in Process)

Introduction

This is the story of my journey into terminal based email. It’s a work in process to get everything documented. (I’ve found that I don’t have a ton of time to write for fun these days.)

Background

A little while ago, I was working for a company that used Microsoft Outlook for their email. In order to manage email, I was required to open up what felt like a very heavy client, and I didn’t have access to the keyboard shortcuts that I had trained myself to use for so many years in Gmail. (I think Outlook for the web has opened up some of those favorite Gmail keyboard shortcuts, so maybe there’s hope.)

Anyway, I really hated the experience of doing email in Outlook. It just felt slow and painful compared to the process that I had become used to with Gmail. There were (in my humble opinion) too many bells and whistles.

Maybe Outlook isn’t bad once you get used to it, but for the six or so months that I worked with Outlook, it never felt like I got “used to it.”

I also felt that I had become rigid in my expectations about email, and so I began to look into alternatives to what I had become accustomed to, and that could provide me with better speed and fewer bells and whistles. I really wanted to pare email down to the essentials and get rid of all the “extras.”

Requirements and Thoughts

As I started looking for a new email client, my requirements were as follows:

  • Must Have: Snappy/fast to navigate with a keyboard centric workflow.
  • Prefer: Focused on email (I don’t need tasks and calendars in my email)
  • Prefer: to use Vim/NeoVim (or something similar) to be able to edit messages.
  • Nice to have: Free/open source

As I went down the rabbit hole, I concluded that there are plenty of tools that happened to tick all the boxes for me, and are all of the requirements led me to clients that run in the terminal.

As someone who loves to use the keyboard as much as possible, I’m well aware of tools that are terminal based. I’ve used the terminal for shell scripting, Spotify, Vim/Neovim, LazyGit, and so forth.

So, the terminal felt like an OK place to call home for email, too. And it turns out, I’m not the only person who feels this way. There are several different tools for reading email in the terminal.

Combining tools to build a personalized workflow.

In the world of Linux/Unix, the general philosphy is that your tools should do one thing, and do them well. So, for the process of downloading, reading, and responding to email, you use separate tools that each do one thing really well, and combine them into one process. This gives you flexibility on how you configure your workflow, and gives you a lot of freedom to make choices.

Unfortunately, with a lot of freedom you also get lots of things that you can mess up. Part of the reason for this entry is to help someone like me who wants to get up and running quickly not have to cut themselves on the sharp edges as much as I felt that I did.

My tool collection

I ended up selecting the following tools:

  1. Mail User Agent (MUA): Aerc. I looked at several different email clients, including Mutt/NeoMutt, Pine/Alpine, and Thunderbird, which are all popular email clients, or “Mail User Agents” in geek-speak. Aerc was originally developed by Drew DeVault and is currently maintained by Robin Jarry and other contributors.

    I like the fact that Aerc is highly configurable and adjustable, and it seems to be a pretty stable product, which I always appreciate. Add to that the fact that everything is highly configurable, it’s keyboard centric, and I can set up my key bindings to match the way that my brain works, and I was sold.

  2. Downloading/Sending: I’ve got a Gmail account that has been in active use since 2004, and it’s the place where people contact me. So, I needed a solution that would work with gmail. The recommended solution that kept turning up for me was Lieer. Lieer is different than tools like OfflineIMAP or iSync/mbsync. It doesn’t try to download your email into separate folders like a standard IMAP tool would. It pulls your email down programmatically, and uses notmuch to organize and tag the email with labels, similar to how Gmail tags messages.

  3. Organization/Tagging: Speaking of notmuch, that’s the tool that I’m using as the ‘backend’ for the aerc MUA. Aerc has support for all kinds of connections, including IMAP, JMAP, MailDir, and others, but since I’m using gmail, notmuch seemed to be the best answer for mail organization for me.

Installation

Since all these tools are primarily built for and by Linux people, I decided to install it on my Linux server rather than on my Windows laptop. (Decisions about operating system choice probably warrant a post of their own, but not today.) This also felt like a natural place to install a terminal based email client, since I remote in to the terminal on a regular basis to do my development work.

These steps were run on a server running Ubuntu 24.04.

Installing Aerc

If you weren’t using notmuch as your backend, then you can just use Homebrew to install the Aerc package with brew install aerc. You can also use the standard sudo apt install aerc to get Aerc on your Ubuntu/Linux Mint/Debian machine. I, however, decided to build from source so that I could ensure support for Notmuch, and ensure that I have the latest version. Aerc is under constant development, and at the time of this writing the version available from apt is 0.17 and the version I installed was version 0.20.

I followed the instructions from the aerc repository/README to build from source, which required installing go, scdoc, and the notmuch headers first. To do that, run:

brew install go # I like to use homebrew when I can
sudo apt install scdoc libnotmuch-dev

Then clone the repo and build with gmake:

cd ~/repos # I keep a directory of repos that I've cloned in my home directory
git clone https://git.sr.ht/~rjarry/aerc
cd aerc
gmake GOFLAGS=-tags=notmuch # I included the notmuch tag to ensure notmuch support

Validate that your build was successful by checking the version of Aerc that compiled and seeing that notmuch support was included.

./aerc -v
# aerc 0.20.1-8-g985ce7a92be4 +notmuch-5.6.0 (go1.23.6 amd64 linux 2025-02-12)
#                              ^^^^^^^ This is what we're looking for for notmuch support

Finally, install aerc to your machine:

gmake install

You can check that is installed with a simple which aerc and it should be installed in /usr/local/bin/. We’ll come back to configuration in a little bit.

Installing notmuch

Notmuch is a pretty easy install using Homebrew:

brew install notmuch

As with Aerc, we’ll come back to configuration shortly.

Installing Lieer

For this one, you could follow the installation instructions as described on the Lieer project website. Or (assuming that you already have Python3 installed like I do), you could make it easy on yourself and run:

pipx install lieer

Using pipx ensures that lieer gets installed in its own Python virtual environment, that it has the necessary dependencies, and that you don’t create dependency conflicts between lieer and other Python packages on the system.

Configuration for lieer is coming up shortly.

Configurations

Now we can configure all our programs to work in harmony with one another.

Configuring notmuch

Since both lieer and Aerc depend on the notmuch backend being ready for use, we configure it first.

cd ~ # ensure we're in the home directory
mkdir .mail # create a directory where we will be storing our mail
mkdir .mail/gmail # create a mail directory for our gmail
mkdir .mail/[workaccount] # create a mail directory for our work mail
notmuch # Follow the prompts in the Wizard to set up your notmuch config

We hope that the notmuch wizard process is self-explanatory. But sometimes it isn’t as clear as it could be.

The database path is where you will store the database. In my case that is /home/aaron/.mail. Notmuch will create its own database and hidden directory (.notmuch) under this directory.

You will be prompted for the different email addresses that you use. Select one as your primary email, and add as many other aliases as you might need. For me, one of the aliases I added is *@theaccountingnerd.com because I use some different email addresses for different things even though they all come to one email box.

If you have multiple accounts that you will be managing in notmuch, and want to maintain separation between the two then I recommend that you set a new tag for new mail.

Here’s a basic .notmuch-config file based on my settings. I’ve edited out the explanations that should appear:


# Database configuration
#
[database]
path=/home/[username]/.mail/
hook_dir=/home/[username]/.mail/.notmuch/hooks  # This is coming up in a minute

# User configuration
#
[user]
primary_email=username@yourdomain.com
other_email=username123d@gmail.com;username_zyx@gmail.com;*@yourdomain.com

# Configuration for "notmuch new"
#
[new]
tags=new   # The new will help us later while we're assigning tags to our new mail
ignore=/.*[.](json|lock|bak)$/ # This is technically for the Lieer config

# Search configuration
#
[search]
exclude_tags=spam;trash;

# Maildir compatibility configuration
#
[maildir]
synchronize_flags=true

notmuch hooks

Since we’re using Lieer to pull the data from Gmail, and I have a couple of gmail accounts that I want to pull data from, I’ve set up some hooks with notmuch to help me distingush. I choose to store my hooks under the .notmuch directory.

These hooks are simple bash scripts that automatically tag my email as it comes in.

We’ll start here with the post-new hook:

# name the file post-new and make sure it is executable using chmod +x

#!/bin/bash
echo "Beginning post-new processing of notmuch data."
echo "Removing tags from spam and trash."

# For more information about the Inbox-Paused tag, visit
# https://github.com/accountingnerd/pause-gmail-inbox
notmuch tag -inbox -new -unread -Inbox-Paused tag:spam
notmuch tag -inbox -new -unread -Inbox-Paused tag:trash

# Since I use my Inbox-Paused flag to drive whether or not something should be in the inbox
# I remove "inbox" from anything that is tagged as Inbox-Paused. Once I sync my mail, if it truly
# is in the inbox, it will show back up where it should.
echo "Taking the inbox off the inbox-paused"
notmuch tag -inbox tag:Inbox-Paused

# For managing multiple accounts, we assign a tag based on the folder,
# and then remove the new tag that we had notmuch assign when mail originally
# came into the box. We could do the tagging on *everything* in the folder,
# but that takes time and resources.
echo "Tagging the different email accounts."
notmuch tag +gmail -new folder:/gmail/ tag:new
notmuch tag +[workaccount] -new folder:/[workaccount]/ tag:new

# Tag the aerc-discuss mailing list for later viewing
echo "Tagging mailing lists"
notmuch tag +aerc-discuss -inbox -Inbox-Paused to:"~rjarry/aerc-discuss@lists.sr.ht"

echo "Post-new processing of notmuch data is complete."

Setting up these hooks ensures that data is tagged according to our specs.

You can run notmuch new to have notmuch check the mail that you’ve stored on your computer. There shouldn’t be any at this point.

Configuring Lieer

I decided to create my own Google Cloud project following the instructions on the Lieer website. I won’t reproduce the instructions here for the sake of brevity, but follow the instructions closely and save the Client secret somewhere safe.

You may need to repeat the process for your personal and work gmail accounts, as I did.

Once you’ve saved your client secret somewhere safe, then we initialize lieer so that it can download and tag your mail for you. Lieer’s program name is gmi:

gmi init \
    -C ~/.mail/gmail \
    -c ~/secrets/gmail-client-secret.json \ # change this to the actual location for your client secret
    username@gmail.com # Obviously, change this to your gmail account name

Please be sure to give the Lieer website a thorough read to understand the implications of the different options. Here’s how I ended up with my settings under ~/.mail/gmail/.gmaileer.json. (NOTE: Comments are not valid in plain JSON, and have been added for clarity. They aren’t part of the configuration file.)

{
    "replace_slash_with_dot": true, // Since I have nested labels, I didn't want to have to deal with "/" in my tags.
    "account": "username@gmail.com",
    "timeout": 600,
    "drop_non_existing_label": false,
    "ignore_empty_history": false,
    "ignore_tags": ["archive", "new", "gmail", "workaccount"], // I don't want to sync my tags that I use for personal functionality back to gmail
    "ignore_remote_labels": [], // By default, Lieer doesn't include the Social, Promotions, Forums, etc. categories. I wanted to include them.
    "remove_local_messages": true,
    "file_extension": "",
    "local_trash_tag": "trash",
    "translation_list_overlay": [],
}

Configuring Lieer on a headless server

If you’re somehow like me (you unlucky individual) and have to run Windows on your local machine and operate a headless server that doesn’t have a GUI based browser to handle the Google authentication, here’s how to make that work.

Before you run the gmi init command, you’ll want to set up some SSH port forwarding between your main computer and the remote terminal.

The gmi init uses port 8080 to listen for the authorization response. So open a separate terminal and enter the following command:

# Replace the 5432 with the port that you use to connect to your server
# Replace username with your username
# Replace the ip address with the actual IP of your server
ssh -f -N -P 5432 -L localhost:8080:localhost:8080 username@192.168.1.2

Running this command lets your local machine forward requests to port 8080 to the remote machine’s port 8080 (localhost:8080:localhost:8080).

Hat tip to Matthew Lear for this post on the Lieer issue board with the solution.

Syncing Gmail

Phew… Now that we’ve made it this far, we’re finally ready to pull down our email down to our device. If you’ve got 20+ years of history in your email, this will probably take a while. Be of good courage, though. Lieer lets you resume failed syncs.

Since this is the first time we’re getting email, we’ll use the pull action to do a one-way pull from Google to our machine.

gmi pull -C ~/.mail/gmail

Now we wait for Lieer to download all our email to our machine.

Bonus: This download means that you’ve now got two separate locations where your email is stored: Google and local. You can now backup your local copy as needed, just in case.

Take a break and let the data flow down.

Once the data has flowed down, you can initialize notmuch with notmuch new and then start searching your mail to validate and verify that everything came down from Google with the appropriate tags.

Every time I did this process, Gmail/Lieer/notmuch (I’m still not sure which) tagged everything from my archives as ‘inbox’ and ‘unread’ so learning to search and re-tag email became important for me.

I’m an inbox zero kind of guy, so I could see on the webmail interface the earliest date that I had mail in my inbox. So, to search for stuff that was incorrectly marked as ‘inbox’ and ‘unread’ from before that date, I used the following command:

notmuch search date:..20XX-XX-XX tag:inbox tag:unread | head -50

You should see a list of 50 messages with a summary and the related tags. The subject line and date are probably adequate information to allow you to search in Gmail to confirm that the email message has been read and is no longer in the inbox.

You can read the man pages for notmuch with man notmuch and man notmuch-search to help you refine your search.

Once you have the search narrowed down and you’re comfortable with it, then you can use the tag action to remove the ‘inbox’ and ‘unread’ tags from your mail, like this:

notmuch tag -inbox -unread date:..20XX-XX-XX tag:inbox tag:unread folder:/gmail/

The tag action can be used with +tag and -tag to add or remove tags, and then it’s followed by a search to determine which messages get tagged. That’s what we did earlier in the post-new hook script.

To be continued…