diff --git a/README.md b/README.md
index 05e854d..e29e33e 100644
--- a/README.md
+++ b/README.md
@@ -174,6 +174,7 @@ Also, the stack fits together nicely thanks to [contracts](#contracts).
- Nextcloud
- Audiobookshelf
- Deluge + *arr stack
+- Simple NixOS Mailserver
- Firefly-iii
- Forgejo
- Grocy
diff --git a/docs/redirects.json b/docs/redirects.json
index f863386..a4f9341 100644
--- a/docs/redirects.json
+++ b/docs/redirects.json
@@ -2720,6 +2720,9 @@
"services-category-documents": [
"services.html#services-category-documents"
],
+ "services-category-emails": [
+ "services.html#services-category-emails"
+ ],
"services-category-finance": [
"services.html#services-category-finance"
],
@@ -3944,6 +3947,294 @@
"services-karakeep-usage": [
"services-karakeep.html#services-karakeep-usage"
],
+ "services-mailserver": [
+ "services-mailserver.html#services-mailserver"
+ ],
+ "services-mailserver-certs": [
+ "services-mailserver.html#services-mailserver-certs"
+ ],
+ "services-mailserver-debug": [
+ "services-mailserver.html#services-mailserver-debug"
+ ],
+ "services-mailserver-debug-auth": [
+ "services-mailserver.html#services-mailserver-debug-auth"
+ ],
+ "services-mailserver-debug-folder-mapping": [
+ "services-mailserver.html#services-mailserver-debug-folder-mapping"
+ ],
+ "services-mailserver-debug-folders": [
+ "services-mailserver.html#services-mailserver-debug-folders"
+ ],
+ "services-mailserver-debug-local-folders": [
+ "services-mailserver.html#services-mailserver-debug-local-folders"
+ ],
+ "services-mailserver-debug-ports": [
+ "services-mailserver.html#services-mailserver-debug-ports"
+ ],
+ "services-mailserver-debug-systemd": [
+ "services-mailserver.html#services-mailserver-debug-systemd"
+ ],
+ "services-mailserver-declarative-ldap": [
+ "services-mailserver.html#services-mailserver-declarative-ldap"
+ ],
+ "services-mailserver-impermanence": [
+ "services-mailserver.html#services-mailserver-impermanence"
+ ],
+ "services-mailserver-mobile": [
+ "services-mailserver.html#services-mailserver-mobile"
+ ],
+ "services-mailserver-options": [
+ "services-mailserver.html#services-mailserver-options"
+ ],
+ "services-mailserver-options-shb.mailserver.adminPassword": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.adminPassword"
+ ],
+ "services-mailserver-options-shb.mailserver.adminPassword.request": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.adminPassword.request"
+ ],
+ "services-mailserver-options-shb.mailserver.adminPassword.request.group": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.adminPassword.request.group"
+ ],
+ "services-mailserver-options-shb.mailserver.adminPassword.request.mode": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.adminPassword.request.mode"
+ ],
+ "services-mailserver-options-shb.mailserver.adminPassword.request.owner": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.adminPassword.request.owner"
+ ],
+ "services-mailserver-options-shb.mailserver.adminPassword.request.restartUnits": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.adminPassword.request.restartUnits"
+ ],
+ "services-mailserver-options-shb.mailserver.adminPassword.result": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.adminPassword.result"
+ ],
+ "services-mailserver-options-shb.mailserver.adminPassword.result.path": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.adminPassword.result.path"
+ ],
+ "services-mailserver-options-shb.mailserver.adminUsername": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.adminUsername"
+ ],
+ "services-mailserver-options-shb.mailserver.backup": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.backup"
+ ],
+ "services-mailserver-options-shb.mailserver.backup.request": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.backup.request"
+ ],
+ "services-mailserver-options-shb.mailserver.backup.request.excludePatterns": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.backup.request.excludePatterns"
+ ],
+ "services-mailserver-options-shb.mailserver.backup.request.hooks": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.backup.request.hooks"
+ ],
+ "services-mailserver-options-shb.mailserver.backup.request.hooks.afterBackup": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.backup.request.hooks.afterBackup"
+ ],
+ "services-mailserver-options-shb.mailserver.backup.request.hooks.beforeBackup": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.backup.request.hooks.beforeBackup"
+ ],
+ "services-mailserver-options-shb.mailserver.backup.request.sourceDirectories": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.backup.request.sourceDirectories"
+ ],
+ "services-mailserver-options-shb.mailserver.backup.request.user": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.backup.request.user"
+ ],
+ "services-mailserver-options-shb.mailserver.backup.result": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.backup.result"
+ ],
+ "services-mailserver-options-shb.mailserver.backup.result.backupService": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.backup.result.backupService"
+ ],
+ "services-mailserver-options-shb.mailserver.backup.result.restoreScript": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.backup.result.restoreScript"
+ ],
+ "services-mailserver-options-shb.mailserver.domain": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.domain"
+ ],
+ "services-mailserver-options-shb.mailserver.enable": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.enable"
+ ],
+ "services-mailserver-options-shb.mailserver.imapSync": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync"
+ ],
+ "services-mailserver-options-shb.mailserver.imapSync.accounts": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts"
+ ],
+ "services-mailserver-options-shb.mailserver.imapSync.accounts._name_.host": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts._name_.host"
+ ],
+ "services-mailserver-options-shb.mailserver.imapSync.accounts._name_.mapSpecialDrafts": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts._name_.mapSpecialDrafts"
+ ],
+ "services-mailserver-options-shb.mailserver.imapSync.accounts._name_.mapSpecialJunk": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts._name_.mapSpecialJunk"
+ ],
+ "services-mailserver-options-shb.mailserver.imapSync.accounts._name_.mapSpecialSent": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts._name_.mapSpecialSent"
+ ],
+ "services-mailserver-options-shb.mailserver.imapSync.accounts._name_.mapSpecialTrash": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts._name_.mapSpecialTrash"
+ ],
+ "services-mailserver-options-shb.mailserver.imapSync.accounts._name_.password": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts._name_.password"
+ ],
+ "services-mailserver-options-shb.mailserver.imapSync.accounts._name_.password.request": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts._name_.password.request"
+ ],
+ "services-mailserver-options-shb.mailserver.imapSync.accounts._name_.password.request.group": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts._name_.password.request.group"
+ ],
+ "services-mailserver-options-shb.mailserver.imapSync.accounts._name_.password.request.mode": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts._name_.password.request.mode"
+ ],
+ "services-mailserver-options-shb.mailserver.imapSync.accounts._name_.password.request.owner": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts._name_.password.request.owner"
+ ],
+ "services-mailserver-options-shb.mailserver.imapSync.accounts._name_.password.request.restartUnits": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts._name_.password.request.restartUnits"
+ ],
+ "services-mailserver-options-shb.mailserver.imapSync.accounts._name_.password.result": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts._name_.password.result"
+ ],
+ "services-mailserver-options-shb.mailserver.imapSync.accounts._name_.password.result.path": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts._name_.password.result.path"
+ ],
+ "services-mailserver-options-shb.mailserver.imapSync.accounts._name_.port": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts._name_.port"
+ ],
+ "services-mailserver-options-shb.mailserver.imapSync.accounts._name_.sslType": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts._name_.sslType"
+ ],
+ "services-mailserver-options-shb.mailserver.imapSync.accounts._name_.timeout": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts._name_.timeout"
+ ],
+ "services-mailserver-options-shb.mailserver.imapSync.accounts._name_.username": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts._name_.username"
+ ],
+ "services-mailserver-options-shb.mailserver.imapSync.debug": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.debug"
+ ],
+ "services-mailserver-options-shb.mailserver.imapSync.syncTimer": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.syncTimer"
+ ],
+ "services-mailserver-options-shb.mailserver.impermanence": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.impermanence"
+ ],
+ "services-mailserver-options-shb.mailserver.ldap": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.ldap"
+ ],
+ "services-mailserver-options-shb.mailserver.ldap.account": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.ldap.account"
+ ],
+ "services-mailserver-options-shb.mailserver.ldap.adminName": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.ldap.adminName"
+ ],
+ "services-mailserver-options-shb.mailserver.ldap.adminPassword": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.ldap.adminPassword"
+ ],
+ "services-mailserver-options-shb.mailserver.ldap.adminPassword.request": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.ldap.adminPassword.request"
+ ],
+ "services-mailserver-options-shb.mailserver.ldap.adminPassword.request.group": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.ldap.adminPassword.request.group"
+ ],
+ "services-mailserver-options-shb.mailserver.ldap.adminPassword.request.mode": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.ldap.adminPassword.request.mode"
+ ],
+ "services-mailserver-options-shb.mailserver.ldap.adminPassword.request.owner": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.ldap.adminPassword.request.owner"
+ ],
+ "services-mailserver-options-shb.mailserver.ldap.adminPassword.request.restartUnits": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.ldap.adminPassword.request.restartUnits"
+ ],
+ "services-mailserver-options-shb.mailserver.ldap.adminPassword.result": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.ldap.adminPassword.result"
+ ],
+ "services-mailserver-options-shb.mailserver.ldap.adminPassword.result.path": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.ldap.adminPassword.result.path"
+ ],
+ "services-mailserver-options-shb.mailserver.ldap.dcdomain": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.ldap.dcdomain"
+ ],
+ "services-mailserver-options-shb.mailserver.ldap.enable": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.ldap.enable"
+ ],
+ "services-mailserver-options-shb.mailserver.ldap.host": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.ldap.host"
+ ],
+ "services-mailserver-options-shb.mailserver.ldap.port": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.ldap.port"
+ ],
+ "services-mailserver-options-shb.mailserver.ldap.userGroup": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.ldap.userGroup"
+ ],
+ "services-mailserver-options-shb.mailserver.smtpRelay": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.smtpRelay"
+ ],
+ "services-mailserver-options-shb.mailserver.smtpRelay.host": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.smtpRelay.host"
+ ],
+ "services-mailserver-options-shb.mailserver.smtpRelay.password": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.smtpRelay.password"
+ ],
+ "services-mailserver-options-shb.mailserver.smtpRelay.password.request": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.smtpRelay.password.request"
+ ],
+ "services-mailserver-options-shb.mailserver.smtpRelay.password.request.group": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.smtpRelay.password.request.group"
+ ],
+ "services-mailserver-options-shb.mailserver.smtpRelay.password.request.mode": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.smtpRelay.password.request.mode"
+ ],
+ "services-mailserver-options-shb.mailserver.smtpRelay.password.request.owner": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.smtpRelay.password.request.owner"
+ ],
+ "services-mailserver-options-shb.mailserver.smtpRelay.password.request.restartUnits": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.smtpRelay.password.request.restartUnits"
+ ],
+ "services-mailserver-options-shb.mailserver.smtpRelay.password.result": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.smtpRelay.password.result"
+ ],
+ "services-mailserver-options-shb.mailserver.smtpRelay.password.result.path": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.smtpRelay.password.result.path"
+ ],
+ "services-mailserver-options-shb.mailserver.smtpRelay.port": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.smtpRelay.port"
+ ],
+ "services-mailserver-options-shb.mailserver.smtpRelay.username": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.smtpRelay.username"
+ ],
+ "services-mailserver-options-shb.mailserver.ssl": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.ssl"
+ ],
+ "services-mailserver-options-shb.mailserver.ssl.paths": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.ssl.paths"
+ ],
+ "services-mailserver-options-shb.mailserver.ssl.paths.cert": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.ssl.paths.cert"
+ ],
+ "services-mailserver-options-shb.mailserver.ssl.paths.key": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.ssl.paths.key"
+ ],
+ "services-mailserver-options-shb.mailserver.ssl.systemdService": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.ssl.systemdService"
+ ],
+ "services-mailserver-options-shb.mailserver.subdomain": [
+ "services-mailserver.html#services-mailserver-options-shb.mailserver.subdomain"
+ ],
+ "services-mailserver-usage": [
+ "services-mailserver.html#services-mailserver-usage"
+ ],
+ "services-mailserver-usage-backup": [
+ "services-mailserver.html#services-mailserver-usage-backup"
+ ],
+ "services-mailserver-usage-disk-layout": [
+ "services-mailserver.html#services-mailserver-usage-disk-layout"
+ ],
+ "services-mailserver-usage-ldap": [
+ "services-mailserver.html#services-mailserver-usage-ldap"
+ ],
+ "services-mailserver-usage-secrets": [
+ "services-mailserver.html#services-mailserver-usage-secrets"
+ ],
"services-monitoring-features": [
"blocks-monitoring.html#services-monitoring-features"
],
diff --git a/docs/services.md b/docs/services.md
index 582b68e..c4e6481 100644
--- a/docs/services.md
+++ b/docs/services.md
@@ -13,18 +13,19 @@ Not all services are yet documented. You can find all available services [in the
The following table summarizes for each documented service what features it provides. More
information is provided in the respective manual sections.
-| Service | Backup | Reverse Proxy | SSO | LDAP | Monitoring | Profiling |
-|----------------------|--------|---------------|-----|-------|------------|-----------|
-| [*Arr][] | Y (1) | Y | Y | Y (4) | Y (2) | N |
-| [Firefly-iii][] | Y (1) | Y | Y | Y | Y (2) | N |
-| [Forgejo][] | Y (1) | Y | Y | Y | Y (2) | N |
-| [Home-Assistant][] | Y (1) | Y | N | Y | Y (2) | N |
-| [Jellyfin][] | Y (1) | Y | Y | Y | Y (2) | N |
-| [Karakeep][] | Y (1) | Y | Y | Y | Y (2) | N |
-| [Nextcloud Server][] | Y (1) | Y | Y | Y | Y (2) | P (3) |
-| [Open WebUI][] | Y (1) | Y | Y | Y | Y (2) | N |
-| [Pinchflat][] | Y | Y | Y | Y (4) | Y (5) | N |
-| [Vaultwarden][] | Y (1) | Y | Y | Y | Y (2) | N |
+| Service | Backup | Reverse Proxy | SSO | LDAP | Monitoring | Profiling |
+|-----------------------------|--------|---------------|-----|-------|------------|-----------|
+| [*Arr][] | Y (1) | Y | Y | Y (4) | Y (2) | N |
+| [Firefly-iii][] | Y (1) | Y | Y | Y | Y (2) | N |
+| [Forgejo][] | Y (1) | Y | Y | Y | Y (2) | N |
+| [Home-Assistant][] | Y (1) | Y | N | Y | Y (2) | N |
+| [Jellyfin][] | Y (1) | Y | Y | Y | Y (2) | N |
+| [Karakeep][] | Y (1) | Y | Y | Y | Y (2) | N |
+| [Nextcloud Server][] | Y (1) | Y | Y | Y | Y (2) | P (3) |
+| [Open WebUI][] | Y (1) | Y | Y | Y | Y (2) | N |
+| [Pinchflat][] | Y | Y | Y | Y (4) | Y (5) | N |
+| [Simple NixOS Mailserver][] | Y | Y | N | Y | Y | N |
+| [Vaultwarden][] | Y (1) | Y | Y | Y | Y (2) | N |
Legend: **N**: no but WIP; **P**: partial; **Y**: yes
@@ -44,6 +45,7 @@ Legend: **N**: no but WIP; **P**: partial; **Y**: yes
[Nextcloud Server]: services-nextcloud.html
[Open WebUI]: services-open-webui.html
[Pinchflat]: services-pinchflat.html
+[Simple NixOS Mailserver]: services-mailserver.html
[Vaultwarden]: services-vaultwarden.html
## Documents {#services-category-documents}
@@ -52,6 +54,12 @@ Legend: **N**: no but WIP; **P**: partial; **Y**: yes
modules/services/nextcloud-server/docs/default.md
```
+## Emails {#services-category-emails}
+
+```{=include=} chapters html:into-file=//services-mailserver.html
+modules/services/mailserver/docs/default.md
+```
+
## Passwords {#services-category-passwords}
```{=include=} chapters html:into-file=//services-vaultwarden.html
diff --git a/flake.nix b/flake.nix
index f075e47..9691486 100644
--- a/flake.nix
+++ b/flake.nix
@@ -112,6 +112,7 @@
"services/home-assistant" = ./modules/services/home-assistant.nix;
"services/jellyfin" = ./modules/services/jellyfin.nix;
"services/karakeep" = ./modules/services/karakeep.nix;
+ "services/mailserver" = ./modules/services/mailserver.nix;
"services/nextcloud-server" = {
module = ./modules/services/nextcloud-server.nix;
optionRoot = [
@@ -398,6 +399,7 @@
self.nixosModules.home-assistant
self.nixosModules.jellyfin
self.nixosModules.karakeep
+ self.nixosModules.mailserver
self.nixosModules.nextcloud-server
self.nixosModules.open-webui
self.nixosModules.pinchflat
@@ -435,6 +437,7 @@
nixosModules.home-assistant = modules/services/home-assistant.nix;
nixosModules.jellyfin = modules/services/jellyfin.nix;
nixosModules.karakeep = modules/services/karakeep.nix;
+ nixosModules.mailserver = modules/services/mailserver.nix;
nixosModules.nextcloud-server = modules/services/nextcloud-server.nix;
nixosModules.open-webui = modules/services/open-webui.nix;
nixosModules.paperless = modules/services/paperless.nix;
diff --git a/modules/services/mailserver.nix b/modules/services/mailserver.nix
new file mode 100644
index 0000000..828607b
--- /dev/null
+++ b/modules/services/mailserver.nix
@@ -0,0 +1,703 @@
+{
+ config,
+ lib,
+ shb,
+ pkgs,
+ ...
+}:
+let
+ cfg = config.shb.mailserver;
+in
+{
+ imports = [
+ (
+ builtins.fetchGit {
+ url = "https://gitlab.com/simple-nixos-mailserver/nixos-mailserver.git";
+ ref = "master";
+ rev = "5965fae920b6b97f39f94bdb6195631e274c93a5";
+ }
+ + "/default.nix"
+ )
+ ../blocks/lldap.nix
+ ];
+
+ options.shb.mailserver = {
+ enable = lib.mkEnableOption "SHB's nixos-mailserver module";
+
+ subdomain = lib.mkOption {
+ type = lib.types.str;
+ description = "Subdomain under which imap and smtp functions will be served.";
+ default = "imap";
+ };
+
+ domain = lib.mkOption {
+ type = lib.types.str;
+ description = "domain under which imap and smtp functions will be served.";
+ example = "mydomain.com";
+ };
+
+ ssl = lib.mkOption {
+ description = "Path to SSL files";
+ type = lib.types.nullOr shb.contracts.ssl.certs;
+ default = null;
+ };
+
+ adminUsername = lib.mkOption {
+ type = lib.types.nullOr lib.types.str;
+ default = null;
+ description = ''
+ Admin username.
+
+ postmaster will be made an alias of this user.
+ '';
+ example = "admin";
+ };
+
+ adminPassword = lib.mkOption {
+ description = "Admin user password.";
+ default = null;
+ type = lib.types.nullOr (
+ lib.types.submodule {
+ options = shb.contracts.secret.mkRequester {
+ mode = "0400";
+ owner = config.services.postfix.user;
+ ownerText = "services.postfix.user";
+ restartUnits = [ "dovecot.service" ];
+ };
+ }
+ );
+ };
+
+ imapSync = lib.mkOption {
+ description = ''
+ Synchronize one or more email providers through IMAP
+ to your dovecot2 instance.
+
+ This allows you to backup that email provider
+ and centralize your accounts in this dovecot2 instance.
+ '';
+ default = null;
+ type = lib.types.nullOr (
+ lib.types.submodule {
+ options = {
+ syncTimer = lib.mkOption {
+ type = lib.types.str;
+ default = "5m";
+ description = ''
+ Systemd timer for when imap sync job should happen.
+
+ This timer is not scheduling the job at regular intervals.
+ After a job finishes, the given amount of time is waited then the next job is started.
+
+ The default is set deliberatily slow to not spam you when setting up your mailserver.
+ When everything works, you will want to reduce it to 10s or something like that.
+ '';
+ example = "10s";
+ };
+
+ debug = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ description = "Enable verbose mbsync logging.";
+ };
+
+ accounts = lib.mkOption {
+ description = ''
+ Accounts to sync emails from using IMAP.
+
+ Emails will be stored under `''${config.mailserver.mailDirectory}/''${name}/''${username}`
+ '';
+ type = lib.types.attrsOf (
+ lib.types.submodule {
+ options = {
+ host = lib.mkOption {
+ type = lib.types.str;
+ description = "Hostname of the email's provider IMAP server.";
+ example = "imap.fastmail.com";
+ };
+
+ port = lib.mkOption {
+ type = lib.types.port;
+ description = "Port of the email's provider IMAP server.";
+ default = 993;
+ };
+
+ username = lib.mkOption {
+ type = lib.types.str;
+ description = "Username used to login to the email's provider IMAP server.";
+ example = "userA@fastmail.com";
+ };
+
+ password = lib.mkOption {
+ description = ''
+ Password used to login to the email's provider IMAP server.
+
+ The password could be an "app password" like for [Fastmail](https://www.fastmail.help/hc/en-us/articles/360058752854-App-passwords)
+ '';
+ type = lib.types.submodule {
+ options = shb.contracts.secret.mkRequester {
+ mode = "0400";
+ owner = config.mailserver.vmailUserName;
+ restartUnits = [ "mbsync.service" ];
+ };
+ };
+ };
+
+ sslType = lib.mkOption {
+ description = "Connection security method.";
+ type = lib.types.enum [
+ "IMAPS"
+ "STARTTLS"
+ ];
+ default = "IMAPS";
+ };
+
+ timeout = lib.mkOption {
+ description = "Connect and data timeout.";
+ type = lib.types.int;
+ default = 120;
+ };
+
+ mapSpecialDrafts = lib.mkOption {
+ type = lib.types.str;
+ default = "Drafts";
+ description = ''
+ Drafts special folder name on far side.
+
+ You only need to change this if mbsync logs the following error:
+
+ Error: ... far side box Drafts cannot be opened
+ '';
+ };
+ mapSpecialSent = lib.mkOption {
+ type = lib.types.str;
+ default = "Sent";
+ description = ''
+ Sent special folder name on far side.
+
+ You only need to change this if mbsync logs the following error:
+
+ Error: ... far side box Sent cannot be opened
+ '';
+ };
+ mapSpecialTrash = lib.mkOption {
+ type = lib.types.str;
+ default = "Trash";
+ description = ''
+ Trash special folder name on far side.
+
+ You only need to change this if mbsync logs the following error:
+
+ Error: ... far side box Trash cannot be opened
+ '';
+ };
+ mapSpecialJunk = lib.mkOption {
+ type = lib.types.str;
+ default = "Junk";
+ description = ''
+ Junk special folder name on far side.
+
+ You only need to change this if mbsync logs the following error:
+
+ Error: ... far side box Junk cannot be opened
+ '';
+ example = "Spam";
+ };
+ };
+ }
+ );
+ };
+ };
+ }
+ );
+ };
+
+ smtpRelay = lib.mkOption {
+ description = ''
+ Proxy outgoing emails through an email provider.
+
+ In short, this can help you avoid having your outgoing emails marked as spam.
+ See the manual for a lengthier explanation.
+ '';
+ default = null;
+ type = lib.types.nullOr (
+ lib.types.submodule {
+ options = {
+ host = lib.mkOption {
+ type = lib.types.str;
+ description = "Hostname of the email's provider SMTP server.";
+ example = "smtp.fastmail.com";
+ };
+
+ port = lib.mkOption {
+ type = lib.types.port;
+ description = "Port of the email's provider SMTP server.";
+ default = 587;
+ };
+
+ username = lib.mkOption {
+ description = "Username used to login to the email's provider SMTP server.";
+ type = lib.types.str;
+ };
+
+ password = lib.mkOption {
+ description = ''
+ Password used to login to the email's provider IMAP server.
+
+ The password could be an "app password" like for [Fastmail](https://www.fastmail.help/hc/en-us/articles/360058752854-App-passwords)
+ '';
+ type = lib.types.submodule {
+ options = shb.contracts.secret.mkRequester {
+ mode = "0400";
+ owner = config.services.postfix.user;
+ ownerText = "services.postfix.user";
+ restartUnits = [ "postfix.service" ];
+ };
+ };
+ };
+ };
+ }
+ );
+ };
+
+ ldap = lib.mkOption {
+ description = ''
+ LDAP Integration.
+
+ Enabling this app will create a new LDAP configuration or update one that exists with
+ the given host.
+ '';
+ default = { };
+ type = lib.types.nullOr (
+ lib.types.submodule {
+ options = {
+ enable = lib.mkEnableOption "LDAP app.";
+
+ host = lib.mkOption {
+ type = lib.types.str;
+ description = ''
+ Host serving the LDAP server.
+ '';
+ default = "127.0.0.1";
+ };
+
+ port = lib.mkOption {
+ type = lib.types.port;
+ description = ''
+ Port of the service serving the LDAP server.
+ '';
+ default = 389;
+ };
+
+ dcdomain = lib.mkOption {
+ type = lib.types.str;
+ description = "dc domain for ldap.";
+ example = "dc=mydomain,dc=com";
+ };
+
+ account = lib.mkOption {
+ type = lib.types.str;
+ description = ''
+ Select one account from those defined in `shb.mailserver.imapSync.accounts`
+ to login with.
+
+ Using LDAP, you can only connect to one account.
+ This limitation could maybe be lifted, feel free to post an issue if you need this.
+ '';
+ };
+
+ adminName = lib.mkOption {
+ type = lib.types.str;
+ description = "Admin user of the LDAP server.";
+ default = "admin";
+ };
+
+ adminPassword = lib.mkOption {
+ description = "LDAP server admin password.";
+ type = lib.types.submodule {
+ options = shb.contracts.secret.mkRequester {
+ mode = "0400";
+ owner = "nextcloud";
+ restartUnits = [ "dovecot.service" ];
+ };
+ };
+ };
+
+ userGroup = lib.mkOption {
+ type = lib.types.str;
+ description = "Group users must belong to to be able to use mails.";
+ default = "mail_user";
+ };
+ };
+ }
+ );
+ };
+
+ backup = lib.mkOption {
+ description = ''
+ Backup emails.
+ '';
+ default = { };
+ type = lib.types.submodule {
+ options = shb.contracts.backup.mkRequester {
+ user = config.mailserver.vmailUserName;
+ sourceDirectories = builtins.filter (x: x != null) [
+ config.mailserver.indexDir
+ config.mailserver.mailDirectory
+ config.mailserver.sieveDirectory
+ config.mailserver.dkimKeyDirectory
+ ];
+ sourceDirectoriesText = ''
+ [
+ config.mailserver.indexDir
+ config.mailserver.mailDirectory
+ config.mailserver.sieveDirectory
+ config.mailserver.dkimKeyDirectory
+ ]
+ '';
+ };
+ };
+ };
+
+ impermanence = lib.mkOption {
+ description = ''
+ Path to save when using impermanence setup.
+ '';
+ type = lib.types.attrsOf lib.types.str;
+ default = {
+ index = config.mailserver.indexDir;
+ mail = config.mailserver.mailDirectory;
+ sieve = config.mailserver.sieveDirectory;
+ dkim = config.mailserver.dkimKeyDirectory;
+ };
+ defaultText = lib.literalExpression ''
+ {
+ index = config.mailserver.indexDir;
+ mail = config.mailserver.mailDirectory;
+ sieve = config.mailserver.sieveDirectory;
+ dkim = config.mailserver.dkimKeyDirectory;
+ }
+ '';
+ };
+ };
+
+ config = lib.mkMerge [
+ (lib.mkIf cfg.enable {
+ mailserver = {
+ enable = true;
+ stateVersion = 3;
+ fqdn = "${cfg.subdomain}.${cfg.domain}";
+ domains = [ cfg.domain ];
+
+ localDnsResolver = false;
+
+ certificateScheme = "acme-nginx";
+ enableImapSsl = true;
+ enableSubmissionSsl = true;
+
+ # Using / is needed for iOS mail.
+ # Both following options are used to organize subfolders in subdirectories.
+ hierarchySeparator = "/";
+ useFsLayout = true;
+ };
+
+ services.postfix.config = {
+ smtpd_tls_security_level = lib.mkForce "encrypt";
+ };
+
+ # Is probably needed for iOS mail.
+ services.dovecot2.extraConfig = ''
+ ssl_min_protocol = TLSv1.2
+ ssl_cipher_list = HIGH:!aNULL:!MD5
+ '';
+
+ services.nginx = {
+ enable = true;
+
+ virtualHosts."${cfg.domain}" =
+ let
+ announce = pkgs.writeTextDir "config-v1.1.xml" ''
+
+
+
+ ${cfg.domain}
+ ${cfg.domain} Mailserver
+
+
+
+ ${cfg.subdomain}.${cfg.domain}
+ 993
+ SSL
+ password-cleartext
+ %EMAILADDRESS%
+
+
+
+
+ ${cfg.subdomain}.${cfg.domain}
+ 465
+ STARTTLS
+ password-cleartext
+ %EMAILADDRESS%
+
+
+
+
+ '';
+ in
+ {
+ forceSSL = true; # Redirect HTTP → HTTPS
+ root = "/var/www"; # Dummy root
+ locations."/.well-known/autoconfig/mail/" = {
+ alias = "${announce}/";
+ extraConfig = ''
+ default_type application/xml;
+ '';
+ };
+ };
+ };
+ })
+ (lib.mkIf (cfg.enable && cfg.adminUsername != null) {
+ assertions = [
+ {
+ assertion = cfg.adminPassword != null;
+ message = "`shb.mailserver.adminPassword` must be not null if `shb.mailserver.adminUsername` is not null.";
+ }
+ ];
+
+ mailserver = {
+ # To create the password hashes, use:
+ # nix run nixpkgs#mkpasswd -- --run 'mkpasswd -s'
+ loginAccounts = {
+ "${cfg.adminUsername}@${cfg.domain}" = {
+ hashedPasswordFile = cfg.adminPassword.result.path;
+ aliases = [ "postmaster@${cfg.domain}" ];
+ };
+ };
+ };
+ })
+ (lib.mkIf (cfg.enable && cfg.ldap != null) {
+ assertions = [
+ {
+ assertion = cfg.adminUsername == null;
+ message = "`shb.mailserver.adminUsername` must be null `shb.mailserver.ldap` integration is set.";
+ }
+ ];
+
+ shb.lldap.ensureGroups = {
+ ${cfg.ldap.userGroup} = { };
+ };
+
+ mailserver = {
+ ldap = {
+ enable = true;
+ uris = [
+ "ldap://${cfg.ldap.host}:${toString cfg.ldap.port}"
+ ];
+ searchBase = "ou=people,${cfg.ldap.dcdomain}";
+ searchScope = "sub";
+ bind = {
+ dn = "uid=${cfg.ldap.adminName},ou=people,${cfg.ldap.dcdomain}";
+ passwordFile = cfg.ldap.adminPassword.result.path;
+ };
+ # Note that nixos simple mailserver sets auth_bind=yes
+ # which means authentication binds are used.
+ # https://doc.dovecot.org/2.3/configuration_manual/authentication/ldap_bind/#authentication-ldap-bind
+ dovecot =
+ let
+ filter = "(&(objectClass=inetOrgPerson)(mail=%{user})(memberOf=cn=${cfg.ldap.userGroup},ou=groups,${cfg.ldap.dcdomain}))";
+ in
+ {
+ passAttrs = "user=user";
+ passFilter = filter;
+ userAttrs = lib.concatStringsSep "," [
+ "=home=${config.mailserver.mailDirectory}/${cfg.ldap.account}/%u"
+ # "mail=maildir:${config.mailserver.mailDirectory}/${cfg.ldap.account}/%u/mail"
+ "uid=${config.mailserver.vmailUserName}"
+ "gid=${config.mailserver.vmailGroupName}"
+ ];
+ userFilter = filter;
+ };
+ postfix = {
+ filter = "(&(objectClass=inetOrgPerson)(mail=%s)(memberOf=cn=${cfg.ldap.userGroup},ou=groups,${cfg.ldap.dcdomain}))";
+ mailAttribute = "mail";
+ uidAttribute = "mail";
+ };
+ };
+ };
+ })
+ (lib.mkIf (cfg.enable && cfg.imapSync != null) {
+ systemd.services.mbsync =
+ let
+ configFile =
+ let
+ mkAccount = name: acct: ''
+ # ${name} account
+
+ IMAPAccount ${name}
+ Host ${acct.host}
+ Port ${toString acct.port}
+ User ${acct.username}
+ PassCmd "cat ${acct.password.result.path}"
+ TLSType ${acct.sslType}
+ AuthMechs LOGIN
+ Timeout ${toString acct.timeout}
+
+ IMAPStore ${name}-remote
+ Account ${name}
+
+ MaildirStore ${name}-local
+ INBOX ${config.mailserver.mailDirectory}/${name}/${acct.username}/mail/
+ # Maps subfolders on far side to actual subfolders on disk.
+ # The other option is Maildir++ but then the mailserver.hierarchySeparator must be set to a dot '.'
+ SubFolders Verbatim
+ Path ${config.mailserver.mailDirectory}/${name}/${acct.username}/mail/
+
+ Channel ${name}-main
+ Far :${name}-remote:
+ Near :${name}-local:
+ Patterns * !Drafts !Sent !Trash !Junk !${acct.mapSpecialDrafts} !${acct.mapSpecialSent} !${acct.mapSpecialTrash} !${acct.mapSpecialJunk}
+ Create Both
+ Expunge Both
+ SyncState *
+ Sync All
+ CopyArrivalDate yes # Preserve date from incoming message.
+
+ Channel ${name}-drafts
+ Far :${name}-remote:"${acct.mapSpecialDrafts}"
+ Near :${name}-local:"Drafts"
+ Create Both
+ Expunge Both
+ SyncState *
+ Sync All
+ CopyArrivalDate yes # Preserve date from incoming message.
+
+ Channel ${name}-sent
+ Far :${name}-remote:"${acct.mapSpecialSent}"
+ Near :${name}-local:"Sent"
+ Create Both
+ Expunge Both
+ SyncState *
+ Sync All
+ CopyArrivalDate yes # Preserve date from incoming message.
+
+ Channel ${name}-trash
+ Far :${name}-remote:"${acct.mapSpecialTrash}"
+ Near :${name}-local:"Trash"
+ Create Both
+ Expunge Both
+ SyncState *
+ Sync All
+ CopyArrivalDate yes # Preserve date from incoming message.
+
+ Channel ${name}-junk
+ Far :${name}-remote:"${acct.mapSpecialJunk}"
+ Near :${name}-local:"Junk"
+ Create Both
+ Expunge Both
+ SyncState *
+ Sync All
+ CopyArrivalDate yes # Preserve date from incoming message.
+
+ Group ${name}
+ Channel ${name}-main
+ Channel ${name}-drafts
+ Channel ${name}-sent
+ Channel ${name}-trash
+ Channel ${name}-junk
+
+ # END ${name} account
+ '';
+
+ in
+ pkgs.writeText "mbsync.conf" (
+ lib.concatStringsSep "\n" (lib.mapAttrsToList mkAccount cfg.imapSync.accounts)
+ );
+ in
+ {
+ description = "Sync mailbox";
+ serviceConfig = {
+ Type = "oneshot";
+ User = config.mailserver.vmailUserName;
+ };
+ script =
+ let
+ debug = if cfg.imapSync.debug then "-V" else "";
+ in
+ ''
+ ${pkgs.isync}/bin/mbsync --all ${debug} --config ${configFile}
+ '';
+ };
+
+ systemd.tmpfiles.rules =
+ let
+ mkAccount =
+ name: acct:
+ # The equal sign makes sure parent directories have the corret user and group too.
+ [
+ "d '${config.mailserver.mailDirectory}/${name}' 0750 ${config.mailserver.vmailUserName} ${config.mailserver.vmailGroupName} - -"
+ "d '${config.mailserver.mailDirectory}/${name}/${acct.username}' 0750 ${config.mailserver.vmailUserName} ${config.mailserver.vmailGroupName} - -"
+ ];
+ in
+ lib.flatten (lib.mapAttrsToList mkAccount cfg.imapSync.accounts);
+
+ systemd.timers.mbsync = {
+ wantedBy = [ "timers.target" ];
+ timerConfig = {
+ OnBootSec = cfg.imapSync.syncTimer;
+ OnUnitActiveSec = cfg.imapSync.syncTimer;
+ };
+ };
+ })
+ (lib.mkIf (cfg.enable && cfg.smtpRelay != null) (
+ let
+ url = "[${cfg.smtpRelay.host}]:${toString cfg.smtpRelay.port}";
+ in
+ {
+ assertions = [
+ {
+ assertion = lib.hasAttr cfg.adminPassword != null;
+ message = "`shb.mailserver.adminPassword` must be not null if `shb.mailserver.adminUsername` is not null.";
+ }
+ ];
+
+ # Inspiration from https://www.brull.me/postfix/debian/fastmail/2016/08/16/fastmail-smtp.html
+ services.postfix = {
+ settings.main = {
+ relayhost = [ url ];
+ smtp_sasl_auth_enable = "yes";
+ smtp_sasl_password_maps = "texthash:/run/secrets/postfix/postfix-smtp-relay-password";
+ smtp_sasl_security_options = "noanonymous";
+ smtp_use_tls = "yes";
+ };
+ };
+
+ systemd.services.postfix-pre = {
+ script = shb.replaceSecrets {
+ userConfig = {
+ inherit url;
+ inherit (cfg.smtpRelay) username;
+ password.source = cfg.smtpRelay.password.result.path;
+ };
+ generator =
+ name:
+ {
+ url,
+ username,
+ password,
+ }:
+ pkgs.writeText "postfix-smtp-relay-password" ''
+ ${url} ${username}:${password}
+ '';
+ resultPath = "/run/secrets/postfix/postfix-smtp-relay-password";
+ user = config.services.postfix.user;
+ };
+ serviceConfig.Type = "oneshot";
+ wantedBy = [ "multi-user.target" ];
+ before = [ "postfix.service" ];
+ requiredBy = [ "postfix.service" ];
+ };
+ }
+ ))
+ ];
+}
diff --git a/modules/services/mailserver/docs/default.md b/modules/services/mailserver/docs/default.md
new file mode 100644
index 0000000..ca6ec42
--- /dev/null
+++ b/modules/services/mailserver/docs/default.md
@@ -0,0 +1,380 @@
+# Mailserver Service {#services-mailserver}
+
+Defined in [`/modules/services/mailserver.nix`](@REPO@/modules/services/mailserver.nix).
+
+This NixOS module is a service that sets up
+the [NixOS Simple Mailserver](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver) project.
+It integrates the upstream project
+with the SHB modules like the SSL module, the contract for secrets and the LLDAP module.
+
+It also exposes an XML file which allows some email clients to auto configure themselves.
+
+Setting up a self-hosted email server in this age
+can be quite time consuming because you need to maintain
+a good IP hygiene to avoid being marked as spam from the big players.
+To avoid needing to deal with this,
+this module provides the means
+to use an email provider (like Fastmail or ProtonMail) as a mere proxy.
+If you also setup the email provider using your own custom domain,
+this combination allows you to change email provider
+without needing to change your clients or notify your email correspondents
+and keep a backup of all your emails at the same time.
+The setup looks like so:
+
+```
+Domain --[ DNS records ]-> Email Provider --[ mbsync ]-> SHB Server
+
+Internet <---------------- Email Provider <-[ postfix ]-- SHB Server
+```
+
+Configuring your domain name to point to your email provider is out of scope here.
+See the documentation for "custom domain" for you email provider,
+like for [Fastmail](https://www.fastmail.com/features/domains/)
+and [ProtonMail](https://proton.me/support/custom-domain)
+
+To use an email provider as a proxy, use the
+[shb.mailserver.imapSync](#services-mailserver-options-shb.mailserver.imapSync)
+and [shb.mailserver.smtpRelay](#services-mailserver-options-shb.mailserver.smtpRelay),
+options.
+
+## Usage {#services-mailserver-usage}
+
+The following snippet assumes a few blocks have been setup already:
+
+- the [secrets block](usage.html#usage-secrets) with SOPS,
+- the [`shb.ssl` block](blocks-ssl.html#usage),
+- the [`shb.lldap` block](blocks-lldap.html#blocks-lldap-global-setup).
+
+```nix
+let
+ domain = "example.com";
+ username = "me@example.com";
+in
+{
+ imports = [
+ selfhostblocks.nixosModules.mailserver
+ ];
+
+ shb.mailserver = {
+ enable = true;
+ inherit domain;
+ subdomain = "imap";
+ ssl = config.shb.certs.certs.letsencrypt."domain";
+
+ imapSync = {
+ syncTimer = "10s";
+ accounts.fastmail = {
+ host = "imap.fastmail.com";
+ port = 993;
+ inherit username;
+ password.result = config.shb.sops.secret."mailserver/imap/fastmail/password".result;
+ mapSpecialJunk = "Spam";
+ };
+ };
+
+ smtpRelay = {
+ host = "smtp.fastmail.com";
+ port = 587;
+ inherit username;
+ password.result = config.shb.sops.secret."mailserver/smtp/fastmail/password".result;
+ };
+
+ ldap = {
+ enable = true;
+ host = "127.0.0.1";
+ port = config.shb.lldap.ldapPort;
+ dcdomain = config.shb.lldap.dcdomain;
+ adminName = "admin";
+ adminPassword.result = config.shb.sops.secret."mailserver/ldap_admin_password".result;
+ account = "fastmail";
+ };
+ };
+
+ # Optionally add some mailboxes
+ mailserver.mailboxes = {
+ Drafts = {
+ auto = "subscribe";
+ specialUse = "Drafts";
+ };
+ Junk = {
+ auto = "subscribe";
+ specialUse = "Junk";
+ };
+ Sent = {
+ auto = "subscribe";
+ specialUse = "Sent";
+ };
+ Trash = {
+ auto = "subscribe";
+ specialUse = "Trash";
+ };
+ Archive = {
+ auto = "subscribe";
+ specialUse = "Archive";
+ };
+ };
+
+ shb.sops.secret."mailserver/smtp/fastmail/password".request =
+ config.shb.mailserver.smtpRelay.password.request;
+
+ shb.sops.secret."mailserver/imap/fastmail/password".request =
+ config.shb.mailserver.imapSync.accounts.fastmail.password.request;
+
+ shb.sops.secret."mailserver/ldap_admin_password" = {
+ request = config.shb.mailserver.ldap.adminPassword.request;
+ # This reuses the admin password set in the shb.lldap module.
+ settings.key = "lldap/user_password";
+ };
+}
+```
+
+### Secrets {#services-mailserver-usage-secrets}
+
+Secrets can be randomly generated with `nix run nixpkgs#openssl -- rand -hex 64`.
+
+### LDAP {#services-mailserver-usage-ldap}
+
+The [user](#services-mailserver-options-shb.mailserver.ldap.userGroup)
+LDAP group is created automatically.
+
+### Disk Layout {#services-mailserver-usage-disk-layout}
+
+The disk layout has been purposely set to use slashes `/` for subfolders.
+By experience, this works better with iOS mail.
+
+### Backup {#services-mailserver-usage-backup}
+
+Backing up your emails using the [Restic block](blocks-restic.html) is done like so:
+
+```nix
+shb.restic.instances."mailserver" = {
+ request = config.shb.mailserver.backup;
+ settings = {
+ enable = true;
+ };
+};
+```
+
+The name `"mailserver"` in the `instances` can be anything.
+The `config.shb.mailserver.backup` option provides what directories to backup.
+You can define any number of Restic instances to backup your emails multiple times.
+
+You will then need to configure more options like the `repository`,
+as explained in the [restic](blocks-restic.html) documentation.
+
+### Certificates {#services-mailserver-certs}
+
+For Let's Encrypt certificates, add:
+
+```nix
+let
+ domain = "example.com";
+in
+{
+ shb.certs.certs.letsencrypt.${domain}.extraDomains = [
+ "${config.shb.mailserver.subdomain}.${config.shb.mailserver.domain}"
+ ];
+}
+```
+
+### Impermanence {#services-mailserver-impermanence}
+
+To save the data folder in an impermanence setup, add:
+
+```nix
+{
+ shb.zfs.datasets."safe/mailserver/index".path = config.shb.mailserver.impermanence.index;
+ shb.zfs.datasets."safe/mailserver/mail".path = config.shb.mailserver.impermanence.mail;
+ shb.zfs.datasets."safe/mailserver/sieve".path = config.shb.mailserver.impermanence.sieve;
+ shb.zfs.datasets."safe/mailserver/dkim".path = config.shb.mailserver.impermanence.dkim;
+}
+```
+
+### Declarative LDAP {#services-mailserver-declarative-ldap}
+
+To add a user `USERNAME` to the user group, add:
+
+```nix
+shb.lldap.ensureUsers.USERNAME.groups = [
+ config.shb.mailserver.ldap.userGroup
+];
+```
+
+## Debug {#services-mailserver-debug}
+
+Debugging this will be certainly necessary.
+The first issue you will encounter will probably be with `mbsync`
+under the [shb.mailserver.imapSync](#services-mailserver-options-shb.mailserver.imapSync) option
+with the folder name mapping.
+
+### Systemd Services {#services-mailserver-debug-systemd}
+
+The 3 systemd services setup by this module are:
+
+- `mbsync.service`
+- `dovecot.service`
+- `postfix.service`
+
+### Folders {#services-mailserver-debug-folders}
+
+The 4 folders where state is stored are:
+
+- `config.mailserver.indexDir` = `/var/lib/dovecot/indices`
+- `config.mailserver.mailDirectory` = `/var/vmail`
+- `config.mailserver.sieveDirectory` = `/var/sieve`
+- `config.mailserver.dkimKeyDirectory` = `/var/dkim`
+
+### Open Ports {#services-mailserver-debug-ports}
+
+The ports opened by default in this module are:
+
+- Submissions: 465
+- Imap: 993
+
+You will need to forward those ports on your router
+if you want to access to your emails from the internet.
+
+The complete list can be found in the [upstream repository](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/blob/5965fae920b6b97f39f94bdb6195631e274c93a5/mail-server/networking.nix).
+
+### List Email Provider Folder Mapping {#services-mailserver-debug-folder-mapping}
+
+Replace `$USER` and `$PASSWORD` by those used to connect to your email provider.
+Yes, you will need to enter verbatim `a LOGIN ...` and `b LIST "" "*"`.
+
+```
+$ nix run nixpkgs#openssl -- s_client -connect imap.fastmail.com:993 -crlf -quiet
+a LOGIN $USER $password
+b LIST "" "*"
+```
+
+Example output will be:
+
+```
+* LIST (\HasNoChildren) "/" INBOX
+* LIST (\HasNoChildren \Drafts) "/" Drafts
+* LIST (\HasNoChildren \Sent) "/" Sent
+* LIST (\Noinferiors \HasNoChildren \Junk) "/" Spam
+
+...
+```
+
+Here you can see the special folder `\Junk` is actually named `Spam`.
+To handle this, set the `.mapSpecial*` options:
+
+```
+{
+ shb.mailserver.imapSync.accounts. = {
+ mapSpecialJunk = "Spam";
+ };
+}
+```
+
+### List Local Folders {#services-mailserver-debug-local-folders}
+
+Check the local folders to make sure the mapping is correct
+and all folders are correctly downloaded.
+For example, if the mapping above is wrong, you will see both a
+`Junk` and `Spam` folder while if it is correct,
+you will only see the `Junk` folder.
+
+```
+$ sudo doveadm mailbox list -u $USER
+Junk
+Trash
+Drafts
+Sent
+INBOX
+MyCustomFolder
+```
+
+The following command shows the number of messages in a folder:
+
+```
+$ sudo doveadm mailbox status -u $USER messages INBOX
+INBOX messages=13591
+```
+
+If any folder is not appearing or has 0 message but should have some,
+it could mean dovecot is not setup correctly and assumes an incorrect folder layout.
+If that is the case, check the user config with:
+
+```
+$ sudo doveadm user $USER
+field value
+uid 5000
+gid 5000
+home /var/vmail/fastmail/$USER
+mail maildir:~/mail:LAYOUT=fs
+virtualMail
+```
+
+### Test Auth {#services-mailserver-debug-auth}
+
+To test authentication to your dovecot instance, run:
+
+```
+$ nix run nixpkgs#openssl -- s_client -connect $SUBDOMAIN.$DOMAIN:993 -crlf -quiet
+. LOGIN $USER $PASSWORD
+```
+
+You must here also enter the second line verbatim,
+replacing your user and password with the real one.
+
+On success, you will see:
+
+```
+. OK [CAPABILITY IMAP4rev1 ...] Logged in
+```
+
+Otherwise, either if the password is wrong or,
+when using LDAP if the user is not part of the LDAP group, you will see:
+
+```
+. NO [AUTHENTICATIONFAILED] Authentication failed.
+```
+
+To test the postfix instance, run:
+
+```
+$ swaks \
+ --server $SUBDOMAIN.$DOMAIN \
+ --port 465 \
+ --tls-on-connect \
+ --auth LOGIN \
+ --auth-user $USER \
+ --auth-password '$PASSWORD' \
+ --from $USER \
+ --to $USER
+```
+
+Try once with a wrong password and once with a correct one.
+The former should log:
+
+```
+<~* 535 5.7.8 Error: authentication failed: (reason unavailable)
+```
+
+## Mobile Apps {#services-mailserver-mobile}
+
+This module was tested with:
+- the iOS mail mobile app,
+- Thunderbird on NixOS.
+
+The iOS mail app is pretty finicky.
+If downloading emails does not work,
+make sure the certificate used includes the whole chain:
+
+```bash
+$ openssl s_client -connect $SUBDOMAIN.$DOMAIN:993 -showcerts
+```
+
+Normally, the other options are setup correctly but if it fails for you,
+feel free to open an issue.
+
+## Options Reference {#services-mailserver-options}
+
+```{=include=} options
+id-prefix: services-mailserver-options-
+list-id: selfhostblocks-service-mailserver-options
+source: @OPTIONS_JSON@
+```