[server] hostname = "mail.main.unkin.net" greeting = "Stalwart ESMTP" # SMTP relay listener (receives from postfix for local delivery) [server.listener."smtp-relay"] bind = ["0.0.0.0:25"] protocol = "smtp" greeting = "Stalwart SMTP Relay" [server.listener."smtp-relay".proxy] trusted-networks = ["127.0.0.0/8", "::1", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"] # SMTP submission listener (user-facing, TLS required) [server.listener."submission"] bind = ["0.0.0.0:587"] protocol = "smtp" greeting = "Stalwart SMTP Submission" tls.require = true [server.listener."submission".proxy] trusted-networks = ["127.0.0.0/8", "::1"] # IMAP listener [server.listener."imap"] bind = ["0.0.0.0:143"] protocol = "imap" [server.listener."imap".proxy] trusted-networks = ["127.0.0.0/8", "::1"] # IMAPS listener (implicit TLS) [server.listener."imaps"] bind = ["0.0.0.0:993"] protocol = "imap" tls.implicit = true [server.listener."imaps".proxy] trusted-networks = ["127.0.0.0/8", "::1"] # HTTPS (web admin + JMAP) for external LoadBalancer [server.listener."https"] bind = ["0.0.0.0:443"] protocol = "http" tls.implicit = true [server.listener."https".proxy] trusted-networks = ["127.0.0.0/8", "::1", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"] # Plain HTTP for Traefik reverse proxy (TLS terminated at Traefik) [server.listener."http-internal"] bind = ["0.0.0.0:8080"] protocol = "http" [server.listener."http-internal".proxy] trusted-networks = ["127.0.0.0/8", "::1", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"] [server.tls] enable = true implicit = false certificate = "default" [server.http] use-x-forwarded = true permissive-cors = false [webadmin] path = "/var/lib/stalwart/webadmin" auto-update = true resource = "https://github.com/stalwartlabs/webadmin/releases/latest/download/webadmin.zip" # PostgreSQL store (via CNPG pooler) [store."postgresql"] type = "postgresql" host = "stalwart-postgres-pooler.stalwart.svc.cluster.local" port = 5432 database = "stalwart" user = "stalwart" password = "%{env:POSTGRES_PASSWORD}%" timeout = "15s" [store."postgresql".tls] enable = true allow-invalid-certs = false [store."postgresql".pool] max-connections = 10 [store."postgresql".purge] frequency = "0 3 *" # S3/Ceph-RGW store for blobs [store."s3"] type = "s3" bucket = "stalwart-maildata" region = "syd1" access-key = "%{env:S3_ACCESS_KEY}%" secret-key = "%{env:S3_SECRET_KEY}%" endpoint = "https://radosgw.service.consul" timeout = "30s" key-prefix = "stalwart/" compression = "lz4" [store."s3".purge] frequency = "30 5 *" # Storage assignment [storage] data = "postgresql" fts = "postgresql" blob = "s3" lookup = "postgresql" directory = "internal" in-memory = "postgresql" # Directory configuration [directory.internal] type = "internal" store = "postgresql" # Fallback admin (password hash from Vault) [authentication.fallback-admin] user = "admin" secret = "%{env:ADMIN_PASSWORD_HASH}%" [authentication] [authentication.directory] directories = ["internal"] [authorization] directory = "internal" # JMAP configuration [jmap] directory = "internal" [jmap.protocol] request-max-size = 10485760 get.max-objects = 500 query.max-results = 5000 changes.max-results = 5000 upload.max-size = 50000000 upload.ttl = "1h" # IMAP configuration [imap] directory = "internal" [imap.protocol] max-requests = 64 # Inbound rate limiting [[queue.limiter.inbound]] key = ["remote_ip"] rate = "500/1s" enable = true # Outbound routing through postfix relay [queue] path = "/var/lib/stalwart/queue" [queue.schedule] retry = ["2s", "5s", "1m", "5m", "15m", "30m", "1h", "2h"] notify = ["1d", "3d"] expire = "5d" [session.extensions] future-release = "7d" # Route local domain mail locally, everything else through postfix relay [queue.strategy] route = [ { if = "is_local_domain('', rcpt_domain)", then = "'local'" }, { else = "'relay'" } ] [queue.route."local"] type = "local" [queue.route."relay"] type = "relay" address = "postfix.mailgateway.svc.cluster.local" port = 25 protocol = "smtp" [queue.route."relay".tls] implicit = false allow-invalid-certs = false # Session configuration [session.ehlo] reject-non-fqdn = false [session.rcpt] type = "internal" store = "postgresql" max-recipients = 25 [session.data] max-messages = 10 max-message-size = 52428800 # Spam filtering offloaded to postfix/rspamd — disable internal spam filter [spam-filter] enable = false # TLS certificate (cert-manager issues to /etc/stalwart/tls/) [certificate."default"] cert = "%{file:/etc/stalwart/tls/tls.crt}%" private-key = "%{file:/etc/stalwart/tls/tls.key}%" default = true # Logging [tracer] type = "log" level = "info" ansi = false multiline = true # Metrics [metrics] prometheus.enable = true prometheus.port = 9090