Overview
Many servers are configured through text files that the administrator edits on disk. Stalwart works differently: it keeps almost all of its configuration in its own data store, the database that also holds its accounts and mail. A small local file, config.json, says only where that data store is.
Everything else is stored in the data store as configuration objects, from domains and accounts to listeners, TLS certificates, SMTP rules and spam filter settings, and it is changed through an API. This page explains that model, the command line of the server, and the point at which a change takes effect.
Configuration objects
Section titled “Configuration objects”Each part of the configuration is an object with a type, such as Domain, NetworkListener or SystemSettings. Types come in two kinds. Most can have any number of objects, for example one per domain or one per listener. Others, such as SystemSettings, are singletons, with exactly one object.
Many types also have variants, which select a backend or a mode. A data store, for example, is either RocksDB or PostgreSQL, and each needs different fields. The variant is written as @type in JSON. The schema reference documents every type, its fields and their defaults.
The objects are read and changed through a JMAP API at /jmap/. JMAP is the JSON-over-HTTP protocol that Stalwart also offers to mail clients. The configuration API adds one method family per type to it, for example x:Domain/get and x:Domain/set. Three tools use the API:
- The WebUI, the browser-based administration console, which has a form for every type.
- The CLI,
stalwart-cli, for the command line, scripts and declarative deployments. - Any JMAP client, for integrations that call the API directly.
Every value is validated when it is saved. An invalid setting is therefore refused with an error, and never stored. Object encoding describes how values are written in JSON.
Keeping the configuration in the data store has two consequences for operations. Every node of a cluster reads the same configuration, so there are no per-node files to keep in step. A backup of the data store also includes the configuration.
Every field has a default chosen for a secure server. A new installation therefore needs only the settings that the setup wizard asks for. The defaults are listed on the reference page of each object.
Configuration file
Section titled “Configuration file”config.json contains a single DataStore object, which holds the connection settings of the data store. The setup wizard writes it. The following file is a minimal one for RocksDB:
{"@type":"RocksDb","path":"/var/lib/stalwart/"}The data store is the only setting that cannot be changed through the API. The API itself reads the configuration from the data store, so it cannot be used to say where the data store is. Moving to another data store therefore means editing the file and restarting the server. The data is not moved with it, as described in Changing a backend later.
When the file does not exist at startup, the server has no data store to read from. It then starts in bootstrap mode and shows the setup wizard.
Command line
Section titled “Command line”The server binary takes a small number of command-line options, because almost everything else is configured in the data store:
| Option | Effect |
|---|---|
-c <path>, --config <path> | Starts the server with the given configuration file. |
-e <path>, --export <path> | Exports all data of the data store to a directory, then exits. See Database migration. |
-i <path>, --import <path> | Imports data exported with --export, then exits. |
-o, --console | Opens the store console. It takes no value, so it must follow the other options: stalwart --config <path> --console. |
-h, --help | Prints the options. |
-V, --version | Prints the version. |
The configuration file is required, so a server started without --config prints the options and exits. --export, --import and --console need the file too, because it is how they find the data store.
The environment variable CONFIG_PATH can name the file instead. When it is set, however, the command line is not read at all. --export, --import and --console are then ignored, and the server starts normally. Environment variables lists the other variables the server reads.
Making a change
Section titled “Making a change”The WebUI, the CLI and the API all make the same change, because the first two are built on the third. The following examples show each of them setting the server’s default hostname. In the WebUI, open Settings › Network › Services, Settings › Network › General, change Default Hostname and save. With the CLI, the change is one command:
stalwart-cli update SystemSettings --field defaultHostname=mail.example.comThrough the API, the same change is an x:SystemSettings/set call sent to /jmap/ and authenticated as an administrator:
curl -X POST https://mail.example.com/jmap/ \ -H 'Content-Type: application/json' \ -d '{ "using": ["urn:ietf:params:jmap:core", "urn:stalwart:jmap"], "methodCalls": [ ["x:SystemSettings/set", {"update": {"singleton": {"defaultHostname": "mail.example.com"}}}, "c1"] ] }'Applying changes
Section titled “Applying changes”Saving an object stores it, but the running server does not always use the new value at once. Each setting takes effect at one of four points. The WebUI does not show which point applies to a field, so the following table is the reference:
| Takes effect | Settings |
|---|---|
| Immediately | Accounts, groups, domains, DKIM keys, mailing lists, roles and tenants. |
| After their own reload action, or a restart | TLS certificates and blocked IP addresses. |
| After a settings reload | Every other object that is not read only at startup. |
| After a restart | Stores, caches, listeners and search indexing concurrency. |
Certificates and blocked addresses have reload actions of their own. Certificates are loaded again by the ReloadTlsCertificates action, and blocked IP addresses by the ReloadBlockedIps action. Neither action is needed for certificates renewed by ACME or for bans added by the server itself, since the server applies those on its own.
Most server settings are compiled into the running configuration, which is rebuilt only when the settings are reloaded. This group covers MTA rules and expressions, limits, the spam filter, the DNS resolver, directories, telemetry and the allowed IP addresses. Changes to them take effect when the ReloadSettings action runs:
stalwart-cli create Action/ReloadSettingsThe same action is available in the WebUI under Management › Actions. A reload is safe to attempt. If it finds an error in the configuration, it changes nothing: the running configuration stays in force, and the errors are returned with the action, in notCreated when it is created through the API.
Some settings are read only at startup, so they change only after a restart:
- The data store set in
config.json, the blob, in-memory and search stores, the coordinator, and the metrics and tracing stores. - Every field of the Cache object, including cache swapping.
- Every NetworkListener field, including bind addresses, TLS options,
maxConnectionsandsocketBacklog, together withmaxConnectionsandproxyTrustedNetworkson SystemSettings. - The search indexing concurrency,
maxConcurrentIndexTasksandindexBatchSize.
A settings reload still checks the listener settings for errors, but it does not open or close ports, and it does not rebuild stores or caches.
When a saved change seems to have no effect, the setting usually belongs to one of the last three groups, and the change is waiting for a reload or a restart. In a cluster, a reload reaches the other nodes only when a coordinator is configured. Without one, it applies only to the node that received the action, as described in Applying configuration changes.