Configuration only applied on restart

Issue Description

When applying a config (e.g. new Domain or SystemSettings) via cli, the config only applies once the container is restarted. For example, auto-discovery doesn’t advertise mydomain.com but instead stalwart-0 (the hostname of my pod). The email domain doesn’t work on a fresh installation, it has to be restarted first before being able to login via thunderbird.

Expected Behavior

Configuration should be applied immediately (similar to 0.15 behavior).

Actual Behavior

Configuration is only applied on restart.

Reproduction Steps

Here’s my exact configuration (with domain changed) that’s applied:

{"@type":"upsert","object":"Tracer","value":{"otel":{"@type":"OtelHttp","enable":true,"enableLogExporter":false,"enableSpanExporter":true,"endpoint":"http://tempo.kube-system.svc.cluster.local:4318/v1/traces","httpAuth":{"@type":"Unauthenticated"},"level":"trace"},"stdout":{"@type":"Stdout","level":"info"}},"matchOn":["@type"]}
{"@type":"update","object":"Metrics","value":{"openTelemetry":{"@type":"Disabled"},"prometheus":{"@type":"Enabled","authSecret":{"@type":"None"}}}}
{"@type":"update","object":"DataStore","value":{"@type":"PostgreSql","authSecret":{"@type":"EnvironmentVariable","variableName":"POSTGRESQL_PASSWORD"},"authUsername":"stalwart","database":"stalwart","host":"postgresql-primary"}}
{"@type":"update","object":"SearchStore","value":{"@type":"PostgreSql","authSecret":{"@type":"EnvironmentVariable","variableName":"POSTGRESQL_PASSWORD"},"authUsername":"stalwart","database":"stalwart","host":"postgresql-primary"}}
{"@type":"update","object":"BlobStore","value":{"@type":"S3","accessKey":{"@type":"EnvironmentVariable","variableName":"BUCKET_ACCESS_KEY"},"bucket":"stalwart","region":{"@type":"Custom","customEndpoint":"http://minio","customRegion":"us-east-1"},"secretKey":{"@type":"EnvironmentVariable","variableName":"BUCKET_SECRET_KEY"},"securityToken":{"@type":"None"},"sessionToken":{"@type":"None"}}}
{"@type":"upsert","object":"Directory","value":{"authentik":{"@type":"Ldap","attrClass":{"objectClass":true},"attrDescription":{"sAMAccountName":true},"attrEmail":{"mail":true},"attrEmailAlias":{"mailAlias":true},"attrMemberOf":{"memberOf":true},"attrSecretChanged":{"pwdChangedTime":true},"baseDn":"dc=belfodil,dc=ca","bindDn":"cn=ldapservice,dc=belfodil,dc=ca","bindSecret":{"@type":"EnvironmentVariable","variableName":"LDAP_BIND_PASSWORD"},"description":"Authentik LDAP","filterLogin":"(|(mail=?)(mailAlias=?)(recoveryEmail=?))","filterMailbox":"(|(mail=?)(mailAlias=?)(recoveryEmail=?))","filterMemberOf":"(\u0026(objectClass=posixGroup)(cn=?))","url":"ldap://ak-outpost-ldap-outpost:389"}},"matchOn":["@type"]}
{"@type":"update","object":"Authentication","value":{"directoryId":"#authentik"}}
{"@type":"upsert","object":"Domain","value":{"default":{"name":"mydomain.com"},"dkimManagement":{"@type":"Manual"}},"matchOn":["name"]}
{"@type":"update","object":"SystemSettings","value":{"defaultDomainId":"#default","defaultHostname":"mail.mydomain.com"}}
{"@type":"upsert","object":"NetworkListener","value":{"imaps":{"bind":{"[::]:993":true},"name":"imaps","overrideProxyTrustedNetworks":{"10.0.0.0/8":true,"103.21.244.0/22":true,"103.22.200.0/22":true,"103.31.4.0/22":true,"104.16.0.0/13":true,"104.24.0.0/14":true,"108.162.192.0/18":true,"131.0.72.0/22":true,"141.101.64.0/18":true,"162.158.0.0/15":true,"172.16.0.0/12":true,"172.64.0.0/13":true,"173.245.48.0/20":true,"188.114.96.0/20":true,"190.93.240.0/20":true,"192.168.0.0/16":true,"197.234.240.0/22":true,"198.41.128.0/17":true,"2400:cb00::/32":true,"2405:8100::/32":true,"2405:b500::/32":true,"2606:4700::/32":true,"2803:f800::/32":true,"2a06:98c0::/29":true,"2c0f:f248::/32":true},"protocol":"imap","tlsImplicit":true},"pop3s":{"bind":{"[::]:995":true},"name":"pop3s","overrideProxyTrustedNetworks":{"10.0.0.0/8":true,"103.21.244.0/22":true,"103.22.200.0/22":true,"103.31.4.0/22":true,"104.16.0.0/13":true,"104.24.0.0/14":true,"108.162.192.0/18":true,"131.0.72.0/22":true,"141.101.64.0/18":true,"162.158.0.0/15":true,"172.16.0.0/12":true,"172.64.0.0/13":true,"173.245.48.0/20":true,"188.114.96.0/20":true,"190.93.240.0/20":true,"192.168.0.0/16":true,"197.234.240.0/22":true,"198.41.128.0/17":true,"2400:cb00::/32":true,"2405:8100::/32":true,"2405:b500::/32":true,"2606:4700::/32":true,"2803:f800::/32":true,"2a06:98c0::/29":true,"2c0f:f248::/32":true},"protocol":"pop3","tlsImplicit":true},"smtp":{"bind":{"[::]:25":true},"name":"smtp","overrideProxyTrustedNetworks":{"10.0.0.0/8":true,"103.21.244.0/22":true,"103.22.200.0/22":true,"103.31.4.0/22":true,"104.16.0.0/13":true,"104.24.0.0/14":true,"108.162.192.0/18":true,"131.0.72.0/22":true,"141.101.64.0/18":true,"162.158.0.0/15":true,"172.16.0.0/12":true,"172.64.0.0/13":true,"173.245.48.0/20":true,"188.114.96.0/20":true,"190.93.240.0/20":true,"192.168.0.0/16":true,"197.234.240.0/22":true,"198.41.128.0/17":true,"2400:cb00::/32":true,"2405:8100::/32":true,"2405:b500::/32":true,"2606:4700::/32":true,"2803:f800::/32":true,"2a06:98c0::/29":true,"2c0f:f248::/32":true},"protocol":"smtp","useTls":false},"smtps":{"bind":{"[::]:465":true},"name":"submissions","overrideProxyTrustedNetworks":{"10.0.0.0/8":true,"103.21.244.0/22":true,"103.22.200.0/22":true,"103.31.4.0/22":true,"104.16.0.0/13":true,"104.24.0.0/14":true,"108.162.192.0/18":true,"131.0.72.0/22":true,"141.101.64.0/18":true,"162.158.0.0/15":true,"172.16.0.0/12":true,"172.64.0.0/13":true,"173.245.48.0/20":true,"188.114.96.0/20":true,"190.93.240.0/20":true,"192.168.0.0/16":true,"197.234.240.0/22":true,"198.41.128.0/17":true,"2400:cb00::/32":true,"2405:8100::/32":true,"2405:b500::/32":true,"2606:4700::/32":true,"2803:f800::/32":true,"2a06:98c0::/29":true,"2c0f:f248::/32":true},"protocol":"smtp","tlsImplicit":true}},"matchOn":["name"]}
{"@type":"update","object":"Http","value":{"useXForwarded":true}}
{"@type":"upsert","object":"Certificate","value":{"default":{"certificate":{"@type":"File","filePath":"/opt/stalwart/etc/certs/tls.crt"},"privateKey":{"@type":"File","filePath":"/opt/stalwart/etc/certs/tls.key"}}},"matchOn":["certificate"]}
{"@type":"update","object":"Email","value":{"compressionAlgorithm":"lz4"}}
{"@type":"update","object":"MtaStageAuth","value":{"require":{"else":"listener != 'smtp'"},"saslMechanisms":{"else":"[plain, login]"}}}
{"@type":"upsert","object":"MtaRoute","value":{"relay":{"@type":"Relay","address":"email-smtp.us-east-1.amazonaws.com","authSecret":{"@type":"EnvironmentVariable","variableName":"SMTP_PASSWORD"},"authUsername":"AKIA2UC3CZ4IUGTQWQ4L","implicitTls":false,"name":"relay","port":587,"protocol":"smtp"}},"matchOn":["name"]}
{"@type":"upsert","object":"MtaVirtualQueue","value":{"remote":{"name":"remote"}},"matchOn":["name"]}
{"@type":"upsert","object":"MtaDeliverySchedule","value":{"relay":{"expiry":{"@type":"Ttl"},"name":"relay","notify":{"@type":"Default"},"queueId":"#remote","retry":{"@type":"Custom","intervals":{"0":{"duration":120000},"1":{"duration":300000},"2":{"duration":600000},"3":{"duration":900000},"4":{"duration":1800000},"5":{"duration":3600000},"6":{"duration":7200000}}}}},"matchOn":["name"]}
{"@type":"update","object":"MtaOutboundStrategy","value":{"route":{"else":"'relay'"},"schedule":{"else":"'relay'"}}}
{"@type":"update","object":"SpamClassifier","value":{"model":{"@type":"FtrlFh"}}}
{"@type":"upsert","object":"SpamRule","value":{"aws_ses_flagged":{"@type":"Header","condition":{"else":"''","match":{"0":{"if":"(name_lower == 'x-ses-spam-verdict' || name_lower == 'x-ses-virus-verdict') \u0026\u0026 contains(value_lower, 'fail')","then":"'AWS_SES_FLAGGED'"}}},"description":"Trust AWS SES Spam and Virus verdicts","name":"AWS_SES_FLAGGED"}},"matchOn":["name"]}
{"@type":"upsert","object":"SpamTag","value":{"ses_flagged":{"@type":"Score","score":1000,"tag":"AWS_SES_FLAGGED"}},"matchOn":["tag"]}

Stalwart Version

v0.16.x

Installation Method

Docker

Database Backend

PostgreSQL

Blob Storage

S3-compatible

Search Engine

PostgreSQL

Directory Backend

LDAP

Additional Context

I have reviewed the documentation and FAQ and confirm that my issue is NOT addressed there.

on

I have searched this support forum (open and closed topics) and confirm this is not a duplicate.

on

I understand that topics in this category are triaged by a bot first but a human reply will follow up. If I’d prefer a human-only reply, I’ll add the no-ai tag to my topic.

on

This is expected behavior, not a bug: server configuration compiled into the running core (listeners, MTA rules/expressions, directory backends, telemetry) is parsed once into an in-memory snapshot at startup. Updating those objects only writes to the database, it doesn’t rebuild the running snapshot. You need to trigger a ReloadSettings action for changes to take effect without a full restart:

stalwart-cli create Action/ReloadSettings

By contrast, directory data (accounts, domains, mailing lists, aliases, group memberships) does take effect immediately since it invalidates caches on save.

So for your apply script, after upserting Domain, SystemSettings, NetworkListener, Directory, etc., append a ReloadSettings action (and possibly ReloadTlsCertificates if certs changed) instead of restarting the container.

Reference: Overview | Stalwart and Actions | Stalwart

This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong.