Rspamd Filtering for Fetchmail, Dovecot & Sieve with Learning

Philipp Mundhenk · August 12, 2025

I have been having some issues with setting up rspamd in combination with Dovecot >2.4. It seemed there was no complete documentation and the configuration options have changed quite a bit in Dovecot 2.4. So I pieced this together myself and provide it as a short tutorial here. Of course, there is lots of possible implementations of this, I use a smarthost setup with Fetchmail. Since no spam filter is perfect out-of-the-box, I also wanted to have learning of spam/ham supported.

Overview

The core idea is simple: Fetchmail pulls mail → Rspamd scans and tags it → Dovecot + Sieve deliver it into the right folder → moving mail between folders teaches Rspamd.

Mail Flow

Here’s the sequence from retrieval to delivery:

[Remote Mailbox] 
    ↓ (POP3/IMAP fetch)
[Fetchmail]
    ↓  (via rspamc --mime)
[Rspamd]
    ↓  (via dovecot-lda)
[Dovecot + Sieve]
    ↳ Inbox or Spam folder
    ↳ Spam/Ham learning on folder moves

Installation

You will need to install Sieve (including IMAPSieve) and Rspamd, the exact syntax for this depends on your system. In my case, on Alpine, this looks like this:

RUN apk add --no-cache \
	dovecot-pigeonhole-plugin \
	rspamd \
	rspamd-client

Also make sure that Rspamd (and Dovecot, of course) is running, e.g., via systemd, or e.g., your container start script.

Fetchmail Configuration

I use Fetchmail to grab mail from my remote POP3 server, send it through Rspamd, and then deliver it to Dovecot.

/etc/fetchmailrc

poll mail.example.com protocol pop3 port 995 user user@example.com
password password ssl
flush forcecr
mda "/usr/bin/rspamc --mime --exec '/usr/libexec/dovecot/dovecot-lda -d user'"

Key points:

  • --mime ensures that checking result is added to e-mail headers. Most notable header X-Spam.
  • rspamc connects to the Rspamd daemon on 127.0.0.1:11334.
  • Delivery is handled by dovecot-lda so Sieve rules can run.

Dovecot Configuration

I enable Sieve for delivery and IMAPSieve to trigger learning when mail is moved between folders.

Enable Sieve in delivery protocols

/etc/dovecot/conf.d/10-master.conf

protocol lmtp {
  mail_plugins {
    sieve = yes
  }
}

/etc/dovecot/conf.d/15-lda.conf

protocol lda {
  mail_plugins {
    sieve = yes
  }
}

/etc/dovecot/conf.d/20-imap.conf

protocol imap {
  mail_plugins {
    imap_sieve = yes
  }
}

IMAPSieve Configuration for Learning

This tells Dovecot what to do when mail is copied to or from the Spam folder.

/etc/dovecot/conf.d/90-sieve.conf

sieve_script personal {
  path = ~/.dovecot.sieve
}

sieve_script default {
  path = /home/.dovecot.sieve
}

sieve_plugins {
  sieve_imapsieve = yes
  sieve_extprograms = yes
}

sieve_global_extensions {
  vnd.dovecot.pipe = yes
  vnd.dovecot.execute = yes
}

sieve_pipe_socket_dir = sieve
sieve_execute_socket_dir = sieve
sieve_pipe_bin_dir = /etc/dovecot/sieve

imapsieve_from Spam {
  sieve_script ham {
    type = before
    cause = copy
    path = /etc/dovecot/sieve/ham.sieve
  }
}

mailbox Spam {
  sieve_script spam {
    type = before
    cause = copy
    path = /etc/dovecot/sieve/spam.sieve
  }
}

Sieve Scripts for Learning

/etc/dovecot/sieve/ham.sieve

require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];

if environment :matches "imap.mailbox" "*" {
  set "mailbox" "${1}";
}

if string "${mailbox}" "Trash" {
  stop;
}

if environment :matches "imap.user" "*" {
  set "username" "${1}";
}

pipe :copy "learn-ham.sh";

Explanation:

  • Loads extensions needed for IMAPSieve and piping to an external program.
  • Captures the mailbox name from which the mail was moved.
  • Stops if the destination is Trash (I don’t want to learn from deletions).
  • Runs the script learn-ham.sh to train Rspamd that this is a legitimate (ham) message.

/etc/dovecot/sieve/spam.sieve

require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
pipe :copy "learn-spam.sh";

Explanation:

  • Much simpler — whenever a message is copied into the Spam folder, it runs learn-spam.sh to tell Rspamd this is spam.

Learning Scripts

/etc/dovecot/sieve/learn-ham.sh

#!/bin/sh
exec /usr/bin/rspamc -h 127.0.0.1:11334 learn_ham

/etc/dovecot/sieve/learn-spam.sh

#!/bin/sh
exec /usr/bin/rspamc -h 127.0.0.1:11334 learn_spam

I make them executable:

chmod +x /etc/dovecot/sieve/learn-*.sh

Default Delivery Sieve

/home/.dovecot.sieve

require ["fileinto", "envelope", "variables", "mailbox", "subaddress", "fileinto", "imap4flags"];

# Handle spam detected by Rspamd
if header :is "X-Spam" "yes" {
    fileinto :create "Spam";
    stop;
} elsif header :is "X-Spam-Flag" "YES" {
    fileinto :create "Spam";
    stop;
} else {
    fileinto "Inbox";
}

Explanation:

  • If Rspamd adds X-Spam: yes (X-Spam-Flag: yes is used by an external spam classifier of another server I fetch mail from), the mail is filed into the Spam folder.
  • Otherwise, it goes into the main Inbox.
  • The :create flag ensures folders are created automatically if they don’t exist.
  • Note that this script is only run if the user has not specified a sieve script! You may want to disallow user scripts (see sieve_script personal above), if you are afraid your users may override this script.

How Learning Works in Practice

  • Mark spam: If I drag a message from Inbox to Spam, Dovecot triggers learn_spam.sh.
  • Mark ham: If I drag a message from Spam to Inbox, Dovecot triggers learn_ham.sh.
  • No learning from Trash: Moving to Trash doesn’t trigger anything.

Over time, Rspamd builds up statistical learning, improving its accuracy.

Testing

To test the filtering, wait for the next spam mail (it will come, don’t worry) and see if this ends up in the Spam folder. Check the headers and look for the X-Spam header or the X-Spam-Score to see if Rspamd classifies an email as spam or not.

To check the learning functionality, watch the Rspamd logs at /var/log/rspamd/rspamd.log. You should see an entry like this:

(controller) <xxx>; csession; rspamd_controller_learn_fin_task: <127.0.0.1> learned message as spam: xxx@example.com

when training for spam, or

(controller) <xxx>; csession; rspamd_controller_learn_fin_task: <127.0.0.1> learned message as ham: xxx@example.com

when training for ham.

Conclusion

This setup lets me run strong server-side spam filtering and training. The key is chaining Fetchmail → Rspamd → Dovecot with Sieve handling both delivery and feedback. It works quietly in the background: I just move messages between folders, and the spam filter keeps getting smarter.

Twitter, Facebook, LinkedIn