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@ +```