💾 Archived View for rawtext.club › ~jmq › recycled › mblaze-flow.gmi captured on 2023-11-14 at 11:16:00. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2023-01-29)

➡️ Next capture (2024-06-16)

-=-=-=-=-=-=-

Customizing mblaze for an "inbox zero" email workflow

April--July 2021

All mail clients suck. This one just sucks less.
-- mutt documentation

Motivation

Having used mutt to manage my email for the past decade, I graciously concede that many of the alternatives are clunkier, underpowered, or more resource-intensive, compared to mutt. I stuck with mutt for so long out of inertia, making incremental improvements to its configuration file as I discovered more exotic options (e.g., a send-hook to customize the "From: " header depending on the recipient's address). But lately I was becoming disenchanted with the idea of packing all this exotic functionality into a monolithic program. The turning point came when I replied to a helpdesk thread on SDF and erroneously advised the OP with a mashup of different syntax from the mutt and procmail config files. At that point I realized that the amazing configurability of mutt made it hard to juggle all its features in working memory. I sought out a replacement that would allow me to deploy mental bandwidth on the emails themselves, not on the tool that accesses them.

I might have followed solene's path and migrated to neomutt, if the updated interface were sufficiently streamlined to overcome my concerns over unnecessary complication. But then I saw intriguing possibilities in the approach taken by mblaze. I had first become aware of mblaze in late 2016, but my schedule back then did not permit much time for reimagining my entire email workflow. With the ample free time afforded by having no commute during the pandemic, I set about trying to replicate an "inbox zero" workflow out of the mblaze suite for handling Maildirs.

The attraction of mblaze is its adherence to the UNIX philosophy that each tool does one task, and complicated tasks are performed by combining multiple tools through pipelines. I expected the reduction in mental overhead to free up working memory for focus on a handful of emails at a time (or just a single message). This narrowing of focus is facilitated in mblaze by trimming the "message sequence" with mpick tests. The message sequence is NOT the same as a mutt mailbox; in fact it can contain messages from multiple mbox files or Maildirs.

Manipulating the message sequence with mblaze is like performing a keyword search in Gmail. Just as the search function of Gmail can be told to ignore "labels", the mlist and mpick commands can be piped together to find matching messages from a disparate collection of Maildirs. Below is my `notmutt` script to illustrate the idea:

#!/usr/bin/env bash
#
# notmutt: populate and display the message sequence, given a list of
# maildirs (with no args, use the default set of maildirs)

usage () {
echo "Usage: $0 (bulk|union|work|inbox|teaching|atpa)*"
exit 1
}

if [ -n "$*" ]; then
	for folder in $@ ; do
		[ -d $HOME/mail/$folder/cur ] || usage
	done
	maildirs=($@)
else
	maildirs=(bulk union house junk inbox play teaching atpa)
fi

# Initialize the sequence with the most recent message sent
mlist $HOME/mail/sent | msort -d | tail -n 1 | mseq -S

# Populate the sequence with relevant messages from the selected folders
TEST="\"X-Irrelevance\" =~ \"Low\""
for folder in "${maildirs[@]}" ; do
	mlist $HOME/mail/$folder | mpick -t "$TEST" | mseq -A
done

# Sort the sequence by date, and display the results
mseq | msort -d | mseq -S
mscan

In mutt the output of such filters ("Limiting" the display to those messages whose headers match certain conditions) cannot be joined together with Boolean "or" to present the result of searching over multiple mailboxes, as done in the array loop above. I would have to visit each mailbox individually and trigger the appropriate "limit" command (perhaps with a macro defined in my muttrc). It's fair to ask whether I really need multiple mailboxes at all; why not just use a single inbox and write muttrc macros for different "limit" commands? This approach is certainly defensible, but I preferred to keep my existing courier-maildrop configuration, which delivers to multiple Maildirs. Archiving old messages based on the names of the parent directories would then be decoupled from the filtering capabilities of mpick and the header extraction of mhdr.

The mail indexer `notmuch` is another way to search across multiple Maildirs, but it relies on a database that must be kept in sync each time the Maildirs get updated. My last experience with a mail user agent that used notmuch for indexing was decidedly underwhelming, and I didn't see a reason to suffer through another mutt lookalike when mblaze could be hacked even more to align with my desired workflow.

Mimicking in mblaze the "archive" functionality of Gmail and Outlook

The Maildirs in the shell script above are populated by courier-maildrop, which adds a custom header called "X-Irrelevance" (roughly the equivalent of the "archived" status in Gmail). Once a message in the sequence has been acted upon, I overwrite the "X-Irrelevance" header so that it no longer shows up when calling `notmutt`. This action, performed by the `mpop` script below, is reversible (by the counterpart script `mpush`) if I want to bring a message back into focus.

#!/usr/bin/env bash

# Pop the current message if no argument was given
if [ $# = 0 ]; then
	poppers="$(mseq .)"
else # try to interpret the arguments using mmsg(7) syntax
	mapfile -t poppers < <(mseq "$@") || exit 1
fi

for msgid in "${poppers[@]}"; do
	[ -f "$msgid" ] || continue
	tmpid="${msgid//\/cur\//\/new\//}"
	# Change the X-Irrelevance header, then overwrite the original file
	reformail -I 'X-Irrelevance: Medium' < "$msgid" > "$tmpid"
	mv "$tmpid" "$msgid"
done

(The unfortunate choices "mpop" and "mpush", whose arguments come from anywhere in the message sequence rather than the top of a stack, are now part of my muscle memory but could be renamed if a more suitable pair were to present themselves.) A typical email session then proceeds roughly along these lines:

1. Retrieve new messages from one or more IMAP servers. Let courier-maildrop tag them appropriately and deliver them to the appropriate Maildir destinations.

2. Run `notmutt` to scan the Maildirs and display all messages still requiring my attention.

3. Skim through any new messages (marked as "unseen" in the mscan output). Then run `mseq | mpick -t '! seen' | mflag -S 1>/dev/null; mseq | mseq -f -S` to change their flags so they no longer show up as "unseen".

4. If any new messages are obviously not worth a second look, pass their indices to `mdelete` (a symlink to `mdeliver` that takes a msg index or range and removes the selected messages from disk).

5. Reply to any messages that I have time to address during this email session.

6. Run `mpop` to mark any messages in the sequence that have received an adequate response at this time.

7. Compose any new messages that I want my teammates to start thinking about.

8. Re-run `notmutt` to check that the message sequence has not grown to an unmanageable length.

On a more infrequent basis (once every few weeks), I can use mpick tests based on the message date to move messages from the default Maildirs to an archive for the current year. This allows long-term backups for later reconstruction of the work that got done by email over the course of each year. A similar use of mpick tests allows me to revisit old threads and follow up on dormant discussions with my less-regular correspondents.

format=flowed and other custom settings

The final hurdle to adopting mblaze was to override its default content-encoding "quoted-printable". I prefer the simplicity of 7-bit encoding, with the charset us-ascii and the parameter "format=flowed", since the resulting files are more easily viewed outside of a dedicated mail program (e.g., with the command-line pager `less`). Quoted-printable encoding offers great advantages when composing emails in languages other than English, but for most of my correspendence this feature would be unnecessary, adding extraneous padding for no obvious benefit. I edited the source code mmime.c in at least three places, as seen in the patch below.

My changes to the mblaze source tree

With these changes, mmime now generates a 7bit-clean message, provided my vimrc has the appropriate settings:

function! FixIndented()
    " remove spaces at end of indented lines
    silent! %s/^\s.*\zs \+$//
endfunction

function! TextFlowed()
    setl tw=72
    setl fo+=w
    setl nojs
    setl nosmartindent
    setl list
    set listchars=trail:•
    setlocal spell
    set spelllang=en_US
endfunction

autocmd BufRead,BufNewFile */sent/cur/* call TextFlowed()
autocmd BufRead,BufWritePre */sent/cur/* call FixIndented()

The patchset above also makes a notable revision to the mcom script, allowing me to reuse a message from my outbox as a template for a new email. I use this feature to perform a manual "mail merge", customizing a weekly reminder message for each student on my roster. Without this patch, the command 'mcom -r $(mseq 17)' would open a draft based on the 17th file in my mail sequence, but with the original message-ID preserved. The Exchange server would then see the same message-ID and silently drop the new email as "already delivered". Until I polled a summer class in a live session and learned that all but one of the students had not received my reminder email, I would never have dug through the davmail logs to discover what the Exchange server was doing with all these "messages from a template".

Back when I first began using mutt, I was an avid user of the feature to spell-check a message immediately prior to sending. In mblaze the compose email loop does not offer such an option after exiting the editor, but thankfully it was easy to incorporate into the editor itself, by placing "setlocal spell; set spelllang=en_US" within the TextFlowed() command.

The mblaze profile does not offer nearly as many features as muttrc, but thankfully it does support automatic selection of the correct Reply-From address. In mutt I found myself writing send-hooks or folder-hooks to change the "From: " header. In mblaze it's as simple as listing all the email addresses people might use to write to me, and if one of them shows up in "To: " or "Cc: ", then mblaze will construct a reply with that address in the "From: " header. In the upstream source code this search is case-sensitive, which fits poorly with the way that most of my correspondents write my address. Hence the other major change to 'mcom' in my patchset above: grep for recognized sending addresses in a case-insensitive manner.

Conclusion

This setup allows a command-line email workflow that focuses my attention on the most urgent tasks. The maildirs continue to retain anything that might prove historically useful, but only the messages that have not yet received a response are presented by default. If further digging through the archives is warranted, I can generate the appropriate message sequence with a one-time invocation of mlist and mpick. The default (cleaned-up) message sequence can be regenerated at any time with the `notmutt` script.

Back to Recycle Bin