diff --git a/mail-server/dovecot.nix b/mail-server/dovecot.nix
index 604a62e..2be417d 100644
--- a/mail-server/dovecot.nix
+++ b/mail-server/dovecot.nix
@@ -27,6 +27,26 @@ let
   dovecotMaildir = "maildir:${cfg.mailDirectory}/%d/%n${maildirLayoutAppendix}";
 
   postfixCfg = config.services.postfix;
+  dovecot2Cfg = config.services.dovecot2;
+
+  stateDir = "/var/lib/dovecot";
+
+  pipeBin = pkgs.stdenv.mkDerivation {
+    name = "pipe_bin";
+    src = ./dovecot/pipe_bin;
+    buildInputs = with pkgs; [ makeWrapper coreutils bash rspamd ];
+    buildCommand = ''
+      mkdir -p $out/pipe/bin
+      cp $src/* $out/pipe/bin/
+      chmod a+x $out/pipe/bin/*
+      patchShebangs $out/pipe/bin
+
+      for file in $out/pipe/bin/*; do
+        wrapProgram $file \
+          --set PATH "${pkgs.coreutils}/bin:${pkgs.rspamd}/bin"
+      done
+    '';
+  };
 in
 {
   config = with cfg; lib.mkIf enable {
@@ -68,6 +88,7 @@ in
 
         protocol imap {
           mail_max_userip_connections = ${toString cfg.maxConnectionsPerUser}
+          mail_plugins = $mail_plugins imap_sieve
         }
 
         protocol pop3 {
@@ -118,14 +139,40 @@ in
         }
 
         plugin {
+          sieve_plugins = sieve_imapsieve sieve_extprograms
           sieve = file:/var/sieve/%u/scripts;active=/var/sieve/%u/active.sieve
           sieve_default = file:/var/sieve/%u/default.sieve
           sieve_default_name = default
+
+          # From elsewhere to Spam folder
+          imapsieve_mailbox1_name = Junk
+          imapsieve_mailbox1_causes = COPY
+          imapsieve_mailbox1_before = file:${stateDir}/imap_sieve/report-spam.sieve
+
+          # From Spam folder to elsewhere
+          imapsieve_mailbox2_name = *
+          imapsieve_mailbox2_from = Junk
+          imapsieve_mailbox2_causes = COPY
+          imapsieve_mailbox2_before = file:${stateDir}/imap_sieve/report-ham.sieve
+
+          sieve_pipe_bin_dir = ${pipeBin}/pipe/bin
+
+          sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment
         }
 
         lda_mailbox_autosubscribe = yes
         lda_mailbox_autocreate = yes
       '';
     };
+
+    systemd.services.dovecot2.preStart = ''
+      rm -rf '${stateDir}/imap_sieve'
+      mkdir '${stateDir}/imap_sieve'
+      cp -p "${./dovecot/imap_sieve}"/*.sieve '${stateDir}/imap_sieve/'
+      for k in "${stateDir}/imap_sieve"/*.sieve ; do
+        ${pkgs.dovecot_pigeonhole}/bin/sievec "$k"
+      done
+      chown -R '${dovecot2Cfg.mailUser}:${dovecot2Cfg.mailGroup}' '${stateDir}/imap_sieve'
+    '';
   };
 }
diff --git a/mail-server/dovecot/imap_sieve/report-ham.sieve b/mail-server/dovecot/imap_sieve/report-ham.sieve
new file mode 100644
index 0000000..da74b34
--- /dev/null
+++ b/mail-server/dovecot/imap_sieve/report-ham.sieve
@@ -0,0 +1,15 @@
+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 "sa-learn-ham.sh" [ "${username}" ];
\ No newline at end of file
diff --git a/mail-server/dovecot/imap_sieve/report-spam.sieve b/mail-server/dovecot/imap_sieve/report-spam.sieve
new file mode 100644
index 0000000..4024b7a
--- /dev/null
+++ b/mail-server/dovecot/imap_sieve/report-spam.sieve
@@ -0,0 +1,7 @@
+require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
+
+if environment :matches "imap.user" "*" {
+  set "username" "${1}";
+}
+
+pipe :copy "sa-learn-spam.sh" [ "${username}" ];
\ No newline at end of file
diff --git a/mail-server/dovecot/pipe_bin/sa-learn-ham.sh b/mail-server/dovecot/pipe_bin/sa-learn-ham.sh
new file mode 100755
index 0000000..76fc4ed
--- /dev/null
+++ b/mail-server/dovecot/pipe_bin/sa-learn-ham.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+set -o errexit
+exec rspamc -h /run/rspamd/worker-controller.sock learn_ham
\ No newline at end of file
diff --git a/mail-server/dovecot/pipe_bin/sa-learn-spam.sh b/mail-server/dovecot/pipe_bin/sa-learn-spam.sh
new file mode 100755
index 0000000..2a2f766
--- /dev/null
+++ b/mail-server/dovecot/pipe_bin/sa-learn-spam.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+set -o errexit
+exec rspamc -h /run/rspamd/worker-controller.sock learn_spam
\ No newline at end of file
diff --git a/mail-server/rspamd.nix b/mail-server/rspamd.nix
index e7e80a8..950ae56 100644
--- a/mail-server/rspamd.nix
+++ b/mail-server/rspamd.nix
@@ -61,6 +61,16 @@ in
           }
         '';
       };
+      workers.controller = {
+        type = "controller";
+        count = 1;
+        bindSockets = [{
+          socket = "/run/rspamd/worker-controller.sock";
+          mode = "0666";
+        }];
+        includes = [];
+      };
+
     };
     systemd.services.rspamd = {
       requires = (lib.optional cfg.virusScanning "clamav-daemon.service");
diff --git a/tests/extern.nix b/tests/extern.nix
index 4ad2ff7..7ccd9ca 100644
--- a/tests/extern.nix
+++ b/tests/extern.nix
@@ -64,6 +64,7 @@ import <nixpkgs/nixos/tests/make-test.nix> {
               };
 
               enableImap = true;
+              enableImapSsl = true;
             };
         };
       client = { nodes, config, pkgs, ... }: let
@@ -79,9 +80,63 @@ import <nixpkgs/nixos/tests/make-test.nix> {
           echo grep '^Message-ID:.*@mail.example.com>$' "$@" >&2
           exec grep '^Message-ID:.*@mail.example.com>$' "$@"
         '';
+        test-imap-spam = pkgs.writeScriptBin "imap-mark-spam" ''
+          #!${pkgs.python3.interpreter}
+          import imaplib
+
+          with imaplib.IMAP4_SSL('${serverIP}') as imap:
+            imap.login('user1@example.com', 'user1')
+            imap.select()
+            status, [response] = imap.search(None, 'ALL')
+            msg_ids = response.decode("utf-8").split(' ')
+            print(msg_ids)
+            assert status == 'OK'
+            assert len(msg_ids) == 1
+
+            imap.copy(','.join(msg_ids), 'Junk')
+            for num in msg_ids:
+              imap.store(num, '+FLAGS', '\\Deleted')
+            imap.expunge()
+
+            imap.select('Junk')
+            status, [response] = imap.search(None, 'ALL')
+            msg_ids = response.decode("utf-8").split(' ')
+            print(msg_ids)
+            assert status == 'OK'
+            assert len(msg_ids) == 1
+
+            imap.close()
+        '';
+        test-imap-ham = pkgs.writeScriptBin "imap-mark-ham" ''
+          #!${pkgs.python3.interpreter}
+          import imaplib
+
+          with imaplib.IMAP4_SSL('${serverIP}') as imap:
+            imap.login('user1@example.com', 'user1')
+            imap.select('Junk')
+            status, [response] = imap.search(None, 'ALL')
+            msg_ids = response.decode("utf-8").split(' ')
+            print(msg_ids)
+            assert status == 'OK'
+            assert len(msg_ids) == 1
+
+            imap.copy(','.join(msg_ids), 'INBOX')
+            for num in msg_ids:
+              imap.store(num, '+FLAGS', '\\Deleted')
+            imap.expunge()
+
+            imap.select('INBOX')
+            status, [response] = imap.search(None, 'ALL')
+            msg_ids = response.decode("utf-8").split(' ')
+            print(msg_ids)
+            assert status == 'OK'
+            assert len(msg_ids) == 1
+
+            imap.close()
+        '';
       in {
         environment.systemPackages = with pkgs; [
-          fetchmail msmtp procmail findutils grep-ip check-mail-id
+          fetchmail msmtp procmail findutils grep-ip check-mail-id test-imap-spam test-imap-ham
         ];
         environment.etc = {
           "root/.fetchmailrc" = {
@@ -325,6 +380,18 @@ import <nixpkgs/nixos/tests/make-test.nix> {
 
       };
 
+      subtest "imap sieve junk trainer", sub {
+          # send email from user2 to user1
+          $client->succeed("msmtp -a test --tls=on --tls-certcheck=off --auth=on user1\@example.com < /etc/root/email1 >&2");
+          # give the mail server some time to process the mail
+          $server->waitUntilFails('[ "$(postqueue -p)" != "Mail queue is empty" ]');
+
+          $client->succeed("imap-mark-spam >&2");
+          $server->waitUntilSucceeds("journalctl -u dovecot2 | grep -i sa-learn-spam.sh >&2");
+          $client->succeed("imap-mark-ham >&2");
+          $server->waitUntilSucceeds("journalctl -u dovecot2 | grep -i sa-learn-ham.sh >&2");
+      };
+
       subtest "no warnings or errors", sub {
           $server->fail("journalctl -u postfix | grep -i error >&2");
           $server->fail("journalctl -u postfix | grep -i warning >&2");