When I switched to my minimalist mail environment (which takes noless than 4 command lines to read my mail) I suddenly found myselfwith a filtering problem. First some background on my environmentI read my mail in mutt. It doesn’t suck, very much. It alsosatisfies my absolute biggest requirement in a mail reader which isthe exact same behavior on Linux as on OS X. Being able to read mymail over SSH isn’t a requirement it’s just a perk. However I do_not_ use mutt’s built in IMAP support, to actually download mymail I use offlineimap to sync my remote mailboxes with a localMaildir format directory. So now I have all my email in~/Mail/dreid.org/INBOX but having all my mail in one place is notreally an optimal solution, and so this brings me to my filteringproblem. I’d used procmail in the past, but frankly at this pointafter so many years of staring at nice clean python code for avariety of purposes I’d rather eat my own excrement than writeanother .procmailrc. Luckily for me there is a nice little toolcalled imapfilter. Written in C but scriptable in Lua so installedit then immediately set about writing a filter for my mailinglists.
The typical List-Id header
My Lua code handles the two most common List-Id headers that I’veseen, and works across all the mailing lists I’mcurrently subscribed to.First there is the domain only header:
List-Id: baypiggies.python.org
Then there is the Description header:
List-Id: Twisted Python Discussion
In my case I wanted to use the first segment of the domain as themailbox name. I use a very simple algorithm that makes thefollowing assumptions:
- Periods only appear in the domain name
- Left Angle brackets only appear around the domain name
- There are no spaces in the domain name
Given the 5 or 6 mailing lists I’m subscribed to and that they allfollow these rules, the obvious deficiencies in the code don’t bother meall that much, however your mileage may vary.
The Code
So without further ado here is the code so you too can be on yourway to mailing list filtering goodness
function parseListId(header) baseheader = string.sub(header, string.find(header, ':')+1, nil) destname = '' for i=1,string.len(baseheader) do c = string.sub(baseheader, i, i) if c == '<' then -- reset destname on any < brackets destname = '' elseif c == '.' then -- stop on the first . return destname elseif c ~= ' ' then -- add any non-space character to the destination name destname = destname .. c end endend
The rest of the config.lua for mailing lists
mailinglist = { 'header "list-id" ""'}results = match(myAccount, 'INBOX', mailinglist)listids = fetchfields(myAccount, 'INBOX', {'list-id'}, results) or {}mailboxes = {}for message, header in pairs(listids)do mailbox = parseListId(header) if not mailboxes[mailbox] then mailboxes[mailbox] = {} end table.insert(mailboxes[mailbox], message)endfor mailbox, messages in pairs(mailboxes)do move(myAccount, ‘INBOX’, myAccount, mailbox, messages)end
Posting this I can see some room for improvement, I could cache andcompare the actual unparsed header (it shouldn’t vary from message tomessage) which would reduce the number of calls to parseListId (I’mnot sure it’s slow enough to worry about though.So now you have what I think is a pretty decent solution, eventuallyimapfilter will get run by a cronjob on my server (cron seems to notactually work on OS X tiger, which is another blog post all together.)Then I’ll only have to run 3 commands to check my mail. Next timeI’ll teach you how to create archives for all subscribed mailboxes.Which is a ridiculously easy bit of lua though I had to patchimapfilter to not get an EXC_BAD_ACCESS when reading folder lists frommy server.
Technorati Tags: code, lua, mail