apiVersion: v1 kind: ConfigMap metadata: name: stalwart-config namespace: stalwart data: # DOMAIN_SUFFIX is replaced by sed at deploy time. # Secrets (%{env:VAR}%) are injected via environment variables in the Deployment. # # Only LOCAL keys belong in this file (store.*, storage.*, server.*, directory.*, # tracer.*, cluster.*, certificate.*, authentication.fallback-admin.*). # Everything else (OIDC, DKIM, spam, etc.) is configured via the Stalwart # web admin UI and stored in the database. config.toml: | # Force http.url to be read from this file (not the database). config.local-keys.0000 = "store.*" config.local-keys.0001 = "directory.*" config.local-keys.0002 = "tracer.*" config.local-keys.0003 = "server.*" config.local-keys.0004 = "!server.blocked-ip.*" config.local-keys.0005 = "!server.allowed-ip.*" config.local-keys.0006 = "authentication.fallback-admin.*" config.local-keys.0007 = "cluster.*" config.local-keys.0008 = "config.local-keys.*" config.local-keys.0009 = "storage.*" config.local-keys.0010 = "certificate.*" config.local-keys.0011 = "http.url" config.local-keys.0012 = "http.use-x-forwarded" # Expression-quoted public URL (inner single quotes required). http.url = "'https://mail.DOMAIN_SUFFIX'" http.use-x-forwarded = true [server] hostname = "mail.DOMAIN_SUFFIX" max-connections = 1024 [server.listener."smtp"] bind = ["0.0.0.0:25"] protocol = "smtp" [server.listener."submission"] bind = ["0.0.0.0:587"] protocol = "smtp" tls.implicit = false [server.listener."smtps"] bind = ["0.0.0.0:465"] protocol = "smtp" tls.implicit = true [server.listener."imap"] bind = ["0.0.0.0:143"] protocol = "imap" tls.implicit = false [server.listener."imaps"] bind = ["0.0.0.0:993"] protocol = "imap" tls.implicit = true [server.listener."jmap"] bind = ["0.0.0.0:8080"] protocol = "http" [server.listener."managesieve"] bind = ["0.0.0.0:4190"] protocol = "managesieve" [certificate."default"] cert = "%{file:/etc/stalwart-tls/tls.crt}%" private-key = "%{file:/etc/stalwart-tls/tls.key}%" # ── Storage backends ───────────────────────────────────────────────────── [store."postgresql"] type = "postgresql" host = "postgres-rw.data.svc.cluster.local" port = 5432 database = "stalwart_db" user = "stalwart" password = "%{env:DB_PASSWORD}%" timeout = "15s" [store."postgresql".pool] max-connections = 20 [store."s3"] type = "s3" bucket = "sunbeam-stalwart" endpoint = "http://seaweedfs-filer.storage.svc.cluster.local:8333" region = "us-east-1" access-key = "%{env:S3_ACCESS_KEY}%" secret-key = "%{env:S3_SECRET_KEY}%" key-prefix = "v1/" timeout = "60s" [store."opensearch"] type = "elasticsearch" url = "http://opensearch.data.svc.cluster.local:9200" index = "stalwart" [store."redis"] type = "redis" urls = ["redis://valkey.data.svc.cluster.local:6379/7"] # ── Storage role assignments ───────────────────────────────────────────── [storage] data = "postgresql" blob = "postgresql" fts = "opensearch" lookup = "redis" directory = "hydra" # ── Directories (user stores) ────────────────────────────────────────── # Internal directory for locally-managed accounts (admin, service accounts). [directory."internal"] type = "internal" store = "postgresql" # OIDC directory — validates Hydra-issued bearer tokens via userinfo. # When a JMAP client presents a Bearer token, Stalwart calls Hydra's # userinfo endpoint to map it to a user identity. [directory."hydra"] type = "oidc" timeout = "15s" endpoint.url = "http://hydra-public.ory.svc.cluster.local:4444/userinfo" endpoint.method = "userinfo" fields.email = "email" fields.username = "email" fields.full-name = "name" # ── Authentication ─────────────────────────────────────────────────────── [authentication.fallback-admin] user = "admin" secret = "%{env:ADMIN_PASSWORD}%" # ── Logging ────────────────────────────────────────────────────────────── [tracer."stdout"] type = "stdout" level = "info" ansi = false enable = true