Macros to score mail in mutt

· 700 words · 4 minute read

In my early mutt days I used procmail and SpamAssassin on my desktop machines. I had a bunch of mutt macros set up to help score email and mark things as spam or not-spam. Once I started using IMAP on someone else’s server that all fell by the wayside, but I really missed being able to score mail while I was processing it. I use Fastmail these days, and they support Sieve scripts, but haven’t chosen to expose the API for that. That’s too bad.

Mutt does, however, have pretty good facilities for scoring individual properties of a message, then do different color treatments for a given message’s score in the index. Scoring and coloring can be driven by mutt’s extensive list of search patterns..

This is a lightly annotated selection from the top of my mutt scores file, which I source in ~/.mutt/muttrc at startup:

# -*- mode: conf-unix -*-
# Mutt scoring file

unscore * # start fresh
score ~p +5 # Mail addressed to me or one of my alternates gets 5 points

# Date-based scoring penalties -- older things fall down
score ~d>3d -1
score ~d>7d -3
score ~d>14d -10

score "~O" +10 # "Old" in mutt is "seen but unread". +10 so I don't miss it
score "~F" +20 # flagged = +20 so it stays in the interesting view for a while, even if older
score "!~p ~d>7d" -10 # not for me directly, getting old, let it fade away
score "!~l" +2 # to a known list, give it a bump

Given some scores, you can add color treatments:

color index white default "~n 6-9"
color index white red "~f trimet ~s Service\\ Alert"
color index brightblack default "~f trimet ~s Service\\ Alert ~d>1d"
color index magenta default "~n <5"
color index brightyellow default "~n >10"
color index brightred default "~n >19"

Adding a score to the scores file manually is no big deal. Mine tends to include specific people, work email lists, things I definitely don’t want to miss (e.g. service alerts from TriMet), and things that I should keep seeing but want to deemphasize (like business messages from hosting providers). But it’d also be nice to do some scoring as I’m reviewing mail to deal with new important senders, necessary nuisances, etc.

So I wrote some ruby and mutt macros that let me pipe a given message into a script that extracts the sender and writes a +/- modifier to their score in a scored file I source alongside my scores file.

#!/usr/bin/env ruby

require 'mail'
require 'tempfile'

# Wants a +/- integer, e.g. +20
score = ARGV.first

score_file = "/Users/mph/.mutt/scored"

msg = Tempfile.new('msg')

msg.write($stdin.read)

mail = Mail.read(msg)

from = mail.from.first

File.open(score_file, "a") { |f| f.write "score ~f#{from} #{score}\n"}

msg.close

msg.unlink

Pretty much just “parse the mail for the first from address, wrap it in a mutt score stanza, tack that on to the end of scored.”

Then I added some macros:

# Score messages
macro index,browser .sp "<pipe-entry>~/.mutt/mailscore.rb +5\n<enter-command>source ~/.mutt/scored<enter>" # score sender +5
macro index,browser .sP "<pipe-entry>~/.mutt/mailscore.rb +20\n<enter-command>source ~/.mutt/scored<enter>" # score sender +20
macro index,browser .sm "<pipe-entry>~/.mutt/mailscore.rb -5\n<enter-command>source ~/.mutt/scored<enter>" # score sender -5
macro index,browser .sM "<pipe-entry>~/.mutt/mailscore.rb -20\n<enter-command>source ~/.mutt/scored<enter>" # score sender -20

They just pipe the message into the Ruby script with a scoring argument, the script modifies scored, then the macro re-sources scored so the index reflects the new scoring.

Tapping .sp (score plus) in the index scores a sender up, .sm (score minus) scores them down. .sP and .sM are embiggened scores.

The way mutt works, it doesn’t accumulate these pluses and minuses, so when it parses the scored file it’s just going to use the last modifier I added. That seems fine, since they’re broad and there are other scoring factors at work. Over time the file will get long. Maybe the right thing to do is read the file in, try to match on a sender, and overwrite their entry, just to cut down on duplication.

The net effect of the current system is that the index is color-coded into four broad tiers: The super important, the pretty important, kinda average and should be read, and not particularly interesting and maybe worth an unsubscribe.