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. The set is closed and the server publishes it: auth.session_lifetime_secs, auth.refresh_reuse_leeway_secs, erasure.reversal_ttl_days, blob_gc.derived_sweep, retention.trash_days (per tenant, from 1 day), and the rate_limit.* ceilings (HTTP per IP and per account, login per account, the anonymous-surface budgets, SMTP submission per tenant, inbound per IP, relay recipients per day, outbound recipients per hour). retention.chat_days and calendar.working_hours_enforce are in the set too, and so are the six trash horizons, drive., calendar., contacts., tasks., notes. and chat.trash_retention_days (per tenant; absent means the instance value, 0 means no purge, see per-tenant horizons). Fail2ban thresholds and [spam] options are not overridable; they take a restart. 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 ([srs] secret_key_file) 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.

Secrets live outside the TOML

The configuration file is tracked, backed up, copied to .bak-* by the deploy rail and quoted in journald in cleartext. No secret sits in it: every secret is a sidecar file the TOML points at, one line, mode 0600, owned by the service user, unreadable or empty is fatal at boot. The sidecar keys are [[smtp.dkim_keys]] key_path, [[smtp.transport_maps]] auth_password_file, [dns] cloudflare_api_token_file, [dns] rfc2136_tsig_key_file, [admin] metrics_token_file, [srs] secret_key_file and previous_secret_key_file, and [meet] turn_shared_secret_file. The server refuses to boot on an inline [srs] secret_key, previous_secret_key, [meet] turn_shared_secret or [dns] rfc2136_tsig_key, and the refusal names the move to make. Before swapping in a binary that carries the refusal, run it on each box:
The verb writes each value to a 0600 sidecar next to the TOML (srs.secret, srs.previous.secret, turn.secret, rfc2136.tsig), hands it to the service user, saves the TOML as oximail.toml.pre-externalize-<ts>, rewrites only the matching lines to their _file form, and re-runs every boot-time validator on the result. An existing sidecar holding a different value is never overwritten. oximail doctor then checks that the service user can read every sidecar.

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:
Each key/value pair is written to the override store for the default tenant; PUT /admin/v1/tenants/{id}/config takes the same body for another tenant (admin API). Values keep their JSON type (a number stays a number, a string stays a string). A key outside the published set is refused with unknownKeys and the knownKeys list, never stored: a typo cannot become a silent setting. A value the server would not read as written (a trash horizon outside 0 to 36500, for example) is refused with details, and nothing in the request is written. GET /admin/v1/config returns the overrides in force and knownKeys. From the CLI, oximail config get-override|list-overrides|set-override|clear-override read and write per tenant (--tenant-id), dry-run by default, --apply writes (CLI); set-override refuses the values this endpoint refuses, dry run included, with the same details and exit 2.

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.

The real top-level sections

The sections the server reads are: [server], [storage], [auth], [mode], [smtp] (with the [[smtp.dkim_keys]], [[smtp.transport_maps]] and [[smtp.egress_pool]] arrays, the last one in bulk sending), [tls], [spam], [security], [rate_limit] (with the [rate_limit.destination_domains] table), [dmarc_reports], [feedback_reports], [complaint_feedback] (bulk sending), [contacts], [quota], [branding], [rights], [logging], [admin], [telemetry], [backup], [erasure], [geocode], [image_proxy], [pgp_directory], [legacy], [passkey], [mta_sts], [dns], [sieve], [distribution_groups], [scheduler], [chat], [calendar], [drive], [tasks], [notes], [meet], [srs], [metrics], [push], [bimi], [smime] and [federation]. An unknown key inside one of them refuses the file; a section outside the list is logged as dead configuration at boot, unless an extension the binary carries claims it. The per-feature pages document the keys inside each:

[bimi] — sender brand logos from external senders

Since 2026-09-20 the server resolves the brand logo of an external sender whose message passed DMARC under an enforcing policy and whose domain publishes a brand indicator backed by a mark certificate the server validates itself (chain to an embedded set of roots, revocation lists, certificate transparency). The logo reaches clients as the same brandLogoBlobId property that already carries your own organization’s logo for internal senders; internal senders keep yours. Next to it, brandMarkType names the kind of certificate behind an external logo (registered_mark or government_mark for a Verified Mark Certificate; prior_use_mark, modified_registered_mark or provisional_mark for a Common Mark Certificate; null for your own logo), so a client can show a verified-brand mark for a VMC only. A BIMI-Selector header is honoured only when the DMARC-aligned DKIM signature that passed covers it; a sending domain resolves at most eight selectors a day.
Per organization, the override key bimi.external_resolution_enabled (default on) switches the feature off for that organization only: its messages stop being resolved AND its links already written stop being served. oximail doctor reports the trust base (bimi_trust: every root, issuing CA or log key that stops being usable within 90 days is named) and the caches (bimi_cache). A trust file you name that does not load is fatal at boot — the embedded base is never substituted for it silently. The resolver works at ingestion: messages that were already in a mailbox when it started carry no logo. oximail bimi backfill --account <email> reports, per sender domain, which stored messages of that account would qualify (same gates as the live path, no network call); --apply resolves and links them. One account per run.

[federation] — the door paired organizations call

Off unless the section is written, and then enabled is required: a [federation] without it is refused, never read as off. On, it mounts /jmap/federation (busy time and shared Drive folders for paired organizations) and the endpoint and tls_pins are what oximail federation identity reads to the other administrator (federation).
With enabled = true the server refuses to start without an endpoint at /jmap/federation, with a cache setting out of its bounds, or when a peer’s recorded address falls inside [security] trusted_ips. check-config does not run these checks; doctor reports the cache bounds (federation_content_cache).

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:
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