Interact with this post using Mastodon or
The power of emails in the terminal: Neomutt (1/3)
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 series, 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.
╭── 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 ]
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
- The power of emails in the terminal: Neomutt (1/3)
- The power of emails in the terminal: Neomutt (2/3)
- The power of emails in the terminal: Neomutt (3/3)
More food for thoughts? Check other posts about: #Cli
Thanks for your read. Hope it's been useful to you.
✄ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈