Interact with this post using Mastodon or
The power of emails in the terminal: Neomutt
Published on

If you’ve ever opened your email in a web browser and felt that creeping frustration: the endless tabs, the sluggish loading, the feeling that your messages are trapped in some glossy corporate interface, then today’s story is for you.
I’ll talk about a way that’s been quietly thriving for decades among developers, system administrators, and people who simply love the feeling of control: Neomutt
This has been my way to make my inbox becoming a text interface where every action feels immediate, powerful, and minimal. Also, we’re no longer living in the 50s, and a terminal interface doesn’t have to be boring.
In this post, I assume you have Neomutt installed and your accounts configured. I’ll focus on sharing some post config info I found crucial in my workflow, but rarely mentioned online.
Some sections are lengthy because of config files descriptions. Here is a table of content to jump faster:
- The .mailcap file
- The muttrc file
- Accounts specific config
- Neomutt is not boring
- Keybindings
- Notmuch, is it?
- Davmail
- Use the evil HTML
╭── The .mailcap file
This is the configuration file that tells NeoMutt how to handle different MIME types in your emails.
Let’s go through mine line by line:
auto_view text/html
Emails are automatically shown as HTML. I prefer plain text and I’ve tried to make it the default, but I had to surrender because of people using crappy email services and nonsense text formatting.image/*; vimiv %s;
Images are open withvimivtext/html; lynx -assume_charset=%{charset} -display_charset=utf-8 -width=1024 -dump %s; nametemplate=%s.html; copiousoutput;
By default, uselynxto open html text.text/html; sh ~/.mutt/mutt_bgrun.sh librewolf %s;
UseLibreWolfto open html text (see associated keybindings in the keybindings section below)text/calendar; mutt-ics; copiousoutput;application/ics; mutt-ics; copiousoutput;
Usemutt-icsto view calendar invites.application/*; sh ~/.mutt/mutt_bgrun.sh xdg-open %s;
All external applications are open asynchronously so I can keep using neomutt. A copy of the mutt_bgrun script can be found at the bottom of this post.
╭── The muttrc file
STOP !!! I will list here all the lines of my own file along with their meanings when not obvious. I experimented extensively with customizations, but a functional file typically requires only a few of these lines.
Once again, the purpose of this blog is to share information that is not readily available elsewhere online. This list aims to provide insights into lesser-known topics.
cd ~/Documents
Set default directory for attachmentsunbind editor <space>
Allow spaces in filename for attachmentsset abort_noattach_regex = "\\<attach(|ed|ments?)\\>"set abort_noattach = ask-yes
Ask to not send email if abort_noattach_regex is found in the email bodyset abort_unmodified = yes
Automatically abort replies if no changes madeset allow_ansi
Color supportset ascii_chars
Use ASCII instead of ACS chars for threadsset attribution = ">On %d, %n wrote:"
Replies headerset autoedit
Go to the editor right away when composingset auto_tag
Always operate on tagged messagesset collapse_all = yes
Collapse all threads on startupset confirmcreate
Prompt when creating new filesset copy = noDon’t create a copy of sent emails. This is handled in each account fileset date_format = "!%b %d %Y @ %H:%M"
To be used in pager_format (%d)set display_filter ="perl -0777pe 's/___{10,}[^_]*microsoft teams meeting.*to join the meeting<([^>]*).*(___{10,})/\\n────────────────────────────────────────────────────────────────────────\\n\\nTeams Meeting ~~\\n\\nMeeting URL:\\n$1\\n\\n────────────────────────────────────────────────────────────────────────/is' | sed 's/^\\(To\\|CC\\): \\([^<]*[^>]\\)$/\\1\:<\\2>/g' | sed -e '/\\[-- Type: text.* --\\]/d' -e '/\\[-- Autoview.* --\\]/d' -e '/\\[-- Type.* --\\]/d' -e '/\\[-- .*unsupported.* --\\]/d' -e '/\\[-- Attachment #[0-9] --\\]/d' -e 's/Attachment #[0-9]: //g' -e '/./,/^$/!d'"# -e 's/\\([A-Z]*\\), *\\([A-Za-z]*\\)\\(\"\\)\\?/\\2 \\L\\u\\1\\E\\3/g'"
Clean Teams meeting emailsset editor = "nvim '+argdo setf markdown' '+:star' '+:put! _' '+:put! _' '+:noh'"
UseNeovimto write emailsstar: start in insert modeput: add empty line at top of filenoh: no highlightset envelope_from = yes
Allow correct server identificationset fast_reply
Skip initial prompts when replyingset fcc_attach = yes
Keep attachments in copies of sent messagesset folder = ~/.mailSet emails folderset folder_format = "%t %f"
File browser formatset forward_decode
Weed and MIME decode forwarded messagesset forward_format = "Fwd: %s"
Subject to use when forwarding messages- set header`
Include message header when replying set header_cache = ~/.mutt/cache/headersset header_cache_compress_method = "zstd"set header_cache_compress_level = 22set help = noset hostname = ""set imap_check_subscribedset implicit_autoview = yes
Pager shows parts having a mailcap viewerset include = yes
Always include messages when replyingset indent_string = " "
How to quote replied textset index_format = "%-3.1M %@attachment_info@ %-1.1S %-16.16@date@ %-35.32F %s %* %b"
Format of the index (see options here ):
index-format-hook date "~N" "* * %[%H:$M] * *"index-format-hook date "~d<1d" "%[%H:%M]"index-format-hook date "~d<1y" "%[%b %d @ %H:%M]"index-format-hook date "~A" "%[%m/%d/%y @ %H:%M]"set ispell = "aspell -e -c"set mailcap_path = "~/.mailcap"set mail_check = 60set mail_check_stats = yesset mark_old = yes
Don’t notify about new unread messages if I have already visited the folder they are inset markers = noset message_cachedir = ~/.mutt/cache/bodiesset mbox = ~/.mail
Where to store read messagesset mbox_type = Maildirset mime_forward = no
Message are forwarded in email bodyset move = no
Don’t move read mails to mbox folderset new_mail_command = "/bin/sh ~/.mutt/mutt-notif.sh"
Use the mutt-notify script to show new emails notifications. I’ve place a short example at the bottom of this postset nm_default_url = "notmuch://$HOME/.mail"
Default notmuch folder. See the notmuch sectionset nm_query_type = threads
Bring in the whole thread instead of just the matched messageset nm_record = yes
Include sent messages in notmuch searchesset noconfirmappend
Don’t ask me if i want to append to mailboxesset noprompt_after
Ask me for a command after the external pager exitsset nosave_empty
Remove files when no messages are leftset pager_format = "⏲️ %@date@ %@attachment_info@ %s"
Format of the pager status barset pager_index_lines = 10
How many index lines to show in the pagerset pager_stop
Don’t move to the next message on next-pageset postponed = ~/.mail/postponed
Postponed email folderset query_format = "%t %-50.50a %n"
Don’t cut lines for addresses autocompletionset quote_regexp = "^ *[a-zA-Z]*[>:#}]"
How to catch quoted textset read_inc = 25
Show progress when reading a mailboxset recall = ask-yes
Prompt to recall postponed messagesset reply_to
Always use reply-to if presentset reply_regexp = "^(([Rr][Ee]?(\[[0-9]+\])?: *)?(\[[^]]+\] *)?)*"
How to identify replies in the subject lineset reverse_name
Use my address as it appears in the message I am replying toset send_charset = "utf-8:iso-8859-1:us-ascii"set shell = "/bin/zsh"
If you want to use a sidebar with folders tree:
set sidebar_folder_indent = yesset sidebar_format = "%D%* %?N? [%N]? %S"set sidebar_indent_string = " "set sidebar_non_empty_mailbox_only = yesset sidebar_divider_char = " | "set sidebar_short_path = yesset sidebar_sort_method = alphaset sidebar_visible = noset sidebar_width = 50set sig_dashes = no
Remove automatic dashes in signatureset sig_on_top = yes
Signature before quoted text``set sleep_time = 0` Speed up folders switch
set smart_wrap
Wrap lines at word boundaries rather than splitting up wordsset sort = last-date
Sort overall thread with latest activity firstset sort_aux = last-date-received
Within thread, sort subthreads by date receivedset sort_browser = alpha
How to sort files in the dir browserset sort_reset status_format = " 📥 %f "set status_on_topset text_flowed = yesset tilde
Virtual lines to pad blank lines in the pagerset tmpdir = /tmp
Where to store temp filesset query_command = "abook --mutt-query '%s'"
Manage contacts withabook. Contacts names and autocompletion is done directly inNeomutset use_from
Always generate the `From:’ header fieldset use_threads = reverse
Primary sorting methodset virtual_spoolfile = yesset visual = nvim
Editor invoked by ~v in the builtin editorset wait_key = no
Won’t ask “press key to continue”set wrap = 0set write_inc = 25
Show progress while writing mailboxesunset imap_passiveignore *Ignore all lines by defaultunignore to cc bcc mail-followup-to x-mailer x-url
Unignore from: subject to cc bcc mail-followup-to date x-mailer x-urlunhdr_order *hdr_order to cc bcc
hdr_order from to cc bcc subject dateindex-format-hook attachment_info '~X >0' "✉"
Show attachment icon if existindex-format-hook attachment_info '~X 0' " "
Hide attachment icon if noneset hidden_tags = "attachment"tag-transforms "attachment" "✉" \tag-formats "attachment" "GA" \alternates user@domain.com[replace with your email address] Ignore own e-mail addresses from group-replymailboxesbase="/home/user/.mail/"; for file in $(fd -t directory . ~/.mail/[Account] | sed ‘/cur|new|tmp|Gmail$/d’); do box=$(echo “$file” | sed “s/$base//”); echo -n “+$box “; done``
Format mailbox fornotmuch
Make sure to replace [Account] with your account name.virtual-mailboxes "Today" "notmuch://?query=date:today not folder:Drafts"
Usenotmuchto create a unified mailbox with today’s emailsvirtual-mailboxes "Unread" "notmuch://?query=tag:unread or tag:old not folder:Drafts"
Usenotmuchto create a unified mailbox with unread emailsalternative_order multiparse/alternative text/calendar text/html text/plain
Order of preference for these MIME types. Make sure that certain types are prioritized when displaying the messageauto_view application/icsauto_view text/html
Start programs at launch. Davmail
is needed for my work emails:
startup-hook '`davmail & \
mbsync -aqq & \
`'`
Execute some commads on close:
shutdown-hook '`kill $(pidof java) & \ # to kill Davmail
notmuch compact --quiet`'
push <change-vfolder>Unread<Enter><refresh>
Switch to unread mailbox at startup
Accounts specific config (see the accounts section ):
folder-hook [Account] 'source ~/.mutt/[Account]'
Make sure to replace [Account] with your account namefolder-hook notmuch 'source ~/.mutt/notmuch'reply-hook . 'push z'
Move to the top of the body email when replyingsource "~/.mutt/color"[see the color section ]source "~/.mutt/bind"[see the keybindings section ]
╭── Accounts-specific config
Stored in the [account_name] file in the mutt folder. Make sure it matches the name used in your .muttrc file.
╭── Neomutt is not boring
Neomutt can use icons as shown in the muttrc file, but you can also make it looks pretty. This is done by defining your colors in the colors file located in the mutt folder.
The basic structure for each color definition is color <element> <foreground> <background>
Here are few examples:
color indicator bold white default
color index_author cyan default "~f john.doe@gmail.com | ~C john.doe@gmail.com"
color body red default "(^|[[:space:][:punct:]])\\~\\~[^*]+\\~\\~([[:space:][:punct:]]|$)" # strikethrough
There are also some colorschemes available out there, like on the official website .
╭── Keybindings
Stored in the bind file in the mutt folder.
I will not list all my keybindings, but share the ones that may be less obvious:
bind index s vfolder-from-query
UseNotmuchto search your emailsmacro index T "<change-vfolder>Today<Enter>"
Jump to the Today virtual folder defined in themuttrcfilemacro index,pager cc "<pipe-message>abook --add-email-quiet<Enter><shell-escape>notify-send 'contact added'<Enter>"
Add a contact to your book (relies onabook)macro attach cn "<pipe-message>khal import --batch -a calendar<Enter><shell-escape>vdirsyncer sync my_cal & notify-send 'calendar invite added to your calendar'<Enter>"
Applied on a. .ics file, add an invite to your calendar (relies onkahlandvdirsyncer)macro compose S "<first-entry>| sh ~/.mutt/html_send<Enter><attach-file>/tmp/conv.html<Enter><toggle-disposition><tag-entry><first-entry><tag-entry><group-alternatives><send-message>"
Convert your email in html and send the email as plain text and HTML (see the html section )
╭── Notmuch, is it?
Beyond its unassuming name, it revolutionizes email searching. This tool perfectly integrates into your workflow and will help you retrieve any email instantly due to its lightning-fast speed and comprehensive filtering system.
Using the vfolder-from-query command (see the keybindings section
), you can perfom any Notmuch search you like. For example:
notmuch search "meeting"
Search your entire email archive for specific keywordsnotmuch search tag:inbox "project"
Search for emails with specific tagsnotmuch search -tag:spam "important"
Exclude emails with a certain tag from the searchnotmuch search has:attachment
Search emails with attachmentnotmuch search filename:*.pdf
Search emails with pdf attachmentnotmuch search "from:alice AND to:bob AND subject:report"
Search with multiple filters using boolean operators
╭── Davmail
If, like me, you are forced to use Outlook services Davmail
could be a life saver. It’s a POP/IMAP/SMTP/Caldav/Carddav/LDAP gateway allowing users to use any mail client with Exchange, even from the internet through Outlook Web Access.
I let you check the link above to see how to configure it. Don’t worry, it’s a GUI and it’s pretty straightforward. Jut make sure to use localhost and the right ports for your imap and smtp in your config files.
╭── Use the evil HTML
People can’t save themselves from using poor formatting, and many large companies’ email services default to HTML. As a result, you - the email recipient - have to deal with that.
We’ve seen earlier how to show HTML email in Neomutt, but you may need to send some as well. After lots of experimentation, I’ve settle on using a script developped by Daring Fireball
.
This script is part of several commands I execute when sending my emails. All commands are stored in a html_send bash script located in my .mutt folder, and called with a keybinding
.
This script itself is called with perl ~/.mutt//Markdown.pl --html4tags "$1" > /tmp/conv.html in my global bash script.
╭── Conclusion
This concludes my journey writing this post, and may mark the beginning of your journey with Neomutt. If you’re already using this email client, I will be happy if this post brings you more ideas of customization.
I will be extremely happy to hear about your use of Neomutt. You may have a better approach with HTML emails for example.
As a final note, I’d say that Tom Robinson (@TomRobinsonIO@fosstodon.org) picked my interest with Aerc - especially its asynchronous approach - but my Outlook work email was not working. Aerc is unable to handle “malformed header”, which happens with some email providers. This issue is known for at least 10 years (1
, 2
, 3
)
SCRIPTS
The mutt_bgrun script:
#!/usr/bin/bash
# @(#) mutt_bgrun $Revision: 1.4 $
# mutt_bgrun - run an attachment viewer from mutt in the background
# Copyright (C) 1999-2002 Gary A. Johnson
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# SYNOPSIS
# mutt_bgrun viewer [viewer options] file
#
# DESCRIPTION
# Mutt invokes external attachment viewers by writing the
# attachment to a temporary file, executing the pipeline specified
# for that attachment type in the mailcap file, waiting for the
# pipeline to terminate, writing nulls over the temporary file,
# then deleting it. This causes problems when using graphical
# viewers such as qvpview and acroread to view attachments.
#
# If qvpview, for example, is executed in the foreground, the mutt
# user interface is hung until qvpview exits, so the user can't do
# anything else with mutt until he or she finishes reading the
# attachment and exits qvpview. This is especially annoying when
# a message contains several MS Office attachments--one would like
# to have them all open at once.
#
# If qvpview is executed in the background, it must be given
# enough time to completely read the file before returning control
# to mutt, since mutt will then obliterate the file. Qvpview is
# so slow that this time can exceed 20 seconds, and the bound is
# unknown. So this is again annoying.
#
# The solution provided here is to invoke the specified viewer
# from this script after first copying mutt's temporary file to
# another temporary file. This script can then quickly return
# control to mutt while the viewer can take as much time as it
# needs to read and render the attachment.
#
# EXAMPLE
# To use qvpview to view MS Office attachments from mutt, add the
# following lines to mutt's mailcap file.
#
# application/msword; mutt_bgrun qvpview %s
# application/vnd.ms-excel; mutt_bgrun qvpview %s
# application/vnd.ms-powerpoint; mutt_bgrun qvpview %s
#
# AUTHOR
# Gary A. Johnson
# <garyjohn@spk.agilent.com>
#
# ACKNOWLEDGEMENTS
# My thanks to the people who have commented on this script and
# offered solutions to shortcomings and bugs, especially Edmund
# GRIMLEY EVANS <edmundo@rano.org> and Andreas Somogyi
# <aso@somogyi.nu>.
prog=${0##*/}
# Check the arguments first.
if [ "$#" -lt "2" ]
then
echo "usage: $prog viewer [viewer options] file" >&2
exit 1
fi
# Separate the arguments. Assume the first is the viewer, the last is
# the file, and all in between are options to the viewer.
viewer="$1"
shift
while [ "$#" -gt "1" ]
do
options="$options $1"
shift
done
file=$1
# Create a temporary directory for our copy of the temporary file.
#
# This is more secure than creating a temporary file in an existing
# directory.
tmpdir=/tmp/$LOGNAME$$
umask 077
mkdir "$tmpdir" || exit 1
tmpfile="$tmpdir/${file##*/}"
# Copy mutt's temporary file to our temporary directory so that we can
# let mutt overwrite and delete it when we exit.
cp "$file" "$tmpfile"
# Run the viewer in the background and delete the temporary files when done.
"$viewer" $options "$tmpfile" >/dev/null 2>&1 &
sleep 1 &&
rm -f "$tmpfile"
rmdir "$tmpdir"
The mutt-notif script:
Make sure to replace [Account] with your account name.
#!/usr/bin/bash
number=$(fd . ~/.mail/[Account]/Inbox/new | wc -l)
if [ "$number" -eq 1 ]; then
notify-send -a [Account] "1 new email [Account]"
elif [ "$number" -gt 0 ]; then
notify-send -a [Account] "$number new emails [Account]"
fi
exit 0
More food for thoughts? Check other posts about: #Cli
Thanks for your read. Hope it's been useful to you.
✄ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈