Reading emails in your terminal is fast, highly customizable, productive, and fun. Unfortunately, it can be frustrating to set up. This is a complete guide to take the frustration out of the process.

We will set up reading, writing, and searching email in a terminal with multiple accounts from multiple providers, with both separate and unified views for accounts, for a seamless, provider-agnostic experience. We will also cover some common pain points, like viewing HTML and images. I’m using Ubuntu 22.04, but it should work on other systems as well with minor modifications (yes, including WSL).

What this guide is NOT about is setting up your own email server (although if you want to go down that particular rabbit hole, I can recommend this project).

Why would I even want to do this?

A valid question. As most people, I also used webmail (Gmail) for many years, and when I got new email accounts (first university and then a company) I handled those from Gmail as well. Nevermind that this violated policies; everybody else did it too. At one point, I exported and then deleted a lot of old emails since I was getting low on Gmail space. I finally gave in when I got my fifth account, started taking company policy more seriously, and my university disallowed autoforwarding. At this point I was using three webmails, two from Gmail and one from Outlook, I was unable to easily access some old emails, and it was simply cumbersome.

I have a few arguments in favour of the setup introduced here. Some of these would be valid for a desktop client like Thunderbird as well, some are just GUI vs. TUI arguments, and some assume you have multiple accounts, but they all point in the same direction:

  • by having your emails on your disk, you’ll actually own them, with easy-to-manage backups;
  • by using a format where all messages go into separate text files, it will be very easy to use all the usual tools with your email that manipulate files and text;
  • by using a separate tool for each part of the process (i.e. by applying the Unix philosophy) you’ll be more flexible and more in control of your email process;
  • by using a desktop client instead of webmail, you’ll have a familiar experience on all your accounts (even with multiple different providers);
  • by choosing the terminal over a GUI, you’ll:
    • have a snappier workflow;
    • better integration with shell tools (e.g. GPG for signing/encryption or vim for editing);
    • send text/plain instead of text/html by default (see here, not that I agree with all the points);
    • get a more customizable and faster experience (to be fair, a GUI could be like this, but the keyboard-driven nature of TUI software always end up being superior in this regard).

It’s also pretty fun and educational to dig a little bit deeper into such an important and ubiquitous tool as email.

Configuration guide

Overview of how email works

If you have never done so before, take a look at one of your emails in its raw form. In Gmail, that is “show original”, but here are some examples for other providers. It is just a text file, with some intuitive (e.g. To:, From:) and some less intuitive, maybe provider-specific headers.

Focusing on the terminal client side of things, here’s a breakdown of the different parts needed to write and read email, along with my choice of software:

  1. An SMTP server that receives and sends emails on your behalf and an IMAP server which allows you to access your emails: Gmail, Outlook, etc., or your own (e.g. with docker-mailserver; JMAP is also a thing, but not very widespread yet).
  2. Secure authentication with the server, without needing to type your password all the time or to store it unencrypted: pass for passwords and mailctl for OAuth, both of which use GPG for the secure storage.
  3. A way to fetch emails from the IMAP server: isync (mbsync), which will download each raw email into a separate file (using the so-called maildir format) on your disk and goimapnotify for notification based fetching.
  4. A “mail user agent” (MUA), that can read the maildir format and compose emails: aerc (other choices here could be e.g. mutt, neomutt, alot, but I’m an aerc fan*).
  5. A way to send emails to the SMTP server for deliver: msmtp.
  6. Optionally, a search engine on-par with Gmail’s: notmuch, which we will also configure to provide a unified view over all accounts.
  7. Optionally, an address book: maildir-rank-addr** or for example khard (or even a combination these).

* and also a contributor, so I’m definitely biased
** this is my creation, so again, I’m biased

Shortcut with aerc IMAP backend

Unarguably, there will be a lot of steps below, so if you just want to read email in your terminal there’s a shortcut you can take by using the IMAP (or the JMAP) backend in aerc. You’ll loose a few things, like offline access, unified view, and search of multiple accounts. You will also be limited a bit more by your provider (for example: Outlook IMAP does not support sorting), but it definitely works, and with the built-in account wizard you can be set up in no time. You might need the authentication part from below and bits of the aerc config. The same accounts I will be configuring below are also configured here for imap as well.

Before we start

The entire configuration will be happening in configuration files. If you don’t have your dotfiles version controlled, it might be a good idea to set that up now. I personally use yadm, but there are many alternatives. For each part, I will provide fully working configuration examples from my own dotfiles. To make sure these do not get out of sync with this tutorial I also tagged the commit at the time of writing.

Also, although many of the programs we need can be installed directly with apt install, the repositories are usually quite out of date, so many of them will need to be built from source. To build everything you will need some build tools (sudo apt install build-essential) and a go environment.

Authentication

Depending on your provider(s), you will either need to use a password or an OAuth token to log in. In any case, it’s not a good idea to store these as unencrypted in a configuration file or anywhere on your computer, so as a first step, make sure you have a working GPG installation and a GPG key you can use to encrypt. Since this can be a pretty deep rabbit hole on its own, and there are many tutorials, this is the one thing I will not go into how to configure, but rather link to a tutorial. Depending on your threat model, there’s a few different ways you can set this up: for example on my personal laptop I have a separate key for encrypting passwords and tokens, and the key’s password is saved in the keyring, but on my work laptop, I have my GPG key on a Yubikey.

The important thing here is that all of the other programs we’re going to use support specifying either the password directly or a command whose output will be used as a password (or authentication token).

A note on Gmail: you can create an “application specific password” for your account somewhere in the google ecosystem, or you can use OAuth. I suggest the latter.

Password authentication (passwordstore)

Let’s set up pass for this:

sudo apt install pass
pass init [your gpg key id]
pass insert email/youremail

pass init will initialize a folder at ~/.password-store and pass insert email/youremail will encrypt a file at ~/.password-store/email/youremail with the password passed to it. Feel free to change the actual path. Verify that pass show email/youremail prints your password.

You can also set up git with pass git init, after that pass git X will take any git command X. Since everything is encrypted, it should be safe to upload it somewhere, although probably a public git repository is not the best idea.

OAuth (mailctl)

Example configs:

For this, we will use mailctl. The easiest way is to grab the latest release and put it on your path. We need to create two configuration files for mailctl.

In the config.yaml you will need to change the paths (i.e. change fbence to your username) and change the GPG key to your own on line 10. From the example services.yaml you can safely copy google and microsoft verbatim, they will identify mailctl as a well known open-source email client. If you have a different provider, you might need to ask on the mailctl mailing list or see the github issues for the exact config to use.

By default, the usage is pretty simple: mailctl authorize google ferdinandybence to authorize using the google service in services.yaml with ferdinandybence@gmail.com. When the command starts, open the link printed by mailctl in your browser, and log in as usual. Check if mailctl access ferdinandybence successfully returns a token.

If you have a personal account, this should work. If you have a company address, for Google you will need to use the extra flag --company and for Microsoft, you will need to do a little dance with the login:

  • click “Sign in with another account”
  • click “Sign-in options”
  • click “Sign in to an organisation”
  • put in the correct domain name which matches your company.email address above

Syncing mail: isync/mbsync

This is a bit confusing, since the software is called isync but the executable is called mbsync due to a package name collision.

Building from source

The currently packaged version on Ubuntu is 1.4.4 which is too old, so we will need to build from source. Note that a newer version hasn’t been released yet because of two regression. Usually this is not an issue, but I have encountered on of them when running against davmail, but reverting a single commit solves the issue in that case as well. We’ll also need to set up some prerequisites:

sudo apt install libsasl2-dev sasl2-bin

# rest taken from [here](https://unix.stackexchange.com/a/632794/82390)
git clone https://github.com/moriyoshi/cyrus-sasl-xoauth2.git

# Configure and make.
cd cyrus-sasl-xoauth2
./autogen.sh
./configure

# SASL2 libraries on Ubuntu are in /usr/lib/x86_64-linux-gnu/; modify the Makefile accordingly
sed -i 's%pkglibdir = ${CYRUS_SASL_PREFIX}/lib/sasl2%pkglibdir = ${CYRUS_SASL_PREFIX}/lib/x86_64-linux-gnu/sasl2%' Makefile

make
sudo make install

Otherwise, it uses a pretty regular build scheme. Personally, I don’t like to install stuff like this as root, but feel free to change the prefix.

git clone https://git.code.sf.net/p/isync/isync
cd isync
./autogen.sh
./configure --prefix=$HOME/.local
make
make install

Verify that running mbsync --version returns 1.5.0 or higher.

Configuration

Example configs:

Configuring isync is far from trivial, unfortunately. You will need to create a ~/.config/isyncrc file.

The logic of the config file is like this:

There’s a setup phase (this account uses password authentication), where you define connection properties, define a remote (bence-remote) and define the local maildir store (bence-local).

IMAPAccount bence
Host mail.ferdinandy.com
User bence@ferdinandy.com
AuthMechs LOGIN
PassCmd "pass show email/ferdinandy/bence | head -n1"
TLSType IMAPS
CertificateFile /etc/ssl/certs/ca-certificates.crt

IMAPStore bence-remote
Account bence

MaildirStore bence-local
SubFolders Verbatim
Path ~/.mail/bence/
Inbox ~/.mail/bence/Inbox

Important: mbsync will not create the Path for you, so in the above example, I need to mkdir ~/.mail/bence before running mbsync.

After this you define channels between the local and the remote, which can then be grouped together for easy referencing. My logic was to use these channels to a) get standardized names for the important folders (e.g. Trash should be Trash independent of language and provider, and remove all the irritating [GMAIL] prefixes), and b) to create “fast” channels, which largely means that they exclude my usually very large Sent and Archive folders.

An easy way to set these up is to have a catch-all channel, but then any extra channel needs to be carefully excluded from it. If you do a renaming like in the below example, then both the far (server side) and near (local) name needs to be excluded; otherwise you will end up with a lot of duplicated mail.

Channel gmail-labels
Far :gmail-remote:
Near :gmail-local:
Patterns * ![Gmail]* !INBOX !Archive* !Drafts !Sent* !Spam !Trash

Channel gmail-archive
Far ":gmail-remote:[Gmail]/Összes levél"
Near ":gmail-local:Archive"
Patterns *

Some words of caution:

  • A LOT has changed in isync since 1.4.4 configuration wise, so instead of relying on the internet, read the manpages (man mbsync).
  • It is entirely possible to create loops when you use channels to have different remote and local names. If you have wildcard and non-wild card channels, carefully exclude any folders from the wildcard channel you are separately syncing in another channel.
  • If things go haywire and you want to restart from scratch you need to also delete ~/.local/state/isync/.

If you have configured everything, then try running mbsync -a and then looking into your ~/.mail/ folder. Caeml might help you out if you quickly want to look at what email you have (go install github.com/ferdinandyb/caeml@latest). If you have issues you can’t figure out, I recommend writing to isync-devel@lists.sourceforge.net, which despite the name is also the channel to ask for help.

Note that some providers may have a daily limit, so if you have a large enough mailbox, the initial syncing may hit hard limits, and you’ll need to stretch it out over a couple of days (Gmail has a pretty low daily limit, for example). You might also need to set PipelineDepth 1 in your isync configuration if your provider needs you to throttle bandwidth.

Sending: msmtp(q)

Installation

  • sudo apt install msmtp
  • copy the msmtpq script to somewhere on your $PATH
  • copy the msmtp-queue script to somewhere on your $PATH

Setup

We need to modify the AppArmor profile to be able to use mailctl with msmtp:

sudo vim /etc/apparmor.d/usr.bin.msmtp, find the profile helpers part and make sure you have pass and mailctl included like in this example:

  profile helpers {
    #include <abstractions/base>
    /{,usr/}bin/bash mr,
    /{,usr/}bin/dash mr,
    /tmp/            rw,
    owner /tmp/*     rw,

    /usr/bin/secret-tool PUx,
    /usr/bin/gpg{,2}     PUx,
    /home/fbence/.local/bin/mailctl{,2} PUx,
    /usr/bin/pass        PUx,
    /usr/bin/head        PUx,
    /usr/bin/keyring     PUx,
    /{,usr/}bin/cat      PUx,
  }

Once done with the modification, run sudo apparmor_parser -r /etc/apparmor.d/usr.bin.msmtp

Create /etc/logrotate.d/msmtp with similar content (change the username):

/home/fbence/.msmtp.log /home/fbence/.msmtp.queue.log {
  rotate 9999
  weekly
  compress
  notifempty
  missingok
}

Configuration

Example config:

After setting up isync this should be a breeze.

Test if it works by running (bence is the account I’m sending from):

echo -e "Subject: Regards\n\nSending regards from Terminal." | msmtp -a bence recipient@mail.com

msmtpq can be used as a drop-in replacement for msmtp. What it does, is that it will try to send the email with msmtp, but if it fails, it will place it in ~/.msmtp-queue. Calling msmtp-queue -r will try to send emails again, that were placed in the queue. You can try this by disconnecting from the internet and trying the previous echo command with msmtpq.

notmuch

Notmuch can be used directly as a backend for several email clients, including alot, dodo, Emacs, vim and (more importantly for us) aerc. While it can be used on its own, we are going to use it for its search index, and ability to seamlessly operate over multiple accounts' maildir folder. This will provide us with the ability to search all of our email regardless of account, and to show a unified overview of certain folders, e.g. a unified inbox. If you are only setting this up for a single account, I still recommend using notmuch for its search capabilities.

Installation

git clone https://git.notmuchmail.org/git/notmuch
cd notmuch
./configure --prefix=$HOME/.local
make
make install

Optionally, if you plan on doing some scripting, the notmuch python bindings can come in handy:

python3 -m pip install notmuch

(there’s a newer notmuch2 library, but my scripts are still with the older one if you want to copy anything).

Configuration

Example config:

The configuration file is probably best placed at $HOME/.config/notmuch/default/config (although multiple databases can also be set up). You basically need to tell where your maildir folders are, and you’re good to go, but it is a good idea to make it ignore .uidvalidity files, which are created by isync, and to set it to sync maildir flags (e.g. read/unread). There are quite a lot of tweaks you could make, but since we’ll not be using every capability, we can leave it fairly simple.

[database]
path=/home/fbence/.mail

[new]
tags=inbox;unread
ignore=.uidvalidity

[search]

[maildir]
synchronize_flags=true

Once you are done, run notmuch new, which could take some time if you have a lot of email. If everything goes well, you can try a search on the command line, for example notmuch search from:example@email.com.

Address book: maildir-rank-addr

An address book is an essential feature, but aerc (and other similar email clients) does not come with one, instead, you choose your own. maildir-rank-addr is my solution for this, which automatically generates a ranked address book, with a few default filters to clear noreply addresses and such. You can then search this really fast with your favourite grep tool (I recommend ugrep). There are other, hand-curated solutions as well, like khard, and also tools that can combine the output from several similar tools (see the README for more details).

Installation

go install github.com/ferdinandyb/maildir-rank-addr@latest

Try running maildir-rank-addr --maildir=~/.mail where you should obviously change ~/.mail to wherever your maildirs are located. This should create the file $HOME/.cache/maildir-rank-addr/addressbook.tsv".

Searching would look something like this (replace searchterm):

ugrep -jP -m 100 --color=never [searchterm] ~/.cache/maildir-rank-addr/addressbook.tsv

Configuration

Example configs:

Two things can be configured. First, it’s probably a good idea to make a helper script which makes the above ugrep query a bit easier to use on the CLI.

Secondly, you can configure maildir-rank-address itself of course, for example by moving the --maildir flag into a configuration file (by default at $HOME/.config/maildir-rank-addr/config). For aerc a minimal config like

maildir = "/home/fbence/.mail"
addresses = [
    "bence@ferdinandy.com",
    "ferdinandy.bence@ttk.elte.hu",
    "priestoferis@gmail.com",
    "bence.ferdinandy@pharmahungary.com",
    "bence.ferdinandy@formsense.com"
]

is probably enough, but there are a few other things you can do, like setting up extra filters for exclusion. Check the repository README for ideas on how to integrate it with vim.

Automation

Now that we have set up everything, we need to automate a few things, which I did with systemd services. To get incoming emails as soon as possible, I use goimapnotify to run isync only the Inbox channels of my appropriate account.

goimapnotify

Example configs:

goimapnotify uses the IMAP IDLE extension to watch for new messages on the server and can be configured to run isync whenever new mail arrives. This insures that incoming email arrives in your maildir as soon as possible.

To install it, run go install gitlab.com/shackra/goimapnotify@latest.

Next we are going to create a template service for it in $HOME/.config/systemd/user with a file named goimapnotify@.service, and the following content (adjust as needed):

[Unit]
Description=start goimapnotify for various mailboxes
# OnFailure=status-email-user@%n.service
After=local-fs.target
After=network.target


[Service]
Restart=always
RestartSec=300
ExecStart=/home/fbence/go/bin/goimapnotify -conf /home/fbence/.config/emailconfiguration/%i.conf

[Install]
WantedBy=default.target
# https://usher.dev/posts/my-email-setup/

The magic here is that if you enable and start it like this:

systemctl --user enable --now goimapnotify@elte_imapnotify.service

then the elte_imapnotify part will be substituted on the service file at the %i, so you can create a configuration file for each of your accounts, but you only need one systemd service file to run them all. It’s possible to configure goimapnotify to not only run isync, but also to run notmuch after syncing, e.g.:

  {
    "host": "outlook.office365.com",
    "hostCmd": "",
    "port": 993,
    "tls": true,
    "tlsOptions": {
      "rejectUnauthorized": true
    },
    "username": "ferdinandy.bence@ttk.elte.hu",
    "usernameCmd": "",
    "password": "",
    "passwordCmd": "mailctl access ferdinandy.bence@ttk.elte.hu",
    "xoauth2": true,
    "onNewMail": "mbsync elte-inbox",
    "onNewMailPost": "notmuch new",
    "wait": 20,
    "boxes": [
      "Inbox"
    ]
  }

Technically, it would be possibly to create a single config file with all accounts, but I do not recommend it, as unfortunately, a failure in one account fails the entire goimaptnotify process, essentially propagating the error to all accounts.

General syncing

Example configs:

For everything else, I have created three scripts, depending on the frequency I want the specific service to run. The high frequency script runs every minutes, the medium frequency script runs every 15 minutes, and the low frequency script runs every 6 hours.

In $HOME/.config/systemd/user create a mailsync-high.service file

[Unit]
Description=Mail Sync high frequency
#OnFailure=status-email-user@%n.service
After=local-fs.target
After=network.target


[Service]
Type=oneshot
ExecStart=/home/fbence/.config/emailconfiguration/mailsync-high

which will be running the script specified at ExecStart, and a mailsync-high.timer file:

[Unit]
Description=Mail Sync high frequency timer

[Timer]
OnBootSec=2min
OnUnitActiveSec=1m

[Install]
WantedBy=default.target

and run systemctl --user enable --now mailsync-high.timer to enable and start. To list all running timers, run systemctl --user list-timers.

The high-frequency one is responsible for keeping notmuch up-to-date, sending emails as soon as I connect to the internet if I was offline, and syncing my most used folders quickly, so my phone is never really out-of-sync with my computer. The medium one just does a full maildir sync, while the low frequency one runs the address book update and my flavour of archiving, which archives by yearly folders, but only email that are older than a year, younger emails stay in a flat archive.

aerc specific note

Aerc has hooks, that can run even external commands on certain events. Not many hooks are implemented currently, but there’s a startup, a shutdown and a mail-recieved hook at the time of writing. Arguably, neither the goimapnotify services nor the high-frequency sync service makes any sense if you are not actively looking at or manipulating your email, so these hooks, in theory, could be used to start and stop the respective services. I haven’t explored this yet.

Actually starting to use email: aerc

The man pages for aerc are quite good, so I suggest reading them after the initial setup. There’s also an aerc wiki. Furthermore, if you don’t know how to collaborate with git+email (instead of the now more common PR/MR-based workflows), this might be an interesting read: https://git-send-email.io/. Aerc has first-class support for this workflow.

Installation

Aerc is still actively developed, so I suggest building from source (modify installation destination according to your tastes):

git clone https://git.sr.ht/~rjarry/aerc
cd ./aerc
make install PREFIX=$HOME/.local

Configuration

Example configuration files:

maildir for each email account you have

In ~/.config/aerc/accounts.conf you need to create an entry for each of your accounts you synced with isync. The most important part here is the first three lines so that we use the correct maildir and that we send with the correct msmtp account:

[bence]
source=maildir:///home/fbence/.mail/bence
outgoing=msmtpq -a bence
from=Bence Ferdinandy <bence@ferdinandy.com>
default=Inbox
folders-sort=Inbox
copy-to=Sent
pgp-key-id=D92449B0F2D9363A2DE3260B207C0A2055199A65
pgp-auto-sign=false
pgp-opportunistic-encrypt=false
signature-file=/home/fbence/.config/emailconfiguration/ferdinandy_signature.txt

If you only have a single account, then I suggest setting up a notmuch://, instead of a maildir:// account, with maildir-store (see below).

Unifying notmuch account

We’re going to add an extra account, that will use the notmuch backend, but we’ll also tell it to use some maildir capacities (maildir-store) which will allow us, amongst other things, to specify a copy-to for sending here as well. The query-map file is used to define notmuch queries, which will then show up as virtual folders. This account and the virtual folders will be used for a unified view and a unified search later. If you only have a single account I actually suggest to set only notmuch up. If you don’t create a query-map, but add a maildir-store then you’ll have a regular maildir account, but with the power of notmuch searching. Keep in mind when mixing notmuch with direct maildir access, that instead of showing the individual files like the maildir backend, notmuch will only show individual messages, even if you have multiple copies of it in different folders (or accounts).

[notmuch]
source=notmuch:///home/fbence/.mail/
maildir-store=/home/fbence/.mail/
outgoing=msmtpq -a bence
copy-to=bence/Sent
query-map=/home/fbence/.config/aerc/notmuchmap.conf
default=Inbox
folders-sort=Inbox
from=Bence Ferdinandy <bence@ferdinandy.com>
pgp-key-id=4A3641B69E9984012F396B31E3B23486302F2FA9
pgp-auto-sign=true
pgp-opportunistic-encrypt=false
signature-file=/home/fbence/.config/emailconfiguration/ferdinandy_signature.txt

Filters

The main feature of aerc is how close it lives to your shell. It runs everything in an embedded terminal (and can also open one on a new tab) so you can utilize all your favourite tools.

In ~/.config/aerc/aerc.conf you can configure a bunch of things. Most important of these is the [filters] section, where you can specify what commands certain parts of your email should be piped into, based on mimetypes or regexes on headers. Aerc comes with a few filters installed already, but you can for example add one for docx or pdf. Or even something like this:

to,tlsrpt@ferdinandy.com=gunzip -c - | jq . | bat -fP --file-name "report.json" --style=plain

which filters for a specific type of report which is a zipped json, and shows the contents nicely formatted, without needing to go through downloading and opening the zip.

You can also use the :pipe command to manually pipe a message part if you happen to need it on the fly.

Editor

You can also configure your editor, either by passing it extra parameters in aerc.conf or if you are using vim, by creating your own rules for the mail filetype in ~/.vim/after/ftplugin/mail.vim.

Binds

The default bindings of aerc are pretty decent. My additions revolve around having pairs of gX and mX bindings which will either take me to a specified folder or move the selected message to a specified folder, and switching between my account tabs. Note that some of these make sense to set under both [message] and [view]:

gi = :cf Inbox<Enter>
g1 = :cf 1_megválaszolni<Enter>

m1 = :read<Enter>:move 1_megválaszolni<Enter>
mr = :read<Enter>:move reports<Enter>

tb = :change-tab bence<Enter>:cf Inbox<Enter>
te = :change-tab elte<Enter>:cf Inbox<Enter>

Some other noteworthy mentions are archiving with e with and archiving the entire thread with E:

e = :read<Enter>:archive flat<Enter>
E = :unmark -a<Enter>:mark -T<Enter>:read<Enter>:archive flat<Enter>

and archiving emails when replying to them by default:

[compose::review]
# Keybindings used when reviewing a message to be sent
y = :send -a flat<Enter>
Y = :send<Enter>

Unified search and account view

This solution was tailored to my needs and is not perfect, but it works more of less. It’s also an example of why it might be worth to go through the hassle of syncing maildirs instead of using imap directly.

Searching

The search part is pretty trivial: since all my accounts are maildir and under ~/.mail notmuch indexes them all so any search in the notmuch account searches all my mail. I actually remapped the search bind to change to my notmuch account tab and start a search there:

/ = ":change-tab notmuch<Enter>:cf "

Since the notmuch account is one specific account, when replying I need to pay attention to using :switch-account to change to the appropriate one if it is important. A possible alternative to :switch-account could be to turn even the regular accounts into notmuch+maildir, but I haven’t explored this yet.

Unified viewing

The idea is straightforward, although setting it up requires a lot of typing so I actually wrote a script to generate the required query-map. The problem I wanted to solve is to have all my separate Inbox folders from my accounts show up in a single folder in the notmuch account. Instead of trying to come up with clever tags, we can leverage the path: search key in notmuch with returns messages under the given path, relative to the maildir folder configured for notmuch. This way any change done in the maildir accounts is trivially translated to the notmuch view. This can of course be done for any number of folders and you wouldn’t even need to call them the same in all your accounts, although having matching names makes writing binds much easier.

If you open the query-map file you’ll notice that all the path: queries are ended with an and not tag:aerc. I use this to signal that I started an operation on the messages from aerc, that needs to be finished, which leads us to the next part.

Moving between the unified folders

Although the notmuch+maildir account of aerc can actually move files between maildir folders if I wanted to, it would be complicated. To move a message from my unified Inbox to my unified 2_rám_vár (= 2_waiting_for_me) folder I would need to move it the correct account’s maildir folder called 2_rám_vár. To be able to reuse similar bindings as in the simple maildir accounts for moving and to not have to manually construct the correct paths every time, I instead tag the message in aerc with the tag 2_rám_vár and aerc and have mailsync-high run a sync script. This script finds all messages tagged with aerc, figures out which account the message is from, moves the message to the correct folder and then removes both tags.

Update:

Since 0.16.0-170-g8922d95ccb00 with the {{Filename}} template and the maildir-store setting this can be solved much easier, as long as your aerc account names and the maildir folder names are the same. For example, if your account name in aerc is bence and the appropriate maildir folder is ~/.mail/bence, then

:mv {{index (.Filename | split ("/")) 4}}/Archive <Enter>`

will move it to the correct folder. See here how I migrate how I migrated.

Similarly, sending with the appropriate account can be solved with:

a = :reply -acqA {{index (.Filename | split ("/")) 4}}<Enter>`

Html email

Any sane provider, even if they have html as default, should send also a text/plain version of the email. A sane provider will also only add the bare minimal markup to the html version. Unfortunately you’ll soon notice, that this is not always the case. Aerc has a nice filter for html already installed with it, but sometimes (usually emails from large companies that are very unnecessarily heavily branded) I just open it directly in my browser (O). In my experience, most of my email is just text, so this slow-down with heavy html emails is easily overcompensated by the ease of handling the others.

I sometimes also reply to them, that the unnecessary use of images makes their content less accessible and sometimes they listen :)

Dealing with images

The default way to view images is to open them in an external program. There’s a shortcut so it’s not terrible, but it is not great either. There are some workarounds, and it will hopefully improve in the near future. At this point I’ll need to get a little bit technical.

Currently there are two ways to show images in a terminal: the kitty terminal graphics protocol and sixels (some work is being put into an alternative). Ideally, you’d run aerc with a terminal emulator supporting one or the other and just use something as a filter for images that outputs the proper format. If you set

image/*=catimg -w$(tput cols) -

as a filter you can get a very low resolution feeling for how this could work.

The problem unfortunately is that aerc uses tcell-term which uses tcell and tcell doesn’t support either protocol, so you can’t just cat an image into the embedded terminal of aerc. What you can do though is cat the image on top of aerc, by communicating directly with your terminal emulator. This works, but is a bit cluncky, because you need to make sure your emulator also cleans up the image and hands back control to aerc. The below monstrosity is a working example with kitty where the image is opened, but then you need to press an extra key to close it:

image/*=convert - -resize 1000x500\>  - | kitty +kitten icat --stdin=yes --silent --transfer-mode stream --hold --place=120x120@0x7 && kitty +kitten icat --silent --transfer-mode stream --clear

Final notes

On the go

If you have Android, then FairEmail is a pretty solid choice for handling all your accounts from a single app. As long as you stick to IMAP folders for sorting your emails, you should be fine.

Community, help, feedback

During my journey, I was in contact with several maintainers, contributors and enthusiasts who helped me figure things out. The IRC channels and mailing lists are full of helpful people, so if you get stuck, I encourage you to reach out to them. You can also reach out to me in email or below in the comments, especially if I managed to mess up something.

Acknowledgments

A heartfelt thank you to Robin Jarry, Tim Culverhouse, Koni Marti, Moritz Poldrack, Peter Dobsan, and Oswald Buddenhagen for helping out with my configuration, whipping up patches when I was missing something and guiding me through some of my own patches. Special thanks to Moritz Poldrack, Gergely Libertiny and inwit for reading the draft and providing helpful comments and fixes to this tutorial.