diff --git a/default.nix b/default.nix
index 8bcb85f..e22e157 100644
--- a/default.nix
+++ b/default.nix
@@ -59,6 +59,29 @@ in
               ```
             '';
           };
+
+          sieveScript = mkOption {
+            type = with types; nullOr lines;
+            default = null;
+            example = ''
+              require ["fileinto", "mailbox"];
+
+              if address :is "from" "notifications@github.com" {
+                fileinto :create "GitHub";
+                stop;
+              }
+
+              # This must be the last rule, it will check if list-id is set, and
+              # file the message into the Lists folder for further investigation
+              elsif header :matches "list-id" "<?*>" {
+                fileinto :create "Lists";
+                stop;
+              }
+            '';
+            description = ''
+              Per-user sieve script.
+            '';
+          };
         };
 
         config.name = mkDefault name;
diff --git a/mail-server/dovecot.nix b/mail-server/dovecot.nix
index 840d090..fb8d963 100644
--- a/mail-server/dovecot.nix
+++ b/mail-server/dovecot.nix
@@ -107,6 +107,10 @@ in
             special_use = \Sent
           }
         }
+
+        plugin {
+          sieve = file:/var/sieve/%u.sieve
+        }
       '';
     };
   };
diff --git a/mail-server/users.nix b/mail-server/users.nix
index b05b10e..9484882 100644
--- a/mail-server/users.nix
+++ b/mail-server/users.nix
@@ -30,19 +30,46 @@ let
 
   # accountsToUser :: String -> UserRecord
   accountsToUser = account: {
-    name = account.name;
     isNormalUser = false;
     group = vmailGroupName;
-    inherit (account) hashedPassword;
+    inherit (account) hashedPassword name;
   };
 
   # mail_users :: { [String]: UserRecord }
   mail_users = lib.foldl (prev: next: prev // { "${next.name}" = next; }) {}
     (map accountsToUser (lib.attrValues loginAccounts));
 
-in
-{
+  virtualMailUsersActivationScript = pkgs.writeScript "activate-virtual-mail-users" ''
+    #!${pkgs.stdenv.shell}
 
+    set -euo pipefail
+
+    # Create directory to store user sieve scripts if it doesn't exist
+    if (! test -d "/var/sieve"); then
+      mkdir "/var/sieve"
+      chown "${vmailUserName}:${vmailGroupName}" "/var/sieve"
+      chmod 770 "/var/sieve"
+    fi
+
+    # Copy user's sieve script to the correct location (if it exists).  If it
+    # is null, remove the file.
+    ${lib.concatMapStringsSep "\n" ({ name, sieveScript }:
+      if lib.isString sieveScript then ''
+        cat << EOF > "/var/sieve/${name}.sieve"
+        ${sieveScript}
+        EOF
+        chown "${name}:${vmailGroupName}" "/var/sieve/${name}.sieve"
+      '' else ''
+        if (test -f "/var/sieve/${name}.sieve"); then
+          rm "/var/sieve/${name}.sieve"
+        fi
+        if (test -f "/var/sieve/${name}.svbin"); then
+          rm "/var/sieve/${name}.svbin"
+        fi
+      '') (map (user: { inherit (user) name sieveScript; })
+            (lib.attrValues loginAccounts))}
+  '';
+in {
   config = lib.mkIf enable {
     # set the vmail gid to a specific value
     users.groups = {
@@ -53,5 +80,14 @@ in
     users.users = mail_users // {
       "${vmail_user.name}" = lib.mkForce vmail_user;
     };
+
+    systemd.services.activate-virtual-mail-users = {
+      wantedBy = [ "multi-user.target" ];
+      before = [ "dovecot2.service" ];
+      serviceConfig = {
+        ExecStart = virtualMailUsersActivationScript;
+      };
+      enable = true;
+    };
   };
 }