{
  config,
  lib,
  pkgs,
  ...
}:

let
  inherit (lib)
    attrValues
    concatMapStringsSep
    concatStrings
    concatStringsSep
    flatten
    imap1
    literalExpression
    mapAttrsToList
    mkEnableOption
    mkIf
    mkOption
    mkRemovedOptionModule
    optional
    optionalAttrs
    optionalString
    singleton
    types
    mkRenamedOptionModule
    nameValuePair
    mapAttrs'
    listToAttrs
    filter
    ;
  inherit (lib.strings) match;

  cfg = config.services.dovecot2;
  dovecotPkg = pkgs.dovecot;

  baseDir = "/run/dovecot2";
  stateDir = "/var/lib/dovecot";

  sieveScriptSettings = mapAttrs' (
    to: _: nameValuePair "sieve_${to}" "${stateDir}/sieve/${to}"
  ) cfg.sieve.scripts;
  imapSieveMailboxSettings = listToAttrs (
    flatten (
      imap1 (
        idx: el:
        singleton {
          name = "imapsieve_mailbox${toString idx}_name";
          value = el.name;
        }
        ++ optional (el.from != null) {
          name = "imapsieve_mailbox${toString idx}_from";
          value = el.from;
        }
        ++ optional (el.causes != [ ]) {
          name = "imapsieve_mailbox${toString idx}_causes";
          value = concatStringsSep "," el.causes;
        }
        ++ optional (el.before != null) {
          name = "imapsieve_mailbox${toString idx}_before";
          value = "file:${stateDir}/imapsieve/before/${baseNameOf el.before}";
        }
        ++ optional (el.after != null) {
          name = "imapsieve_mailbox${toString idx}_after";
          value = "file:${stateDir}/imapsieve/after/${baseNameOf el.after}";
        }
      ) cfg.imapsieve.mailbox
    )
  );

  mkExtraConfigCollisionWarning = term: ''
    You referred to ${term} in `services.dovecot2.extraConfig`.

    Due to gradual transition to structured configuration for plugin configuration, it is possible
    this will cause your plugin configuration to be ignored.

    Consider setting `services.dovecot2.pluginSettings.${term}` instead.
  '';

  # Those settings are automatically set based on other parts
  # of this module.
  automaticallySetPluginSettings = [
    "sieve_plugins"
    "sieve_extensions"
    "sieve_global_extensions"
    "sieve_pipe_bin_dir"
  ]
  ++ (builtins.attrNames sieveScriptSettings)
  ++ (builtins.attrNames imapSieveMailboxSettings);

  # The idea is to match everything that looks like `$term =`
  # but not `# $term something something`
  # or `# $term = some value` because those are comments.
  configContainsSetting = lines: term: (match "[[:blank:]]*${term}[[:blank:]]*=.*" lines) != null;

  warnAboutExtraConfigCollisions = map mkExtraConfigCollisionWarning (
    filter (configContainsSetting cfg.extraConfig) automaticallySetPluginSettings
  );

  sievePipeBinScriptDirectory = pkgs.linkFarm "sieve-pipe-bins" (
    map (el: {
      name = builtins.unsafeDiscardStringContext (baseNameOf el);
      path = el;
    }) cfg.sieve.pipeBins
  );

  dovecotConf = concatStrings [
    ''
      base_dir = ${baseDir}
      protocols = ${concatStringsSep " " cfg.protocols}
      sendmail_path = /run/wrappers/bin/sendmail
      mail_plugin_dir = /run/current-system/sw/lib/dovecot/modules
      # defining mail_plugins must be done before the first protocol {} filter because of https://doc.dovecot.org/configuration_manual/config_file/config_file_syntax/#variable-expansion
      mail_plugins = $mail_plugins ${concatStringsSep " " cfg.mailPlugins.globally.enable}
    ''

    (concatStringsSep "\n" (
      mapAttrsToList (protocol: plugins: ''
        protocol ${protocol} {
          mail_plugins = $mail_plugins ${concatStringsSep " " plugins.enable}
        }
      '') cfg.mailPlugins.perProtocol
    ))

    (
      if cfg.sslServerCert == null then
        ''
          ssl = no
          disable_plaintext_auth = no
        ''
      else
        ''
          ssl_cert = <${cfg.sslServerCert}
          ssl_key = <${cfg.sslServerKey}
          ${optionalString (cfg.sslCACert != null) ("ssl_ca = <" + cfg.sslCACert)}
          ${optionalString cfg.enableDHE ''ssl_dh = <${config.security.dhparams.params.dovecot2.path}''}
          disable_plaintext_auth = yes
        ''
    )

    ''
      default_internal_user = ${cfg.user}
      default_internal_group = ${cfg.group}
      ${optionalString (cfg.mailUser != null) "mail_uid = ${cfg.mailUser}"}
      ${optionalString (cfg.mailGroup != null) "mail_gid = ${cfg.mailGroup}"}

      mail_location = ${cfg.mailLocation}

      maildir_copy_with_hardlinks = yes
      pop3_uidl_format = %08Xv%08Xu

      auth_mechanisms = plain login

      service auth {
        user = root
      }
    ''

    (optionalString cfg.enablePAM ''
      userdb {
        driver = passwd
      }

      passdb {
        driver = pam
        args = ${optionalString cfg.showPAMFailure "failure_show_msg=yes"} dovecot2
      }
    '')

    (optionalString (cfg.mailboxes != { }) ''
      namespace inbox {
        inbox=yes
        ${concatStringsSep "\n" (map mailboxConfig (attrValues cfg.mailboxes))}
      }
    '')

    (optionalString cfg.enableQuota ''
      service quota-status {
        executable = ${dovecotPkg}/libexec/dovecot/quota-status -p postfix
        inet_listener {
          port = ${cfg.quotaPort}
        }
        client_limit = 1
      }

      plugin {
        quota_rule = *:storage=${cfg.quotaGlobalPerUser}
        quota = count:User quota # per virtual mail user quota
        quota_status_success = DUNNO
        quota_status_nouser = DUNNO
        quota_status_overquota = "552 5.2.2 Mailbox is full"
        quota_grace = 10%%
        quota_vsizes = yes
      }
    '')

    # General plugin settings:
    # - sieve is mostly generated here, refer to `pluginSettings` to follow
    # the control flow.
    ''
      plugin {
        ${concatStringsSep "\n" (mapAttrsToList (key: value: "  ${key} = ${value}") cfg.pluginSettings)}
      }
    ''

    cfg.extraConfig
  ];

  mailboxConfig =
    mailbox:
    ''
      mailbox "${mailbox.name}" {
        auto = ${toString mailbox.auto}
    ''
    + optionalString (mailbox.autoexpunge != null) ''
      autoexpunge = ${mailbox.autoexpunge}
    ''
    + optionalString (mailbox.specialUse != null) ''
      special_use = \${toString mailbox.specialUse}
    ''
    + "}";

  mailboxes =
    { name, ... }:
    {
      options = {
        name = mkOption {
          type = types.strMatching ''[^"]+'';
          example = "Spam";
          default = name;
          readOnly = true;
          description = "The name of the mailbox.";
        };
        auto = mkOption {
          type = types.enum [
            "no"
            "create"
            "subscribe"
          ];
          default = "no";
          example = "subscribe";
          description = "Whether to automatically create or create and subscribe to the mailbox or not.";
        };
        specialUse = mkOption {
          type = types.nullOr (
            types.enum [
              "All"
              "Archive"
              "Drafts"
              "Flagged"
              "Junk"
              "Sent"
              "Trash"
            ]
          );
          default = null;
          example = "Junk";
          description = "Null if no special use flag is set. Other than that every use flag mentioned in the RFC is valid.";
        };
        autoexpunge = mkOption {
          type = types.nullOr types.str;
          default = null;
          example = "60d";
          description = ''
            To automatically remove all email from the mailbox which is older than the
            specified time.
          '';
        };
      };
    };
in
{
  imports = [
    (mkRemovedOptionModule [ "services" "dovecot2" "package" ] "")
    (mkRemovedOptionModule [
      "services"
      "dovecot2"
      "modules"
    ] "Now need to use `environment.systemPackages` to load additional Dovecot modules")
    (mkRenamedOptionModule
      [ "services" "dovecot2" "sieveScripts" ]
      [ "services" "dovecot2" "sieve" "scripts" ]
    )
  ];

  options.services.dovecot2 = {
    enable = mkEnableOption "the dovecot 2.x POP3/IMAP server";

    enablePop3 = mkEnableOption "starting the POP3 listener (when Dovecot is enabled)";

    enableImap = mkEnableOption "starting the IMAP listener (when Dovecot is enabled)" // {
      default = true;
    };

    enableLmtp = mkEnableOption "starting the LMTP listener (when Dovecot is enabled)";

    hasNewUnitName = mkOption {
      type = types.bool;
      default = true;
      readOnly = true;
      internal = true;
      description = ''
        Inspectable option to confirm that the dovecot module uses the new
        `dovecot.service` name, instead of `dovecot2.service`.

        This is a helper added for the nixos-mailserver project and can be
        removed after branching off nixos-25.11.
      '';
    };

    protocols = mkOption {
      type = types.listOf types.str;
      default = [ ];
      description = "Additional listeners to start when Dovecot is enabled.";
    };

    user = mkOption {
      type = types.str;
      default = "dovecot2";
      description = "Dovecot user name.";
    };

    group = mkOption {
      type = types.str;
      default = "dovecot2";
      description = "Dovecot group name.";
    };

    extraConfig = mkOption {
      type = types.lines;
      default = "";
      example = "mail_debug = yes";
      description = "Additional entries to put verbatim into Dovecot's config file.";
    };

    mailPlugins =
      let
        plugins =
          hint:
          types.submodule {
            options = {
              enable = mkOption {
                type = types.listOf types.str;
                default = [ ];
                description = "mail plugins to enable as a list of strings to append to the ${hint} `$mail_plugins` configuration variable";
              };
            };
          };
      in
      mkOption {
        type =
          with types;
          submodule {
            options = {
              globally = mkOption {
                description = "Additional entries to add to the mail_plugins variable for all protocols";
                type = plugins "top-level";
                example = {
                  enable = [ "virtual" ];
                };
                default = {
                  enable = [ ];
                };
              };
              perProtocol = mkOption {
                description = "Additional entries to add to the mail_plugins variable, per protocol";
                type = attrsOf (plugins "corresponding per-protocol");
                default = { };
                example = {
                  imap = [ "imap_acl" ];
                };
              };
            };
          };
        description = "Additional entries to add to the mail_plugins variable, globally and per protocol";
        example = {
          globally.enable = [ "acl" ];
          perProtocol.imap.enable = [ "imap_acl" ];
        };
        default = {
          globally.enable = [ ];
          perProtocol = { };
        };
      };

    configFile = mkOption {
      type = types.nullOr types.path;
      default = null;
      description = "Config file used for the whole dovecot configuration.";
      apply = v: if v != null then v else pkgs.writeText "dovecot.conf" dovecotConf;
    };

    mailLocation = mkOption {
      type = types.str;
      default = "maildir:/var/spool/mail/%u"; # Same as inbox, as postfix
      example = "maildir:~/mail:INBOX=/var/spool/mail/%u";
      description = ''
        Location that dovecot will use for mail folders. Dovecot mail_location option.
      '';
    };

    mailUser = mkOption {
      type = types.nullOr types.str;
      default = null;
      description = "Default user to store mail for virtual users.";
    };

    mailGroup = mkOption {
      type = types.nullOr types.str;
      default = null;
      description = "Default group to store mail for virtual users.";
    };

    createMailUser =
      mkEnableOption ''
        automatically creating the user
              given in {option}`services.dovecot.user` and the group
              given in {option}`services.dovecot.group`''
      // {
        default = true;
      };

    sslCACert = mkOption {
      type = types.nullOr types.str;
      default = null;
      description = "Path to the server's CA certificate key.";
    };

    sslServerCert = mkOption {
      type = types.nullOr types.str;
      default = null;
      description = "Path to the server's public key.";
    };

    sslServerKey = mkOption {
      type = types.nullOr types.str;
      default = null;
      description = "Path to the server's private key.";
    };

    enablePAM = mkEnableOption "creating a own Dovecot PAM service and configure PAM user logins" // {
      default = true;
    };

    enableDHE = mkEnableOption "ssl_dh and generation of primes for the key exchange" // {
      default = true;
    };

    showPAMFailure = mkEnableOption "showing the PAM failure message on authentication error (useful for OTPW)";

    mailboxes = mkOption {
      type =
        with types;
        coercedTo (listOf unspecified) (
          list:
          listToAttrs (
            map (entry: {
              name = entry.name;
              value = removeAttrs entry [ "name" ];
            }) list
          )
        ) (attrsOf (submodule mailboxes));
      default = { };
      example = literalExpression ''
        {
          Spam = { specialUse = "Junk"; auto = "create"; };
        }
      '';
      description = "Configure mailboxes and auto create or subscribe them.";
    };

    enableQuota = mkEnableOption "the dovecot quota service";

    quotaPort = mkOption {
      type = types.str;
      default = "12340";
      description = ''
        The Port the dovecot quota service binds to.
        If using postfix, add check_policy_service inet:localhost:12340 to your smtpd_recipient_restrictions in your postfix config.
      '';
    };
    quotaGlobalPerUser = mkOption {
      type = types.str;
      default = "100G";
      example = "10G";
      description = "Quota limit for the user in bytes. Supports suffixes b, k, M, G, T and %.";
    };

    pluginSettings = mkOption {
      # types.str does not coerce from packages, like `sievePipeBinScriptDirectory`.
      type = types.attrsOf (
        types.oneOf [
          types.str
          types.package
        ]
      );
      default = { };
      example = literalExpression ''
        {
          sieve = "file:~/sieve;active=~/.dovecot.sieve";
        }
      '';
      description = ''
        Plugin settings for dovecot in general, e.g. `sieve`, `sieve_default`, etc.

        Some of the other knobs of this module will influence by default the plugin settings, but you
        can still override any plugin settings.

        If you override a plugin setting, its value is cleared and you have to copy over the defaults.
      '';
    };

    imapsieve.mailbox = mkOption {
      default = [ ];
      description = "Configure Sieve filtering rules on IMAP actions";
      type = types.listOf (
        types.submodule (
          { config, ... }:
          {
            options = {
              name = mkOption {
                description = ''
                  This setting configures the name of a mailbox for which administrator scripts are configured.

                  The settings defined hereafter with matching sequence numbers apply to the mailbox named by this setting.

                  This setting supports wildcards with a syntax compatible with the IMAP LIST command, meaning that this setting can apply to multiple or even all ("*") mailboxes.
                '';
                example = "Junk";
                type = types.str;
              };

              from = mkOption {
                default = null;
                description = ''
                  Only execute the administrator Sieve scripts for the mailbox configured with services.dovecot2.imapsieve.mailbox.<name>.name when the message originates from the indicated mailbox.

                  This setting supports wildcards with a syntax compatible with the IMAP LIST command, meaning that this setting can apply to multiple or even all ("*") mailboxes.
                '';
                example = "*";
                type = types.nullOr types.str;
              };

              causes = mkOption {
                default = [ ];
                description = ''
                  Only execute the administrator Sieve scripts for the mailbox configured with services.dovecot2.imapsieve.mailbox.<name>.name when one of the listed IMAPSIEVE causes apply.

                  This has no effect on the user script, which is always executed no matter the cause.
                '';
                example = [
                  "COPY"
                  "APPEND"
                ];
                type = types.listOf (
                  types.enum [
                    "APPEND"
                    "COPY"
                    "FLAG"
                  ]
                );
              };

              before = mkOption {
                default = null;
                description = ''
                  When an IMAP event of interest occurs, this sieve script is executed before any user script respectively.

                  This setting each specify the location of a single sieve script. The semantics of this setting is similar to sieve_before: the specified scripts form a sequence together with the user script in which the next script is only executed when an (implicit) keep action is executed.
                '';
                example = literalExpression "./report-spam.sieve";
                type = types.nullOr types.path;
              };

              after = mkOption {
                default = null;
                description = ''
                  When an IMAP event of interest occurs, this sieve script is executed after any user script respectively.

                  This setting each specify the location of a single sieve script. The semantics of this setting is similar to sieve_after: the specified scripts form a sequence together with the user script in which the next script is only executed when an (implicit) keep action is executed.
                '';
                example = literalExpression "./report-spam.sieve";
                type = types.nullOr types.path;
              };
            };
          }
        )
      );
    };

    sieve = {
      plugins = mkOption {
        default = [ ];
        example = [ "sieve_extprograms" ];
        description = "Sieve plugins to load";
        type = types.listOf types.str;
      };

      extensions = mkOption {
        default = [ ];
        description = "Sieve extensions for use in user scripts";
        example = [
          "notify"
          "imapflags"
          "vnd.dovecot.filter"
        ];
        type = types.listOf types.str;
      };

      globalExtensions = mkOption {
        default = [ ];
        example = [ "vnd.dovecot.environment" ];
        description = "Sieve extensions for use in global scripts";
        type = types.listOf types.str;
      };

      scripts = mkOption {
        type = types.attrsOf types.path;
        default = { };
        description = "Sieve scripts to be executed. Key is a sequence, e.g. 'before2', 'after' etc.";
      };

      pipeBins = mkOption {
        default = [ ];
        example = literalExpression ''
          map lib.getExe [
            (pkgs.writeShellScriptBin "learn-ham.sh" "exec ''${pkgs.rspamd}/bin/rspamc learn_ham")
            (pkgs.writeShellScriptBin "learn-spam.sh" "exec ''${pkgs.rspamd}/bin/rspamc learn_spam")
          ]
        '';
        description = "Programs available for use by the vnd.dovecot.pipe extension";
        type = types.listOf types.path;
      };
    };
  };

  config = mkIf cfg.enable {
    security.pam.services.dovecot2 = mkIf cfg.enablePAM { };

    security.dhparams = mkIf (cfg.sslServerCert != null && cfg.enableDHE) {
      enable = true;
      params.dovecot2 = { };
    };

    services.dovecot2 = {
      protocols =
        optional cfg.enableImap "imap" ++ optional cfg.enablePop3 "pop3" ++ optional cfg.enableLmtp "lmtp";

      mailPlugins = mkIf cfg.enableQuota {
        globally.enable = [ "quota" ];
        perProtocol.imap.enable = [ "imap_quota" ];
      };

      sieve.plugins =
        optional (cfg.imapsieve.mailbox != [ ]) "sieve_imapsieve"
        ++ optional (cfg.sieve.pipeBins != [ ]) "sieve_extprograms";

      sieve.globalExtensions = optional (cfg.sieve.pipeBins != [ ]) "vnd.dovecot.pipe";

      pluginSettings = lib.mapAttrs (n: lib.mkDefault) (
        {
          sieve_plugins = concatStringsSep " " cfg.sieve.plugins;
          sieve_extensions = concatStringsSep " " (map (el: "+${el}") cfg.sieve.extensions);
          sieve_global_extensions = concatStringsSep " " (map (el: "+${el}") cfg.sieve.globalExtensions);
          sieve_pipe_bin_dir = sievePipeBinScriptDirectory;
        }
        // sieveScriptSettings
        // imapSieveMailboxSettings
      );
    };

    users.users = {
      dovenull = {
        uid = config.ids.uids.dovenull2;
        description = "Dovecot user for untrusted logins";
        group = "dovenull";
      };
    }
    // optionalAttrs (cfg.user == "dovecot2") {
      dovecot2 = {
        uid = config.ids.uids.dovecot2;
        description = "Dovecot user";
        group = cfg.group;
      };
    }
    // optionalAttrs (cfg.createMailUser && cfg.mailUser != null) {
      ${cfg.mailUser} = {
        description = "Virtual Mail User";
        isSystemUser = true;
      }
      // optionalAttrs (cfg.mailGroup != null) { group = cfg.mailGroup; };
    };

    users.groups = {
      dovenull.gid = config.ids.gids.dovenull2;
    }
    // optionalAttrs (cfg.group == "dovecot2") {
      dovecot2.gid = config.ids.gids.dovecot2;
    }
    // optionalAttrs (cfg.createMailUser && cfg.mailGroup != null) {
      ${cfg.mailGroup} = { };
    };

    environment.etc."dovecot/dovecot.conf".source = cfg.configFile;

    systemd.services.dovecot = {
      aliases = [ "dovecot2.service" ];
      description = "Dovecot IMAP/POP3 server";
      documentation = [
        "man:dovecot(1)"
        "https://doc.dovecot.org"
      ];

      after = [ "network.target" ];
      wantedBy = [ "multi-user.target" ];
      restartTriggers = [ cfg.configFile ];

      startLimitIntervalSec = 60; # 1 min
      serviceConfig = {
        Type = "notify";
        ExecStart = "${dovecotPkg}/sbin/dovecot -F";
        ExecReload = "${dovecotPkg}/sbin/doveadm reload";

        CapabilityBoundingSet = [
          "CAP_CHOWN"
          "CAP_DAC_OVERRIDE"
          "CAP_FOWNER"
          "CAP_KILL" # Required for child process management
          "CAP_NET_BIND_SERVICE"
          "CAP_SETGID"
          "CAP_SETUID"
          "CAP_SYS_CHROOT"
          "CAP_SYS_RESOURCE"
        ];
        LockPersonality = true;
        MemoryDenyWriteExecute = true;
        NoNewPrivileges = false; # e.g for sendmail
        OOMPolicy = "continue";
        PrivateTmp = true;
        ProcSubset = "pid";
        ProtectClock = true;
        ProtectControlGroups = true;
        ProtectHome = lib.mkDefault false;
        ProtectHostname = true;
        ProtectKernelLogs = true;
        ProtectKernelModules = true;
        ProtectKernelTunables = true;
        ProtectProc = "invisible";
        ProtectSystem = "full";
        PrivateDevices = true;
        Restart = "on-failure";
        RestartSec = "1s";
        RestrictAddressFamilies = [
          "AF_INET"
          "AF_INET6"
          "AF_NETLINK" # e.g. getifaddrs in sieve handling
          "AF_UNIX"
        ];
        RestrictNamespaces = true;
        RestrictRealtime = true;
        RestrictSUIDSGID = false; # sets sgid on maildirs
        RuntimeDirectory = [ "dovecot2" ];
        SystemCallArchitectures = "native";
        SystemCallFilter = [
          "@system-service @resources"
          "~@privileged"
          "@chown @setuid capset chroot"
        ];
      };

      # When copying sieve scripts preserve the original time stamp
      # (should be 0) so that the compiled sieve script is newer than
      # the source file and Dovecot won't try to compile it.
      preStart = ''
        rm -rf ${stateDir}/sieve ${stateDir}/imapsieve
      ''
      + optionalString (cfg.sieve.scripts != { }) ''
        mkdir -p ${stateDir}/sieve
        ${concatStringsSep "\n" (
          mapAttrsToList (to: from: ''
            if [ -d '${from}' ]; then
              mkdir '${stateDir}/sieve/${to}'
              cp -p "${from}/"*.sieve '${stateDir}/sieve/${to}'
            else
              cp -p '${from}' '${stateDir}/sieve/${to}'
            fi
            ${pkgs.dovecot_pigeonhole}/bin/sievec '${stateDir}/sieve/${to}'
          '') cfg.sieve.scripts
        )}
        chown -R '${cfg.mailUser}:${cfg.mailGroup}' '${stateDir}/sieve'
      ''
      + optionalString (cfg.imapsieve.mailbox != [ ]) ''
        mkdir -p ${stateDir}/imapsieve/{before,after}

        ${concatMapStringsSep "\n" (
          el:
          optionalString (el.before != null) ''
            cp -p ${el.before} ${stateDir}/imapsieve/before/${baseNameOf el.before}
            ${pkgs.dovecot_pigeonhole}/bin/sievec '${stateDir}/imapsieve/before/${baseNameOf el.before}'
          ''
          + optionalString (el.after != null) ''
            cp -p ${el.after} ${stateDir}/imapsieve/after/${baseNameOf el.after}
            ${pkgs.dovecot_pigeonhole}/bin/sievec '${stateDir}/imapsieve/after/${baseNameOf el.after}'
          ''
        ) cfg.imapsieve.mailbox}

        ${optionalString (
          cfg.mailUser != null && cfg.mailGroup != null
        ) "chown -R '${cfg.mailUser}:${cfg.mailGroup}' '${stateDir}/imapsieve'"}
      '';
    };

    environment.systemPackages = [ dovecotPkg ];

    warnings = warnAboutExtraConfigCollisions;

    assertions = [
      {
        assertion =
          (cfg.sslServerCert == null) == (cfg.sslServerKey == null)
          && (cfg.sslCACert != null -> !(cfg.sslServerCert == null || cfg.sslServerKey == null));
        message = "dovecot needs both sslServerCert and sslServerKey defined for working crypto";
      }
      {
        assertion = cfg.showPAMFailure -> cfg.enablePAM;
        message = "dovecot is configured with showPAMFailure while enablePAM is disabled";
      }
      {
        assertion = cfg.sieve.scripts != { } -> (cfg.mailUser != null && cfg.mailGroup != null);
        message = "dovecot requires mailUser and mailGroup to be set when `sieve.scripts` is set";
      }
      {
        assertion = config.systemd.services ? dovecot2 == false;
        message = ''
          Your configuration sets options on the `dovecot2` systemd service. These have no effect until they're migrated to the `dovecot` service.
        '';
      }
    ];

  };

  meta.maintainers = [ lib.maintainers.dblsaiko ];
}
