Skip to main content
This page explains the OxiMail configuration model: where settings come from, what must live in the file versus what can change at runtime, and how a runtime change takes effect. It is the hub that the per-feature configuration sections link into. For the exhaustive list of keys in any one area, see the example config and the matching operator page.

The model: TOML baseline, DB overrides

OxiMail does not store its whole configuration in the database. It reads a TOML file at boot as the baseline, and layers optional per-organization database overrides on top for runtime tuning without a restart. This is ADR-007. The lookup order for a setting is:
  1. Database override (if one exists for this key) — wins.
  2. TOML file — the fallback, and the source of truth for everything else.
The reasoning is operability. A TOML file can be read with cat, versioned in git, copied with scp, and edited over SSH. A full-database config cannot, and it creates a chicken-and-egg problem: the database has to be configured before you can read the configuration that configures the database. OxiMail keeps the file as the authoritative baseline and uses the database only for runtime overrides.
The TOML file always remains the source of truth for bootstrap and disaster recovery. If you lose the database overrides, the server still boots from the file. The reverse is not true.

Bootstrap vs runtime settings

Not every setting can be a database override. Anything OxiMail needs before the database is open has to come from the file. That is the chicken-and-egg rule:
Never put bootstrap configuration in the database only.
Bootstrap settings (TOML only) — needed before or in order to open storage and start listening:
  • [storage] sqlite_path, blob_path, search_path, encrypted — where and how the database itself lives.
  • [server] bind, http_bind, admin_bind, hostname, base_url — the listening sockets and identity.
  • [smtp] bind, submission_bind, smtps_bind, hostname — the SMTP listening sockets.
  • [auth] default_tenant.
These describe how the process comes up. Putting them in a database override is meaningless, because the server cannot read the database until they have already been applied. Runtime settings (overridable) — things you may want to tune on a running server: rate-limit ceilings, fail2ban thresholds, spam options, and similar operational knobs. These have a sensible TOML default and can be overridden at runtime through the admin API (see Setting a runtime override).
A handful of settings are deliberately restart-only even though they look like runtime knobs. For example, the SRS secret key ([srs] secret_key) is read once at boot and is not hot-reloadable, so rotating it requires a restart. When a section is restart-only, its operator page says so.

Hot-reload

Runtime overrides are designed to take effect without restarting the process. The mechanism is ArcSwap, a lock-free atomic pointer swap (ADR-016): the component holds an Arc to its current state, and a reload atomically swaps in a new Arc. Readers are never blocked — in-flight requests finish on the old value, and every subsequent read sees the new one. This is used for the hot-reloadable components: TLS certificates (so an ACME renewal applies without a restart), the spam blocklist, runtime config overrides, and compiled Sieve scripts. Because reads are never locked, hot-reload has no measurable cost on the request path: a renewal or an override is a single pointer swap, and reloads are rare.

Setting a runtime override

Runtime overrides are stored per organization (a tenant in the storage model) and set through the admin REST API. See the Admin API for authentication (every admin endpoint requires the admin bearer token configured under [admin] token). To set one or more overrides, PUT /admin/v1/config with a JSON body whose overrides object maps each config key to its new value:
curl -X PUT https://mail.example.com/admin/v1/config \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"overrides": {"limits.max_message_size": "100MB"}}'
Each key/value pair is written to the override store for the default tenant. Values keep their JSON type (a number stays a number, a string stays a string).
Listing the current overrides via GET /admin/v1/config is not implemented in v0.30.0 — the endpoint returns a usage hint rather than a dump. To inspect what is in effect, check the TOML file and the overrides you have set.

The config file and its shape

The server reads /etc/oximail/oximail.toml by default. CLI subcommands and the serve command accept --config <path> to point elsewhere. The setup wizard writes this file for you on first boot; this page is for tuning it afterwards. A representative annotated file looks like this. It is not exhaustive — it shows the shape and the most common sections. See the per-feature operator pages for the full set of keys in each area.
# /etc/oximail/oximail.toml

[server]
hostname = "mail.example.com"          # server identity
bind = "0.0.0.0:443"                    # HTTPS / JMAP socket
base_url = "https://mail.example.com"  # used in JMAP Session URLs
http_bind = "0.0.0.0:80"               # ACME challenges + HTTPS redirect
# admin_bind = "127.0.0.1:9000"        # optional separate admin port
trusted_proxies = ["127.0.0.1"]        # read X-Forwarded-For from these
allowed_origins = []                   # CORS allowlist (empty = reject all)

[storage]
sqlite_path = "/var/lib/oximail/data.db"
blob_path = "/var/lib/oximail/blobs"
encrypted = true                       # SQLCipher on by default

[auth]
default_tenant = "default"

[mode]
role = "primary"                       # "primary" or "backup"

[smtp]
bind = "0.0.0.0:25"                    # inbound (receive)
submission_bind = "0.0.0.0:587"        # submission (send, STARTTLS)
smtps_bind = "0.0.0.0:465"             # submission over implicit TLS
hostname = "mail.example.com"
dane_enabled = true                    # outbound DANE (RFC 7672)

# One signing key per sending domain (v0.30 array form).
[[smtp.dkim_keys]]
domain = "example.com"
selector = "default"
key_path = "/etc/oximail/dkim/example.com.default.key"

[tls]
acme_enabled = true                    # Let's Encrypt; off when behind a proxy
acme_email = "admin@example.com"

[security]
fail2ban_max_attempts = 5
fail2ban_ban_minutes = 60
trusted_ips = ["10.0.0.0/8"]           # bypass rate limit AND fail2ban

[rate_limit]
http_per_ip_per_minute = 100
smtp_outbound_per_hour = 50

[spam]
dnsbl_servers = []

[greylisting]
enabled = false

[legacy]
enabled = true                         # IMAP / CalDAV / CardDAV bridges

The real top-level sections

The sections that exist in v0.30.0 are: [server], [storage], [auth], [mode], [smtp] (with the [[smtp.dkim_keys]] and [[smtp.transport_maps]] arrays), [tls], [spam], [greylisting], [security], [rate_limit] (with the [rate_limit.destination_domains] table), [logging], [admin], [telemetry], [legacy], [mta_sts], [dns], [sieve], [network], [distribution_groups], [scheduler], [srs], [metrics], and [push]. The per-feature pages document the keys inside each:

Validating a config before you deploy

OxiMail fails loud on a bad config: rather than booting in a half-broken state, it refuses to start and prints what is wrong. You can run the same validators ahead of time without starting the server:
oximail check-config --config /etc/oximail/oximail.toml
This catches things like a primary server with no DKIM signing key, dane_enabled set with no DNS provider, and deprecated pre-v0.30 DKIM keys still in the file. See the CLI reference for the full command set and Operations for the day-to-day workflow.

Next steps