Skip to main content Logo (IEC resistor symbol)logo

Quis custodiet ipsos custodes?
Home | About | All pages | RSS Feed | Gopher

Filtering IMAP mail with imapfilter

Published: 17-01-2015 | Author: Remy van Elst | Text only version of this article

Table of Contents


I have several email accounts at different providers. Most of them don't offerfiltering capabilites like Sieve, or only their own non exportable rule system(Google Apps). My mail client of choice, Thunderbird, has filtering capabilitiesbut my phone has not and I don't want to leave my machine running Thunderbirdall the time since it gets quite slow with huge mailboxes. Imapfilter is a mailfiltering utility written in Lua which connects to one or more IMAP accounts andfilters on the server using IMAP queries. It is a lightweight command lineutility, the configuration can be versioned and is simple text and it is veryfast.

If you like this article, consider sponsoring me by trying out a Digital OceanVPS. With this link you'll get $100 credit for 60 days). (referral link)

Imapfilter is configured via a config file. This article will discuss thisconfig file with filtering and other examples. Start with a blank one:

mkdir -p ~/.imapfiltervim ~/.imapfilter/config.lua


imapfilter has a few global options which are configured via theoptions.$OPTION = $VALUE format. These are the ones I have, the manpage hasmore. Comments in the config file are prefix by two dashes (--).

-- One of the work mailservers is slow.-- The time in seconds for the program to wait for a mail server's response (default 60)options.timeout = 120-- According to the IMAP specification, when trying to write a message to a non-existent mailbox, the server must send a hint to the client, whether it should create the mailbox and try again or not. However some IMAP servers don't follow the specification and don't send the correct response code to the client. By enabling this option the client tries to create the mailbox, despite of the server's response. options.create = true-- By enabling this option new mailboxes that were automatically created, get also subscribed; they are set active in order for IMAP clients to recognize themoptions.subscribe = true-- Normally, messages are marked for deletion and are actually deleted when the mailbox is closed. When this option is enabled, messages are expunged immediately after being marked deleted.options.expunge = true


I've defined two example accounts, one for work and one for personal stuff:

account1 = IMAP {  server = "",  username = "",  password = "P@ssw0rd",  ssl = "tls1"}account2 = IMAP {  server = "",  username = "joe",  password = "W0rdP@ss",  ssl = "ssl3"}

You can define as much accounts as needed. You can even get your accounts from offlineimap:

function offlineimap (key)  local status  local value  status, value = pipe_from('grep -A2 ~/.offlineimaprc | grep ' .. key .. '|cut -d= -f2')  value = string.gsub(value, ' ', '')  value = string.gsub(value, '\n', '')  return valueendT = IMAP {  server   = offlineimap('remotehost'),  username = offlineimap('remoteuser'),  password = offlineimap('remotepass'),  ssl = 'ssl3',}

Mailboxes / Folders

imapfilter has the concept of mailboxes. While technically correct, we generalusers just call them (top level) folders. INBOX is a mailbox, other foldersare as well. After an IMAP account has been initialized, mailboxes residing inthat account can be accessed simply as elements of the account table:


If mailbox names don't only include letters, digits and underscores, or beginwith a digit, an alternative form must be used:


A mailbox inside a folder (subfolder) can be only accessed by using thealternative form:


In this article I use this alternative form for ease of use and consistensy.


The filters defined are processed in order from top to bottom. I mostly filtermy inbox by moving messages to another folder. If a message matched a filter itis moved, if it would then lower on match another filter that would not apply tothat mail because it is already moved.

See the manpage for all configuration options.

If you simply want to filter a message based on the sender, receipient orsubject you can use the following. It moves all messages with the Duplicitymailing list address to the mailinglists folder:

  messages = account1["INBOX"]:contain_to("")  messages:move_messages(account1["Mailinglists/Duplicity-Talk"])

If you want to filter based on a few more parameters, you can use the followingoperators, * for AND, + for OR and - for NOT. To filter nagios messageswith a certain subject line:

  messages = account1["INBOX"]:contain_from("")    * account1["INBOX"]:contain_subject("important_hostname")  messages:move_messages(account1["Important/Nagios"])

To move messages from Nagios from less important hosts, but not with the"CRITICAL" subject and mark them as read:

  messages = account1["INBOX"]:contain_from("")    - account1["INBOX"]:contain_subject("CRITICAL:")  messages:mark_seen()    messages:move_messages(account1["Monitoring/Nagios"])    

As you can see the mark_seen() operator marks messages as read.

With these operators you can construct advanced filters.

Copying mail

To copy mail from one account to another account's folder and mark those copiedmessages as read, for archival purposes for example, use the following filter:

    messages = account2['INBOX']:is_unseen()    messages:copy_messages(account1["Backup_of_Account2"])    messages = account1['Backup_of_Account2']:is_unseen()    messages:mark_seen()

Place this at the top of the account2 filtering rules.


Taken from the extended configuration example here is an example piece ofcode which sends mail to an external program and based on the output deletes themessages the program marked as spam.

-- The auxiliary function pipe_to() is supplied for conveniency.  For-- example if there was a utility named "bayesian-spam-filter", which-- returned 1 when it considered the message "spam" and 0 otherwise:all = account1["INBOX"]:is_unseen()results = Set {}for _, mesg in ipairs(all) do    mbox, uid = table.unpack(mesg)    text = mbox[uid]:fetch_message()    if (pipe_to('bayesian-spam-filter', text) == 1) then        table.insert(results, mesg)    endendresults:delete_messages()


The above examples will get you started with message filtering right away. Themanpage and the example config and the extended example configwill get you even further.

Tags: blog, dovecot, filter, gmail, imap, imapfilter, lua, mail, sieve, smtp