💾 Archived View for gem.sdf.org › jquah › extracurriculars › mblaze-flow.gmi captured on 2022-04-29 at 11:45:09. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2021-12-03)
-=-=-=-=-=-=-
All mail clients suck. This one just sucks less.
-- mutt documentation
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:
#!/bin/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.
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.
#!/bin/bash # # mpop: prevent a message in the current sequence from showing up on the next search # Compute length of mail sequence let L=$(mseq | wc -l) # Error message if argument is out of bounds, or if no argument at all invalid_argv() { echo "Error: mblaze sequence only goes from 1 to $L." exit 1 } invalid_argc() { echo "Usage: ${0##/*/} (1|2|3|...|$L)" } (( $# >= 1 )) || invalid_argc for num in $@ ; do (( (num <= L) && (num >= 1) )) || invalid_argv # Determine the file name associated with numerical argument msgid=$(mseq | head -n $num | tail -n 1) tmpid=$(echo "$msgid" | sed "s,/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 colleagues 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 narration 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.
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()
Back when I first began using mutt, I was an avid user of the feature to spell-check a message after exiting the editor. In mblaze the compose email tool lacks this feature, 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.
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 mless and mpick. The default (cleaned-up) message sequence can be regenerated at any time with the `notmutt` script.