Marking the 10th birthday of my e-mail server I decided it is time to share with the world my simple solution to sorting the massive amounts of incoming mail, including spam. Over the years I have accumulated a good number of different email accounts that are difficult to track, thus I have setup a central IMAP server where I fetch all mail into and read exclusively what I choose to. I use several methods to fetch and sort mail, but maybe the most useful utility for sorting mail is imapfilter. imapfilter is written in Lua and relies on configuration files written in Lua to process emails. I’m not a Lua expert, but I managed to setup a simple and scalable method for filtering my mail.

I use three types of filter tables that I can add filters to as I see it fit:

  1. pre_filters: filters applied before any other sorting rules. These rules are great for getting rid of abusive mail, spam, or ban any hosts from filling your mailbox.
  2. filters: filters that do the bulk of message processing and sorting.
  3. post_filters: filters applied after all message processing has been completed. This is useful if for instance you decide to get rid of any messages that are marked as spam, but they were not sorted due to any of the previous rules (so they are most likely spam). This also allows you to place messages that matched no rules to special folders, etc.

Filter tables are simple Lua tables, of which every filter is another table that contains the details of that particular filter:

filter_table_name = {
  { filter1 },
  { filter2 },
}

Individual filters are tables with index keys (header, p and moveto):

filter_table_name = {
  { header = "Header_String" , p = "Pattern_String", moveto = account['folder'] },
  { header = "Other_Header_String" , p = "Other_Pattern_String", moveto = account['folder'] },
}

In the order they appear, filters are applied on a table of email messages, e.g. those messages present in the INBOX of an IMAP account. To apply these filters, I wrote a very simple Lua function (see below parseRules) that takes a table of email messages and a table of filters to apply to them. Bear in mind, that to avoid losing any email, I typically just sort unwanted email in the Trash or a Junk folder that I clean up later, either manually or at certain intervals with imapfilter functionality (not shown here).

Putting altogether in an imapfilter configuration file (e.g. ~/.imapfilter/config.lua) it should look similar to the following:

----------------------
--  Global Options  --
----------------------

options.timeout = 120
options.subscribe = true

----------------
--  Accounts  --
----------------
-- For every account create an account entry and check() its status

acc1 = IMAP {
        server = 'collector.server.com',
        username = 'stathis-user',
        password = 'secret',
        ssl = 'ssl3',
}

acc1.INBOX:check_status()

----------------

acc2 = IMAP {
        server = 'imap.gmail.com',
        username = 'stathis-user',
        password = 'secret',
        ssl = 'ssl3',
}

acc2.INBOX:check_status()

----------------

acc3 = IMAP {
        server = 'imap.work.com',
        username = 'stathis-user',
        password = 'secret',
        ssl = 'ssl3',
}

acc3.INBOX:check_status()

----------------

-- select all messages from all INBOXes and construct a single table of messages
results = acc1.INBOX:select_all() + acc2.INBOX:select_all() + acc3.INBOX:select_all()

-----------------
--  Functions  --
-----------------
-- parseRules is used to filter message results using a table of rules (see below)

-- @param res         the table of messages to filter
-- @param ruleTable   the table of rules to match messages against
parseRules = function ( res, ruleTable )
local subresults = {}
  for _,entry in pairs(ruleTable) do
    -- don't use match_field, it downloads part of the msg (slow)
    subresults = res:contain_field(entry["header"], entry["p"])
    if subresults:move_messages( entry["moveto"] ) == false then
      print("Cannot move messages!")
    end
  end
end

---------------
--  Filters  --
---------------

pre_filters = {
  -- Banned IPs typically sending me spam
  { header = "Received" , p = "85.31.186.26", moveto = acc1['Trash'] },
  { header = "Received" , p = "176.251.35.146", moveto = acc1['Trash'] },
}

filters = {
  
  -- Google Alerts
  { header = "From", p = "googlealerts@google.com", moveto = acc1['Misc/Google_Alerts'] },
  
  -- Intel
  { header = "From", p = "isd@intel-dispatch.com", moveto = acc1['Misc/Intel'] },
  { header = "From", p = "@softwaredispatch.intel.com", moveto = acc1['Misc/Intel'] },
  { header = "From", p = "@software-dispatch.intel.com", moveto = acc1['Misc/Intel'] },
  
  -- Boost
  { header = "Sender", p = "@lists.boost.org", moveto = acc1['Mailing_Lists/Boost'] },
  { header = "To", p = "users@lists.boost.org", moveto = acc1['Mailing_Lists/Boost'] },
  
  -- OpenCV
  { header = "Subject", p = "[OpenCV]", moveto = acc1['Mailing_Lists/OpenCV'] },
  { header = "Subject", p = "[OpenCV - Bug #", moveto = acc1['Misc/Bugs/OpenCV'] },
  
}

post_filters = {
  
  -- Any remaining mail having in the subject SPAM? goes in Junk
  { header = "Subject" , p = "[SPAM?] ", moveto = acc1['Junk'] },
  
}

---------------------
--  Parse messages --
---------------------

-- pre filter messages
parseRules(results, pre_filters)

-- sort messages
parseRules(results, filters)

-- post filter messages
parseRules(results, post_filters)

----------------

Software used

Share this: