www-gem words

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 with vimiv
  • text/html; lynx -assume_charset=%{charset} -display_charset=utf-8 -width=1024 -dump %s; nametemplate=%s.html; copiousoutput;
    By default, use lynx to open html text.
  • text/html; sh ~/.mutt/mutt_bgrun.sh librewolf %s;
    Use LibreWolf to open html text (see associated keybindings in the keybindings section below)
  • text/calendar; mutt-ics; copiousoutput;
  • application/ics; mutt-ics; copiousoutput;
    Use mutt-ics to 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 attachments
  • unbind editor <space>
    Allow spaces in filename for attachments
  • set 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 body
  • set abort_unmodified = yes
    Automatically abort replies if no changes made
  • set allow_ansi
    Color support
  • set ascii_chars
    Use ASCII instead of ACS chars for threads
  • set attribution = ">On %d, %n wrote:"
    Replies header
  • set autoedit
    Go to the editor right away when composing
  • set auto_tag
    Always operate on tagged messages
  • set collapse_all = yes
    Collapse all threads on startup
  • set confirmcreate
    Prompt when creating new files
  • set copy = no Don’t create a copy of sent emails. This is handled in each account file
  • set 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 emails
  • set editor = "nvim '+argdo setf markdown' '+:star' '+:put! _' '+:put! _' '+:noh'"
    Use Neovim to write emails
    star: start in insert mode
    put: add empty line at top of file
    noh: no highlight
  • set envelope_from = yes
    Allow correct server identification
  • set fast_reply
    Skip initial prompts when replying
  • set fcc_attach = yes
    Keep attachments in copies of sent messages
  • set folder = ~/.mail Set emails folder
  • set folder_format = "%t %f"
    File browser format
  • set forward_decode
    Weed and MIME decode forwarded messages
  • set forward_format = "Fwd: %s"
    Subject to use when forwarding messages
  • set header`
    Include message header when replying
  • set header_cache = ~/.mutt/cache/headers
  • set header_cache_compress_method = "zstd"
  • set header_cache_compress_level = 22
  • set help = no
  • set hostname = ""
  • set imap_check_subscribed
  • set implicit_autoview = yes
    Pager shows parts having a mailcap viewer
  • set include = yes
    Always include messages when replying
  • set indent_string = " "
    How to quote replied text
  • set 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 = 60

  • set mail_check_stats = yes

  • set mark_old = yes
    Don’t notify about new unread messages if I have already visited the folder they are in

  • set markers = no

  • set message_cachedir = ~/.mutt/cache/bodies

  • set mbox = ~/.mail
    Where to store read messages

  • set mbox_type = Maildir

  • set mime_forward = no
    Message are forwarded in email body

  • set move = no
    Don’t move read mails to mbox folder

  • set 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 post

  • set nm_default_url = "notmuch://$HOME/.mail"
    Default notmuch folder. See the notmuch section

  • set nm_query_type = threads
    Bring in the whole thread instead of just the matched message

  • set nm_record = yes
    Include sent messages in notmuch searches

  • set noconfirmappend
    Don’t ask me if i want to append to mailboxes

  • set noprompt_after
    Ask me for a command after the external pager exits

  • set nosave_empty
    Remove files when no messages are left

  • set pager_format = "⏲️ %@date@ %@attachment_info@ %s"
    Format of the pager status bar

  • set pager_index_lines = 10
    How many index lines to show in the pager

  • set pager_stop
    Don’t move to the next message on next-page

  • set postponed = ~/.mail/postponed
    Postponed email folder

  • set query_format = "%t %-50.50a %n"
    Don’t cut lines for addresses autocompletion

  • set quote_regexp = "^ *[a-zA-Z]*[>:#}]"
    How to catch quoted text

  • set read_inc = 25
    Show progress when reading a mailbox

  • set recall = ask-yes
    Prompt to recall postponed messages

  • set reply_to
    Always use reply-to if present

  • set reply_regexp = "^(([Rr][Ee]?(\[[0-9]+\])?: *)?(\[[^]]+\] *)?)*"
    How to identify replies in the subject line

  • set reverse_name
    Use my address as it appears in the message I am replying to

  • set 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 = yes

  • set sidebar_format = "%D%* %?N? [%N]? %S"

  • set sidebar_indent_string = " "

  • set sidebar_non_empty_mailbox_only = yes

  • set sidebar_divider_char = " | "

  • set sidebar_short_path = yes

  • set sidebar_sort_method = alpha

  • set sidebar_visible = no

  • set sidebar_width = 50

  • set sig_dashes = no
    Remove automatic dashes in signature

  • set 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 words

  • set sort = last-date
    Sort overall thread with latest activity first

  • set sort_aux = last-date-received
    Within thread, sort subthreads by date received

  • set sort_browser = alpha
    How to sort files in the dir browser

  • set sort_re

  • set status_format = " 📥 %f "

  • set status_on_top

  • set text_flowed = yes

  • set tilde
    Virtual lines to pad blank lines in the pager

  • set tmpdir = /tmp
    Where to store temp files

  • set query_command = "abook --mutt-query '%s'"
    Manage contacts with abook . Contacts names and autocompletion is done directly in Neomut

  • set use_from
    Always generate the `From:’ header field

  • set use_threads = reverse
    Primary sorting method

  • set virtual_spoolfile = yes

  • set visual = nvim
    Editor invoked by ~v in the builtin editor

  • set wait_key = no
    Won’t ask “press key to continue”

  • set wrap = 0

  • set write_inc = 25
    Show progress while writing mailboxes

  • unset imap_passive

  • ignore * Ignore all lines by default

  • unignore to cc bcc mail-followup-to x-mailer x-url
    Unignore from: subject to cc bcc mail-followup-to date x-mailer x-url

  • unhdr_order *

  • hdr_order to cc bcc
    hdr_order from to cc bcc subject date

  • index-format-hook attachment_info '~X >0' "✉"
    Show attachment icon if exist

  • index-format-hook attachment_info '~X 0' " "
    Hide attachment icon if none

  • set 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-reply

  • mailboxes base="/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 for notmuch
    Make sure to replace [Account] with your account name.

  • virtual-mailboxes "Today" "notmuch://?query=date:today not folder:Drafts"
    Use notmuch to create a unified mailbox with today’s emails

  • virtual-mailboxes "Unread" "notmuch://?query=tag:unread or tag:old not folder:Drafts"
    Use notmuch to create a unified mailbox with unread emails

  • alternative_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 message

  • auto_view application/ics

  • auto_view text/html

Start programs at launch. Davmail is needed for my work emails:

sh
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 name

  • folder-hook notmuch 'source ~/.mutt/notmuch'

  • reply-hook . 'push z'
    Move to the top of the body email when replying

  • source "~/.mutt/color" [see the color section ]

  • source "~/.mutt/bind" [see the keybindings section ]


SCRIPTS

The mutt_bgrun script:

sh
#!/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.

sh
#!/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

This post is part of the Neomutt series
  1. The power of emails in the terminal: Neomutt (1/3)
  2. The power of emails in the terminal: Neomutt (2/3)
  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.


Interact with this post using Mastodon or

Comment on wwwgem's post

Copy and paste this URL into the search field of your favourite Fediverse app or the web interface of your Mastodon server.

✄ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈