You are here: Home Tech Sieve server-side mail filtering with Postfix, Dovecot

Sieve server-side mail filtering with Postfix, Dovecot

by Chris Shenton last modified May 16, 2009 07:14 PM
Turned out to be easier than I expected, but took a bit of poking around.

I've got a bunch of computers that access my mail server, so naturally the server runs IMAP so I can store and file the mail on the server.  To filter my mail -- dozens of lists, several clients (Gnus, Mail.app, Evolution) -- I had each of my mail readers running their own client-side filtering.  This meant I had to do it on each one, but had to be consistent, and sometimes one box put things some place that I had configured another box to file into a different box.  Tedious. Slow too because each client files the mail at retrieval time, while I'm waiting to read.

I used to be a qmail and courier-imap guy, but switched to Postfix and Dovecot and have been happy with them -- there's plenty of features the community's developed for both.  Seems odd to me, but Dovecot -- my IMAP server -- has a local delivery agent (LDA) called 'deliver' that can be used with some Sieve implementation to file mail.  Once I had this set up, I could write sieve rules in my account on the server and the mail will get filed at delivery time.

I'm currently using the system accounts so this is a little simpler than virtual domains and accounts. I built Postfix and Dovecot from FreeBSD's ports collection, so did the same for dovecot-sieve.

Configure Postfix to use Dovecot's LDA 'deliver' in /usr/local/etc/postfix/main.cf:

mailbox_command = /usr/local/libexec/dovecot/deliver

If you're using virtual accounts there may be lines to add to master.cf as well.

Now edit /usr/local/etc/dovecot.conf; I turned up the logging and used syslog (as log files were created as the normal user, preventing subsequent user logs to write).

protocol lda {
  #...
  deliver_log_format = %$ -- FROM=%f SUBJECT=%s
  mail_plugins = cmusieve
  log_path =
  info_log_path =
  syslog_facility = mail
}

The 'sievec' and 'sieved' programs were installed into /usr/local/libexec/dovecot (next to 'deliver') by the dovecot-sieve port. Dovecot-1.1 which I'm running supports cmusieve, but the upcoming 1.2 release has its own sieve which I'll probably switch to eventually since it has some features not in cmusieve.

Do a "postfix reload" to get it to start using the LDA.  It should continue to delivery to your INBOX as before, the default behavior without a sieve configuration.

Create a ~/.dovecot.sieve file with filters like:

require ["fileinto", "regex" ];

if header :contains "List-ID" "ip@v2.listbox.com"                               { fileinto "INBOX.list.ip"; stop; }
if header :contains "List-Post" "<mailto:gnu.emacs.gnus@googlegroups.com>"      { fileinto "INBOX.list.gnu-emacs-gnus"; stop;}

if address :is "sender" "repoze-dev-bounces@lists.repoze.org"           { fileinto "INBOX.list.repoze-dev"; stop; }
if address :is "sender" "owner-freebsd-net@freebsd.org"                 { fileinto "INBOX.list.freebsd-net"; stop; }

if anyof (
   address :is "from" "support@myclient.com",
   address :is "from" "x@mail.myclient.com",
   address :is "from" "root@myclient.com")                                { fileinto "INBOX.in.myclient.zw"; stop; }   

My old courier-imap used "INBOX." as the namespace prefix for all mailboxes, so I'm just using the same file structure with Dovecot. I'll probably remove that later since it's not needed and will make my Gnus mailbox display shorter.

There are much more sophisticated rules you can use.  Be careful with your punctuation: sieve doesn't tolerate extra commas which might make the anyof(...) lists easier to read.  There are filter file test sites on the net but I ended up just watching my home directory: when mail comes in, the sieve program tries to parse the file to ~/.dovecot.sievec and if it can't it leaves an error file descibing the problem.

One thing I'm really missing is a regex with variable substitution; you specify "variable" on the same 'require' lines as the "regex".  This would allow me to have a rule that catches most email lists with something like:

if header :regex "List-ID" "<(.*)@" {
       fileinto "lists.${1}"; stop;
}

Currently, cmusieve that I'm using doesn't support this RFC extension, and it will cause an error if you try to require "variables".

There appear to be GUI tools to create rules, and a managesieve protocol which allows uploading them to your account remotely. Those seem great for larger sites or ones which host editor-fearing users, but for me, editing the filter file by hand is no biggie.
Share this: