NeoMutt: Multiple accounts and themes

Screenshot of NeoMutt running the Neonwolf 256 theme

Figure 1 (click to enlarge): NeoMutt with the Neonwolf 256 theme

Many of us have have multiple email accounts, reflecting the different groups and organisations that we're part of. At the very least, it's common to have a work and a personal account. Today we'll look at how to run multiple accounts with NeoMutt. The easiest way to do this is to run separate instances of NeoMutt with one account for each one, but in this post we'll configure the more common method of being able to see multiple accounts in a single instance of NeoMutt. This will also mean we'll have to configure sending through the correct SMTP server for each account.

We'll also explore using maildir and how to make NeoMutt look cooler with themes and colours!

This post builds on part 1 where we configured NeoMutt's basic settings, and part 2 where we set-up NeoMutt's native IMAP. To follow along with this post you'll need two email accounts.

Current configuration

Last time the configuration ended up like this:

# vim: ft=muttrc fdm=marker foldcolumn=2

set header_cache     = ~/.cache/neomutt/cache/  # where to store headers
set message_cachedir = ~/.cache/neomutt/cache/  # where to store bodies
set header_cache_compress_method = "zstd"
set header_cache_compress_level = 3

set message_cache_clean = yes                   # not recommended to set this!

set tmpdir           = /tmp

set from  = <username>@<domain>
set real_name = "My Name"

set use_threads = yes       # show email in threads in the Index
set sort = reverse-last-date-received
set sort_aux = reverse-last-date-received

set pager_index_lines = 6   # show this many lines of the Index when
                            # reading email
set pager_context = 3       # lines of context between pages
set pager_stop = yes        # don't scroll of the end of one message onto
                            # the next
unset markers               # don't show + for wrapped lines in the pager

set mark_old = no           # don't show 'new' emails as old, if user has

set edit_headers = yes    # allow user to edit message headers
set editor       = "vim"  # define which editor to use

set fast_reply   = yes    # skip initial prompts when replying
set include      = yes    # include the message we're replying to

set sidebar_visible       = yes
set sidebar_width         = 15   # width in screen columns
set sidebar_next_new_wrap = yes  # wrap around the list when scrolling


setenv PINENTRY_USER_DATA curses  # ask for the password in the terminal
set imap_pass="`gpg --quiet --for-your-eyes-only --no-tty --decrypt ~/sec.gpg`"

# new email continues to come from the IMAP server
set spool_file   = imaps://user@email.domain@mail.runbox.com

# default location is set to the local filesystem
set folder      = ~/mail

set record      = +Sent
set postponed   = +Drafts

set trash       = !Trash

set mail_check = 120                 # time in seconds between mail checks
set timeout    = 30                  # time to wait for user input
set mail_check_stats = yes
set mail_check_stats_interval = 120  # calculate every 120 secs
set imap_idle = yes

set new_mail_command="notify-send --app-name=Neomutt --icon='~/.config/neomutt/NeoMutt.png' \
    --urgency=low --expire-time=3000 'New Email in %D' &"

mailboxes -label Inbox -notify -poll ! \
          -label Drafts -nonotify -nopoll =Sent \
          -label Archive -nonotify -nopoll =Archive \
          -label Sent -nonotify -nopoll =Sent

smtp_url = smtps://user@email.domain@mail.runbox.com
smtp_auth = "`gpg --quiet --for-your-eyes-only --no-tty --decrypt ~/sec.gpg`"

Remember, we're only configuring the minimum settings as NeoMutt has a good set of defaults, and we're trying to avoid over-complicating things.

Using maildir

The mbox format can be risky if there are large files for a mailbox as the file could become corrupted. For example lets say you have a Sent folder that has email you've sent from the last 10 years, ideally we don't want to risk all those emails in a single file. I don't have particular issues with mbox, but personally I prefer the Maildir format. It uses one file for each email which means the risk of losing an email is a bit more spread out. It also avoids state so you can easily synchronise email between machines (I don't do this, but it's a nice idea!).

To use maildir set the mbox_type:

set mbox_type = maildir

If it complains that a mailbox doesn't exist (for example Sent) don't worry as it will create it as soon as you initially send an email.

Colours and themes

Screenshot of NeoMutt running the solarized-dark-256 theme

Figure 2 (click to enlarge): NeoMutt with Solarized Dark 256 theme

Everybody likes some theming, and NeoMutt has lots of theming features. There are some colour themes bundled with NeoMutt in share/neomutt/colorschemes, on Guix they're in $GUIX_PROFILE/share/neomutt/colorschemes. The ones that are bundled are:

  • neonwolf-256.neomuttrc
  • solarized-dark-256.neomuttrc
  • vombatidae.neomuttrc
  • zenburn.neomuttrc

To use them use the source command which includes the configuration from another file. See Figures 1-4 for screenshots of each theme in action!

Using the source command is a great way to break up configuration into logical units and is going to be important for setting up multiple accounts. To use it for setting colours:

source $GUIX_PROFILE/share/neomutt/colorschemes/zenburn.neomuttrc
source $GUIX_PROFILE/share/neomutt/colorschemes/vombatidae.neomuttrc

Stefan Huber created a mutt-gruvbox which I also really like. Remember that you have to have a 256 colour capable terminal and that the terminal's $TERM environment variable has to be set. NeoMutt also supports 24-bit TrueColor that relies on both (at least I have to set both!) detecting the right TERM and configuring NeoMutt. There aren't any TrueColor themes in the default bundle but they are some on Github, download NeoMutt Gruvbox Truecolor. Then set the following in neomuttrc:

color_directcolor = yes
source ~/.config/neomutt/truecolor-gruvbox.neomuttrc

As color_directcolor is boolean it can be either yes or no. I found I also had to set a TERM variable, by starting XTerm and doing the following:

# set term and start neomutt
TERM=xterm-direct neomutt

The best way to configure this is probably to create a shell alias. See Figure 5 for a screenshot of the theme.

Multiple Accounts

It's common to have multiple email accounts, for example a work and personal one. There previously wasn't great support for reading email from multiple accounts, but as NeoMutt has extended the URL syntax to support specifying a remote host and the associated username we can have multiple accounts.

Screenshot of NeoMutt running the vombatidae theme

Figure 3 (click to enlarge): NeoMutt with vombatidae theme

For the purposes of this example we'll pretend that we have one email account on Runbox, and another on Gmail.

Before we set up the mailboxes for the two accounts we have to figure out how to retrieve the passwords for the two different IMAP servers. NeoMutt has an account_command feature which calls an external program to retrieve account credentials. The NeoMutt source code has an option for using a GPG-based JSON database, which would be really nice - but I ran into some problems with it (https://github.com/neomutt/neomutt/pull/4616) which should be fixed in later releases.

For now I've re-used a script that himitsu-mutt created. This uses the format that the account_command variable is expecting. As we know the host part of the URL we're setting in neomuttrc, we look for this and use GnuPG to decrypt the secret. Here's the altered shell script:

#!/usr/bin/env bash
set -eu

while [ $# -gt 0 ]; do
  case $1 in
    --hostname)
      HOSTNAME="$2"
      shift # past argument
      shift # past value
      ;;
    --username)
      USERNAME="$2"
      shift # past argument
      shift # past value
      ;;
    --type)
      PROTO="$2"
      shift # past argument
      shift # past value
      ;;
    *)
      echo "Unknown argument $1" >> $LOG 2>&1
      exit 1
      ;;
  esac
done

if [ $HOSTNAME == "mail.runbox.com" ]; then
    PASS=`gpg --quiet --for-your-eyes-only --no-tty --decrypt ~/.config/neomutt/runbox-sec.gpg`
    echo "password: $PASS"
fi

if [ $HOSTNAME == "imap.googlemail.com" ] || [ $HOSTNAME == "smtp.gmail.com" ]; then
    PASS=`gpg --quiet --for-your-eyes-only --no-tty --decrypt ~/.config/neomutt/gmail-sec.gpg`
    echo "password: $PASS"
fi

After processing the arguments that NeoMutt provides the script checks the $HOSTNAME variable. If it's "mail.runbox.com" it unlocks the runbox secret that was previously encrypted using GnuPG, if it's either "imap.googlemail.com" or "smtp.gmail.com" then it unlocks the gmail secret.

The following configuration is added to neomuttrc:

setenv PINENTRY_USER_DATA curses
set account_command = /home/user/bin/neomutt-pw.sh
set mbox_type = maildir

set spool_file = ~/.mail/inbox

set folder      = ~/mail
set record      = +Sent
set postponed   = +Drafts
set trash       = !Trash

mailboxes -label R:Inbox -notify -poll imaps://user@email.domain@mail.runbox.com/INBOX
          -label G:Inbox -notify -poll imaps://user@gmail.com@imap.googlemail.com/INBOX \
          -label L:Drafts -nonotify -nopoll =Drafts

Remember to remove the old imap_pass setting that's in the current configuration, and replace it with the new account_command which accepts a command (a path to the shell script). Note that spool_file must be set to point to some form of mailbox file otherwise NeoMutt won't start.

The mailboxes line is doing the main work, notice that I'm using a label of R for Runbox (e.g. R:Inbox), G for Google (e.g. G:Inbox), and L for local (e.g L:Drafts). The labels can be anything you want, I've kept them short to stay within the limited number of characters that have been given to the Sidebar.

This is great, we can now read email from multiple accounts by using the mixture of the URL syntax and mailboxes capability. One problem is that we can't actually send email through both accounts!

Multiple Accounts Enhanced

Screenshot of NeoMutt running the Zenburn theme

Figure 4 (click to enlarge): NeoMutt with Zenburn theme

The reason we can't send email from the two accounts is that we can only have one SMTP server defined at a time (using $smtp_url). To send email through each account requires the $smtp_url to be configured to the SMTP server of that account.

One option to solve this is to have two different configurations and to start two instances of NeoMutt, see ArchLinux Wiki article on NeoMutt - Alternative Way. This would mean having an instance of Mutt for work email with the $smtp_url variable configured to use the work provider's SMTP server. And, in a different terminal an instance running for personal email, where the $smtp_url would point to the personal SMTP server. The upside of this approach is simplicity.

The other way is to use hooks to change NeoMutts behaviour depending on whether we're in one account or another. Both work and personal email will be visible in the same instance of NeoMutt. When we send email NeoMutt will choose which SMTP server to use. The upside of this approach is having all email accounts in one instance of NeoMutt, but it does involve a more complex configuration. To make this work requires NeoMutt's hook capabilities.

NeoMutt uses the concept of a hook to execute user-defined commands before an operation. There are lots of situations where a user can define a hook, for example save-hook, send-hook, startup-hook, and shutdown-hook - for a full list see the NeoMutt Guide's Using Hooks section.

The first step is to define two IMAP servers using the mailboxes command, and use a folder-hook to change settings each time the user moves between the two IMAP server folders. A folder-hook is a hook that's activated when the user switches into a new folder (mailbox). Each time the hook runs we can execute any changes to the configuration that we want, for example changing the $from address.

In the neomuttrc file add the following:

set spool_file   = ~/.mail/inbox
set mbox_type   = maildir

# these mailbox shortcuts are used in Local mailboxes
set folder      = ~/.mail
set record      = +Sent
set postponed   = +Drafts
set Trash       = +Trash

# Account 1 (Runbox): mailboxes and folder-hook
mailboxes -label R:Inbox -notify -poll imaps://user@email.domain@mail.runbox.com/INBOX \
          -label R:Sent  -nonotify -poll imaps://user@email.domain@mail.runbox.com/Sent \
          -label R:Trash -nonotify -nopoll imaps://user@email.domain@mail.runbox.com/Trash

folder-hook imaps://user@email.domain@mail.runbox.com/ 'source ~/config/neomutt/runbox-account-neomuttrc'


# Account 2 (Gmail): mailboxes and folder-hook
mailboxes -label G:Inbox -notify -poll imaps://user@gmail.com@imap.googlemail.com/INBOX \
          -label G:Sent -nonotify -poll imaps://user@gmail.com@imap.googlemail.com/Sent

folder-hook imaps://user@gmail.com@imap.googlemail.com/ 'source ~/.config/neomutt/gmail-account-neomuttrc'


# Local mailboxes
mailboxes -label L:Drafts -nonotify -nopoll =Drafts \
          -label L:Sent -nonotify -nopoll =Sent

folder-hook ~/.mail 'source ~/.config/neomutt/local-account-neomuttrc'

Each folder-hook is triggered by NeoMutt loading a mailbox that matches. The hook specifies two parts, the first is a folder to match against, the second is a command (or commands) to run. To provide the folder to match against the full path of the IMAP host for each is the easiest way. Getting a match can be opaque (see lower for some ways to debug), using the full server path makes it really specific and the slash (/) at the end is important.

When it matches the mailbox the folder-hook executes the command which is to load the specific settings in each file. Here's the gmail-account-neomuttrc configuration:

# vim: ft=muttrc fdm=marker foldcolumn=2

set folder = imaps://user@gmail.com@imap.googlemail.com
set spoolfile = +INBOX
unset record                     # Gmail automatically records sent email
                                 # if we set $record we duplicate email
set trash     = +[Gmail]/Trash
set postponed = +[Gmail]/Drafts
set from      = user@gmail.com
set real_name = "Your Name"

# set status: black (color0) text, yellow (color11) foreground
color status color0 color11

The equivalent runbox-account-neomuttrc file:

set folder    = imaps://user@email.domain@mail.runbox.com
set spoolfile = +INBOX
set record    = +Sent
set trash     = +Trash
set from      = user@email.domain
set realname  = "Your Name"

# set status: black (color0) text, DeepSkyBlue1 (color39) foreground
color status color0 color39

And, the local-account-neomuttrc has

set folder    = ~/.mail
set spoolfile = ~/.mail/inbox
set record    = +Sent
set postponed = +Drafts
set trash     = +Trash

# set status: black (color0) text, Wheat4 (color101) foreground
color status color0 color101

With this in place start NeoMutt and move between folders by using the folder browser (default y). When a folder is opened the folder_hook should match and change the settings.

To test whether that's happening successfully query whether the variables are set correctly by doing:

:set ?variable-name
:set ?folder
:set ?from

# when initially starting NeoMutt
:set ?folder        # folder="/home/<user>/.mail"
:set ?from          # from="name@email.domain"

# change to the Runbox folder (using 'y')
:set ?folder        # folder="imaps://user@email.domain@mail.runbox.com/"
:set ?from          # from="name@email.domain"

# change to the Gmail folder (using 'y')
:set ?folder         # folder="imaps://user@gmail.com@imap.googlemail.com/"
:set ?from           # from="user@gmail.com"

# change to the Local folder Sent
:set ?folder         # folder="/home/<user>/.mail"
:set ?from           # from="name@email.domain"

There's also the keyboard shortcut M to show messages from NeoMutt.

One key point to remember with hooks is that the configuration won't change unless there is a hook. That's why there's a local-account-neomuttrc, to change the settings to the local defaults when the user goes to a local folder. Without this, if we went from the Gmail folder and switched to a local folder, the variables would be set incorrectly remaining on the Google settings.

Multiple Account Sending

We're now ready to configure how NeoMutt sends email depending on the account. Each account's mail provider uses it's own SMTP server and will only relay email from an account it recognises (so we can't send Gmail email out using Runbox for example).

This is where msmtp is really nice as it can be configured to use the correct SMTP server depending on the from address that NeoMutt provides to it. There's no additional configuration within NeoMutt. And, email can be written and queued when offline (e.g. travelling with a laptop). See my msmtp tutorial for the details.

The alternative is to configure the SMTP smarthost for each account within each account's configuration file. The main downside is that we can't send email when we're not online. Just the same as configuring the IMAP servers make sure there's a default setting in the local-account-neomuttrc file.

Add the following to gmail-account-neomuttrc:

set smtp_url = smtps://user@gmail.com@smtp.gmail.com

Note that we configured the account_command script earlier to recognise smtp.gmail.com and to provide the correct password.

For runbox-account-neomuttrc and local-account-neomuttrc add:

set smtp_url = smtps://user@email.domain@mail.runbox.com

This will mean that NeoMutt will default to using Runbox as the outbound SMTP server to use.

Switching Accounts

One nice configuration is to switch between accounts using a shortcut, rather than navigating to a folder. Here's a macro:

macro index <f7> '<sync-mailbox><refresh><enter-command>source ~/.config/neomutt/runbox-account-neomuttrc<enter><change-folder>!<enter>'
macro index <f8> '<sync-mailbox><refresh><enter-command>source ~/.config/neomutt/gmail-account-neomuttrc<enter><change-folder>!<enter>'

These both sync the current mailbox then source the account's specific configuration and change-folder to the new $spool_file (using the exclamation mailbox shortcut).

If you only want to see the mailboxes associated with a particular account then in each account's configuration file remove the mailboxes and reconfigure them like this:

unmailboxes *
mailboxes -label G:Inbox -notify -poll ! \
          -label G:All-Mail -nonotify -nopoll "+[Gmail]/All Mail" \
          -label G:Sent -nonotify -poll +Sent \
          -label G:Trash -nonotify -nopoll +Trash

Remember to do this in all account configuration files, otherwise switching to another account from Gmail won't update the mailboxes list.

Screenshot of NeoMutt running the Truecolor Gruvbox theme

Figure 5 (click to enlarge): NeoMutt with Truecolor Gruvbox theme

Alternate identities

NeoMutt will deal with messages differently if it knows whether you sent them or if they were from someone else (see NeoMutt Alternative Addresses. This is particularly important for <group-reply> (g by default) where NeoMutt will exclude 'you' from the reply if it knows the email was sent by 'you'.

To tell NeoMutt about multiple accounts configure the alternates with a list of regular expressions:

alternates = "^name@email.domain$ ^user@gmail.com$"

set reverse_name = yes

Notice that each regexp is matching on the start of the email address (using ^) and the end of it (with $), each subsequent email address is separated with a space, and the whole thing is within a string. As it's a regex field it's worth closely matching and the field only has the email address (the name isn't in this field).

There are a lot of configuration examples that use a slightly different format but Kevin McCarthy (maintainer of Mutt) has a useful Mutt MultiAccount Wiki page where he points out that the format of $alternates changed and that the correct form is a list of multiple names.

To exclude emails from yourself in replies NeoMutt uses the me_too variable. The default is not to include yourself, so there's no reason to change it - unless you want to email yourself a lot!

Generally, if we receive an email to our work account we want replies to come from our work email address. NeoMutt will do this if the reverse_name boolean is set to yes. It will look at which address the email is (To:), then check alternates to see if it's a known identity, if it is it builds the from line accordingly. Note that the use_from must be set - this is the default and I have no idea why anyone would unset it!

There's a similar setting for altering the real_name variable, by setting reverse_real_name. The use-case for this might be that on a work account you use a formal name, and on a personal account you use an informal name.

Lots of complex configurations are possible, but hopefully this covers the main use-cases.

What we've learnt!

In this post we've explored some ways to make NeoMutt look more colourful and gone through operating multiple accounts. We've used folder-hooks and the source command to set-up configuration that's appropriate for each account. There's lots more that can be done with folder-hooks, and NeoMutt has hooks for other situations which are worth exploring.

Our current configuration requires us to be online and able to access the IMAP server to read email. Even if there's lots of bandwidth the latency of operating on each email individually can be annoying. Next time we'll look at having a local copy of all email and synchronising changes (mirroring-imap).


Posted in Tech Monday 19 May 2025
Tagged with tech ubuntu guix email neomutt