Skip to main content
Once the binary is installed, oximail setup brings the server up: it generates the config, the DKIM key, the first organization, and the admin account, writes a systemd unit, starts the service, escrows the server key, and verifies it all with a doctor round. The wizard is the supported path for a first boot. Everything it does can also be run from a setup file or as individual subcommands for headless deployments.
This page documents what the wizard in OxiMail v0.30.0 actually does, step by step, in the order it runs. Where a step is conditional or optional, that is called out. Steps the wizard does not perform are listed at the end.

Running the wizard

By default the wizard reads and writes the server config at /etc/oximail/oximail.toml. It needs to bind privileged ports and write under /etc/oximail and /var/lib/oximail, so run it as root. Flags: Exit codes: 0 done, 1 done but the verification reported a degraded point, 2 a hard failure (the verification failed, or a refusal before any write), 130 cancelled by the operator (Escape then Quit, Ctrl-C, or declining to overwrite an existing configuration), 143 stopped by SIGTERM. A cancelled run is never 0: oximail setup … && systemctl start oximail stops there. The wizard takes the whole screen on a capable terminal (a terminal on both ends, TERM set and not dumb, at least 80×24): one dialog per question, the steps listed on the left, the probes, the review and the engine’s output in the body. Escape asks whether to quit, and quitting is clean: nothing is written before the review is confirmed, and the terminal is given back on every exit. Everything the full-screen wizard prints also goes to /var/log/oximail-setup.log (the temporary directory when that file cannot be opened), so nothing the screen showed is lost when it closes. While the screen is up, anything else written to the terminal (a command the wizard runs, a notice from another part of the program) goes to the same pane and log, never over the screen. --no-tui is the way out of a TERM that claims more than the terminal can do; --config-file and the subcommands always run on the line. If /etc/oximail/oximail.toml already exists, the wizard warns and asks whether to overwrite. Declining cancels setup and leaves the existing config untouched.

Interactive flow (primary mail server)

The wizard runs in steps: Checks, Identity, Topology, Configuration, Review, Apply, Start, Recovery, Doctor, Import. Every question comes before the review; nothing is written until the review is confirmed.

Host checks and preflights

The wizard first measures the host and shows it: the distribution, whether systemd is the init system, the memory, the free space on the volume of /var/lib/oximail, the architecture. These are recommendations, never a refusal: a distribution outside those tested (Debian 12 and 13, Ubuntu 24.04), less than 2 GiB of memory or less than 10 GiB free are shown as warnings. Then, before either path (interactive or --config-file), and before writing anything, it runs three preflights. A dry run measures nothing, so it gates nothing.
  • Clock. It reads the kernel clock state and refuses when no time source is disciplining it: a mail server whose time is wrong fails silently and late (inbound DKIM signatures refused as future-dated, ACME renewals and TLS validity broken, token and fail2ban windows expiring wrong). In a container the fix is on the host (chrony on the hypervisor); timedatectl showing NTP service: inactive inside the container is expected. oximail setup --accept-unmanaged-clock continues anyway and records the acknowledgement in the wizard state.
  • Volume. It walks the device-mapper stack under the data path and refuses a bare volume: since ADR-148 the database, blobs and search index are written in clear, and the encrypted volume is the only at-rest protection. It continues if the volume is encrypted, continues without asserting anything if it cannot measure, and otherwise prints the six-point reference configuration and stops. oximail setup --accept-bare-volume lifts the refusal knowingly: the consent is written as [security] storage_encryption_attested = true, and doctor keeps measuring.
  • Resolver. The server asks every DNS question to unbound on 127.0.0.1 and believes a DNSSEC answer only once that resolver is proven. The wizard runs the server’s own probe: the identity setup wrote in unbound’s configuration, the root validated, an unsigned zone not, zones that do not validate refused, denials of existence proven. When it does not pass, setup refuses: DANE would be blind (see email authentication). The server itself never refuses to start for a resolver reason, but until its own probe has proven the resolver it believes no DNSSEC answer and holds the mail to DANE domains. There is no “continue anyway”. On Debian the wizard offers to install it: apt-get update, unbound with its root trust anchor (dns-root-data), and the configuration carrying the box’s identity (an unbound already running keeps its own configuration; the file is added beside it). It then verifies the resolver (active, listening on loopback, trust anchor present, the probe passing) before touching /etc/resolv.conf, and orders oximail.service after unbound.service. The server never reads /etc/resolv.conf; the box’s other DNS readers do. The wizard rewrites it to name 127.0.0.1 only, saving the previous content beside it, where the rewrite sticks: a plain file, the systemd-resolved symlink, or a Proxmox container once setup has written /etc/.pve-ignore.resolv.conf. Under resolvconf, NetworkManager or cloud-init, which regenerate the file, it leaves it and prints that manager’s own step. A --config-file run installs only with oximail setup --install-resolver. On a host without apt-get, setup prints the unbound configuration the probe recognises and where to put it (the file, its include, /etc/resolv.conf, then oximail resolver check), keeping an identity already written. The configuration setup writes turns aggressive NSEC off (aggressive-nsec: no): with it on, a denial cached before a record was published hid that record for the zone’s whole negative TTL (1800 s at Cloudflare), past the five-minute cap. A box installed by an earlier binary keeps its old text: oximail doctor names it (resolver_config), and oximail resolver ensure names the first line that differs, then with --apply rewrites the file (identity kept), checks it and restarts unbound (exit 0 current or rewritten, 1 a rewrite pending, 2 a failure; --json).

Identity and topology

  1. Primary email domain (for example example.com) — the domain part of your addresses.
  2. Mail hostname (defaults to mail.<domain>). Both names are checked where they are typed: letters, digits and hyphens only (no hyphen at either end of a label), labels of 1 to 63 characters, at most 253 in all, at least two labels. They are lower-cased and a final dot is dropped; an internationalised name is converted to its ASCII form (bücher.example becomes xn--bcher-kva.example). A setup file and oximail setup dns are held to the same rules.
  3. Deploy mode: Primary mail server, Backup MX, or Smarthost relay (dedicated hub). (Backup MX and the smarthost hub each have their own flow, described later.)
  4. Deployment topology: Direct (OxiMail handles TLS on port 443) or Behind reverse proxy (Caddy, Nginx, and so on). Choosing the proxy option also asks for a local base port (default 8080; OxiMail’s ACME challenge listener binds that port + 1, and a port typed wrong is asked again, never replaced by a default) and for the system group of the reverse proxy (proposed: caddy, else www-data). The proxy reaches OxiMail’s web surface through a Unix socket only that group may open, never a TCP port: on a loopback port every process of the machine could claim a client address in X-Forwarded-For, get someone banned by fail2ban or claim a trusted_ips exemption.

Configuration

  1. Cloudflare API token (leave empty to configure DNS manually).
  2. Admin email (defaults to admin@<domain>) and, for a direct deployment, the ACME (Let’s Encrypt) email (defaults to the admin email). Behind a reverse proxy the ACME email is not asked (it is admin@<domain>), but ACME stays on: OxiMail orders the certificate of the mail ports, and the proxy forwards the HTTP-01 challenge to it.
  3. Outbound send mode: Direct or Through a relay. The wizard first measures what direct delivery would face: whether outbound port 25 is open, the reverse DNS of the public IP (missing, not forward-confirmed, or a provider’s default name counts as weak), and whether the IP is already on a public blocklist. It recommends a relay when any of the three is against direct delivery (most clouds block the port). Outbound port 25 is shown for IPv4 and IPv6 separately, with the reverse DNS of the IPv6 address: mail leaves over IPv4 first, and over IPv6 only toward a destination without IPv4. When the box’s IPv4 exit is a shared NAT address that is not its own (see the DNS step), a relay is recommended: no SPF record can authorise that address. See Outbound relay & smarthost for the whole relay workflow, including the exit preflight for cutting over to direct later. This question is about the leaf side (sending through a relay) and is skipped on the smarthost-hub flow — the two ends of a relay never conflate.
  4. Default time zone of the organization (an IANA name). This is the last tier of the calendar fallback chain (event zone → user zone → organization zone → UTC), so floating events resolve in local time instead of UTC. For a primary server it is asked once, after the country of establishment: the default is the host’s OS zone when it is a precise one, else the zone of that country (a rented server usually says UTC), else UTC; an unknown zone is asked again. On the smarthost flow it is asked here, defaulting to the host’s zone. Change it later with oximail tenant update --default-timezone.
  5. Webmail origins on another host (empty for none), written to [server] allowed_origins.
  6. Reports: Send reports? [y]es / [c]rashes only / [n]o / [d]etails, with no answer preselected: Enter alone asks again, d shows what is sent. The answer is recorded before the config is written; see community telemetry.
  7. How the server will be backed up: Hypervisor snapshot (the default), oximail backup on a schedule, or Later. Nothing here arranges a backup; the answer is written as [backup] strategy (hypervisor-snapshot, oximail-backup or none), and oximail doctor reports a failure until a plan is declared, or while a scheduled oximail backup has not run (backups).
An extension the binary carries can ask for its own section of oximail.toml: the wizard asks it after these questions and shows it in the review, and it is written with the core file.

Review

The wizard probes the ports this configuration will bind (see port preflight), then shows the whole configuration as a setup file would carry it, secrets named but never shown, and offers three choices: apply, save the answers as a setup.toml, then apply (the file, mode 0600, carries no secret: replay it with oximail setup --config-file once the *_file references it names are in place), or abort, which leaves nothing written.

Step: port preflight check

The wizard tries to bind each required port locally and reports OK or FAIL with the reason (permission denied, already in use, cannot bind). The required ports are: When you chose “Behind reverse proxy”, ports 80 and 443 are skipped (the proxy owns them). Blocked ports do not stop the wizard, but the server may not work until they are opened in your firewall. A port already in use names the process that holds it and its systemd unit, with the command that frees it, and the review then pre-selects abort; the ports OxiMail’s own service holds on a re-run are not reported as blocked.

Step: data directories

The wizard creates /var/lib/oximail, /var/lib/oximail/blobs, /etc/oximail, /etc/oximail/dkim, and /etc/oximail/tls. There is no /var/log/oximail: the server’s logs go to stdout and are captured by journald.

Step: DNS

The wizard establishes which public addresses are the server’s own, then checks the domain’s DNS records and prints a pass/fail report.
  • IPv6: a global IPv6 address is carried by the box itself and is taken as detected.
  • IPv4: the address seen from outside is the server’s only when it is on one of its interfaces, or when a forward to this box is proven. Two proofs, both read-only: a connection to that address on port 25 comes back to the box with a one-time token (NAT loopback), or the router’s UPnP table maps port 25 to the box. Nothing is ever written on the router. When nothing proves it, the wizard asks whether the router forwards the mail ports to this machine, “no” by default. For a shared exit (a NAT used by other services), no A record and no ip4: in SPF are published: the server is published and authorised over IPv6 only.
  • If no address can be detected, the wizard asks you to type one.
  • With a Cloudflare token, it can create the missing records via the Cloudflare API: A, MX (priority 10), SPF, DMARC, MTA-STS (TXT + CNAME), TLS-RPT, autoconfig and autodiscover CNAMEs, a CAA record restricting issuance to Let’s Encrypt, and SRV records for client autodiscovery (_autodiscover, _imaps, _submission, _caldavs, _carddavs). If a conflicting MX record exists it asks before replacing it. An SPF or DMARC record already published is never doubled (two SPF records make every receiver’s check fail): an identical one is left, a different one is shown. For SPF the wizard proposes a single record that keeps your mechanisms and your all and adds this server’s, and publishes it only if you accept; a DMARC record is replaced only if you accept. It then re-checks DNS, after emptying the local resolver’s cache of the zone (unbound-control flush_zone): the check before publishing had cached “no such name”, and the re-check and the end-of-install verification used to call missing, for up to five minutes, the records just published. The token is also written to /etc/oximail/cloudflare.token (mode 0600, owned by the service user), and the config gets [dns] provider = "cloudflare" with that file and the zone id, resolved through the API, so the server republishes the DANE TLSA records at every certificate issuance and renewal.
  • Without a token, it prints the check results and tells you to create the records yourself; the final summary reminds you to publish the TLSA records after each certificate (oximail dane publish).

Step: DKIM

The wizard generates an RSA DKIM keypair (selector default) at /etc/oximail/dkim/<domain>.default.key and prints the DNS record to publish. If a Cloudflare token was supplied, it also publishes the DKIM TXT record at default._domainkey.<domain> (deleting any stale record for that selector first). The published key is in SPKI format (v=DKIM1; k=rsa; p=...).
The Cloudflare DKIM publish only happens inside the full wizard. Domains added later, or setups where no Cloudflare token was provided, have no DKIM TXT record published automatically — generate and publish those manually (see DKIM key generation below).

Step: configuration file

The reports answer is recorded first, in the consent marker next to the installation id (/var/lib/oximail/.telemetry_consent.json). The wizard then writes /etc/oximail/oximail.toml for a primary server. Key contents:
  • [server] hostname, base URL, bind addresses (direct: 0.0.0.0:443 / 0.0.0.0:80; proxy: bind = "unix:/run/oximail-http/jmap.sock" and http_bind = "127.0.0.1:<port + 1>" for the ACME challenge, with no trusted_proxies, since the peer on the socket is the proxy and its X-Forwarded-For is the client), and the admin surface on 127.0.0.1:9090.
  • [storage] SQLite at /var/lib/oximail/data.db, blobs at /var/lib/oximail/blobs (plain SQLite; the at-rest protection is the volume).
  • [security] storage_encryption_attested = true only if you ran setup --accept-bare-volume: the wizard measures the volume under the data path and refuses a bare one without that consent.
  • [auth] default_tenant = "default".
  • [mode] role = "primary".
  • [smtp] bind ports plus a [[smtp.dkim_keys]] block for the domain/selector.
  • [tls] with ACME on, in both topologies (behind a proxy it serves the mail ports; the proxy keeps the certificate of the web names). On a direct box acme_domains holds the hostname, with the mta-sts., autoconfig. and autodiscover. names below it, commented: an ACME order fails as a whole on a name that does not resolve yet.
  • [legacy] IMAP/CalDAV/CardDAV enabled.
  • [spam], [security] (fail2ban + trusted IPs), [rate_limit], and [logging] (JSON to journald).
  • [metrics] dnsbl_self_ips with the public IP the wizard detected, so doctor watches the sending address on the blocklists.
  • [dns] (with a Cloudflare token, above).
  • [admin] metrics_token_file = "/etc/oximail/metrics.token": the wizard creates a random token for /metrics, so the administration token no longer opens the metrics (operations).
  • [backup] strategy, the declared plan.
  • [telemetry] enabled, false when you answered no; the comments point to the public page listing every field of the report and to oximail telemetry purge.
For a reverse-proxy deployment the wizard prints a ready-to-use Caddyfile snippet (the ACME challenge path to 127.0.0.1:<port + 1>, everything else to unix//run/oximail-http/jmap.sock) and notes that SMTP (25, 587, 465) and IMAP (993) bind directly and are not proxied. It also says what the proxy is: it terminates TLS for the names it serves and reads the web traffic in clear, so if another entity runs it, that entity is a sub-processor of the data it carries. The snippet proxies everything to OxiMail, with the sole exception of the ACME HTTP challenge path — a route allowlist is deliberately avoided, because one missing route family silently breaks client discovery or DAV. When the webmail is served from a different origin, the wizard also fills [server] allowed_origins; CORS is answered by OxiMail itself, never by the proxy (a blanket OPTIONS interception at the proxy silently breaks DAV client discovery). To tune any of these values afterwards, see Configuration.

Step: organization + admin account

The wizard creates the first organization (a tenant in the storage model: id default, name Default, domain derived from the admin email) and then the admin account. Creation is idempotent: if it already exists the wizard reports so and continues. You are prompted for the admin password, with confirmation. The password must be at least 8 characters and include an uppercase letter, a lowercase letter, and a digit; the prompt loops until it is strong enough. The account is created with the admin role and language fr, and the organization gets the time zone asked earlier.
Until an organization exists, the server starts in web-wizard mode (“no tenants”). The CLI wizard always creates the default tenant before the admin account, so a completed oximail setup run leaves you ready to log in.

Step: systemd unit

The wizard writes /etc/systemd/system/oximail.service. The unit runs oximail serve --config /etc/oximail/oximail.toml, restarts on failure, raises the file-descriptor limit, and sends output to journald under the oximail identifier. If it cannot write the file (not root), it prints the unit content so you can install it manually. See Installation for the unit details. Behind a proxy it also writes /etc/tmpfiles.d/oximail-http.conf and creates /run/oximail-http at once (setgid, the proxy’s group; tmpfiles recreates it at every boot), and the unit’s ReadWritePaths gains that directory. A group that does not exist makes systemd-tmpfiles fail: the wizard says so with the line to fix. It also writes oximail-health.service and oximail-health.timer, which record the doctor round every 15 minutes, and prints the command that enables them (systemctl enable --now oximail-health.timer); see the doctor round. The unit is written after the organization and admin step, because that step creates the database and the wrapped server key, and the unit declares LoadCredentialEncrypted= only for a key that exists: written earlier, it lacked the directive and the service could not unwrap its own key. The service user is created before the first file handed to it, and the data is handed to it last (data.db* in mode 0600), once everything is written, so a fresh install starts. The wizard says “owner oximail” only after that hand-over succeeded.

Step: start the service

The wizard runs systemctl daemon-reload, systemctl enable oximail, and systemctl start oximail, then waits up to 90 seconds until systemd reports the service active and port 443 answers (the ACME certificate is provisioned in that time). When the service is not running, it says so with the state systemd reports and quotes the unit’s last error line; it blames ACME only when the service runs and port 443 is still waiting. On a direct box it then activates, in acme_domains, each of the mta-sts., autoconfig. and autodiscover. names that already resolves to the box (the DNS step published them minutes before) and has the certificate reissued: the running server holds port 80, so it is restarted and orders the names itself, keeping the certificate it serves if that order fails; the wizard waits up to 90 seconds for the new certificate and says which happened. A server that is not running leaves the port to oximail cert renew --apply. The names still waiting for DNS stay commented, and the summary names them. The start is a systemctl restart: on a re-run over an installed box, the service picks up the configuration just written. The ports it holds are not reported as blocked, and the topology step recognises its own service on port 443 instead of guessing a reverse proxy.

Step: verification

It connects to ports 25, 587, 993, and 443, checks the JMAP endpoint at https://<hostname>/.well-known/jmap (a 401 counts as up), and confirms the DKIM directory and config file exist. A failed verification does not end the run: the recovery, doctor and import steps still happen, the summary says the verification failed, and the run exits with its code at the end.

Step: recovery

The server has just created its root key; the wizard escrows it. It shows the recovery key once (full screen on a capable terminal, never written to the setup log), asks you to type it back, which proves it was stored and is the recovery drill doctor ages, and ends with the scp command that takes the escrow blob off the box. A wrong key is asked again. The plaintext copy of the key that waited for the escrow is removed only once the key was typed back; leaving keeps the escrow and that copy, says the key was not confirmed and names oximail key purge-pending, which checks the key and then removes the copy. oximail key escrow --guided replays the same ceremony on a box installed without it, and oximail doctor reports a failure on a server with no escrow (encryption at rest).

Step: doctor

The last check is a full oximail doctor round with --reachability: the web names and their certificates, this box on the blocklists, and the rest of the round, shown as rows with their tally.

Step: optional data import

Finally the wizard offers to import existing mail. Choices: Skip, From IMAP server (Dovecot, Exchange, Gmail, any IMAP4 server), From Stalwart (JMAP API), or From mbox/Maildir files. Each choice prompts for the relevant connection details. This step runs after the server is already configured and running, so a failed import does not break the install. You can retry any time with oximail migrate. See Migration for the standalone import workflow.

The summary

The wizard ends with what outlives the screen: the login URL (https://<hostname>/auth/login), the config path, the log command, the outcome of the recovery step, the certificate names activated or still waiting, and what remains to do: DANE (automatic with the Cloudflare provider, oximail dane publish otherwise), the metrics endpoint and its token file for Prometheus, oximail doctor --reachability with the round’s tally, and the backup plan (for a hypervisor snapshot, the vzdump hook that confirms each run with oximail backup mark --source hypervisor --apply). Its last lines say how a device gets its settings. On an iPhone, iPad or Mac, open https://<hostname>/apple/mobileconfig?email=<address> and install the profile the server serves, pre-filled with the administrator’s address (the address is percent-encoded in the link, since a + would read as a space; the profile is not signed). Thunderbird and Outlook need only the address: they find the settings themselves through autoconfig and autodiscover (autodiscovery).

Backup MX flow

If you choose Backup MX as the deploy mode, the wizard runs a dedicated flow through the same engine as a setup file: it asks for the primary MX hostname (defaults to mail.<domain>), additional relay domains, the forwarding mode (queue to accept locally and retry to the primary, with DSN bounces on permanent reject; or proxy to relay in real time and propagate the primary’s reply, falling back to queue if the primary is unreachable), the admin email, a Cloudflare token, and the reports question. After the review it creates the admin account, writes a role = "backup" config, installs the systemd unit, optionally creates its DNS records through Cloudflare (its MX beside the primary’s, priority 20, and its A and AAAA, each read first and never doubled), and verifies port 25 and the config. A backup MX signs nothing for the domain and points no client at itself, so the flow runs no DKIM step and publishes no client SRV record: the domain’s DKIM key and its _imaps / _submission records are the primary’s. An earlier wizard generated a key at the default selector and, with a Cloudflare token, replaced the primary’s public key with it, so every message the primary signed then failed DKIM. Without a token, the printed instructions list the same records (MX, A, AAAA) and say why there are no others.

Smarthost flow

If you choose Smarthost relay (dedicated hub), the wizard runs the same primary flow with curated deltas — a smarthost is a full outbound MTA, not a separate install mode (there is deliberately no dedicated boot path). What changes:
  • You are asked for the hub’s egress IP(s) (comma-separated). Only IP literals are accepted: they feed the DNSBL self-monitor and the published SPF record, where a hostname or a typo would silently break every client’s SPF.
  • The generated config declares role = "smarthost" under [mode]. The role closes the mailbox surfaces and adds no boot path of its own; at every boot the server warns (as a boot-audit issue your monitoring can alert on) if [legacy] is still enabled or the DNSBL self-monitor is empty. A combined mailbox-plus-hub server stays role = "primary".
  • Mailbox surfaces are curated off ([legacy] enabled = false) and [metrics] dnsbl_self_ips is filled with the egress IPs.
  • The hub SPF record clients will include (spf.<domain> TXT "v=spf1 ip4:… -all") is published through Cloudflare when you provided a token, or printed for manual publication. The name belongs to the hub: it is read first, an identical record is left, a different v=spf1 one is replaced, and a second is never added (two SPF records at one name are a permerror for every domain that includes it).
  • No relay account is created. Relay clients enter through minted tokens only; the wizard prints the oximail account relay mint-token command as the next step. See Outbound relay & smarthost for the whole hub workflow.

Non-interactive setup

For headless or automated installs, write a setup TOML file and pass it with --config-file:
The source tree ships config/setup.example.toml, the reference of every key the file accepts with its meaning; an unknown key is refused. The file has a [server] section (domain, hostname, optional deploy_mode = "primary", "backup" or "smarthost", topology = "direct" or "proxy", proxy_port, webmail_origins, proxy_group (the reverse proxy’s system group, detected when absent), public_ipv4 = "auto" | "forwarded" | "none" for a box behind a NAT, and the required telemetry = "yes" | "crashes" | "no"), plus optional [dns] (cloudflare_api_token_file or cloudflare_api_token_env, cloudflare_zone_id, replace_existing_mx, replace_existing_spf, replace_existing_dmarc), [tls] (acme_email), [admin] (email plus password_file or password_env, and an optional default_timezone), [relay] or [relay_enrolment] for a leaf that sends through a relay, [backup_plan] (strategy), [import] (source = "imap" | "jmap" | "mbox" | "skip" with the matching connection fields), for backup mode, [backup] (primary_hostname, accepted_domains, mode, config_output_path), and, for the smarthost flow, [smarthost] (egress_ips = ["203.0.113.25", …] — validated as IP literals, fail-loud). An extension’s section is given as [extension.<table>]. A committed setup file carries no secret: an inline [admin] password, [dns] cloudflare_api_token, [relay] password or [relay_enrolment] token is refused before anything is written. A question the file does not answer is refused by name, never prompted on a pipe: a file without [server] telemetry stops the run and names the key, and so does a box behind a NAT whose file leaves public_ipv4 on "auto" when nothing proves the forward. A cloudflare_api_token_file the service cannot read (under /root, /home or /tmp, which its sandbox hides, or not readable by the oximail user) is not written into the server configuration: the token is copied to /etc/oximail/cloudflare.token (0600, owner oximail), and the run says so. The run starts the service, then verifies it, as the interactive wizard does. With --no-start it writes everything, starts nothing and skips the verification, saying so. The same steps run without prompts. If there is no [admin] section, the wizard skips admin creation and tells you to create one with oximail account create. Passwords passed non-interactively are still strength-checked, and a weak one aborts the run. Nobody is there to receive a recovery key, so the escrow is not created: the run says so as its last word, and oximail doctor reports a failure until oximail key escrow --guided has run. Use --dry-run with any of these to preview the generated config and actions without writing, starting or verifying anything.

Individual setup subcommands

Every wizard step is also a standalone subcommand under oximail setup, useful for re-running one piece:
All accept --dry-run. oximail setup dns also runs without a terminal, so a script, an agent or ssh without a tty can republish a box’s records. Flags answer every question it can ask: --replace-mx, --replace-spf (the existing SPF is merged when it can be, your mechanisms kept), --replace-dmarc, and --public-ipv4 (auto measures and asks when nothing proves the address is the box’s, which is refused without a terminal; forwarded says the router forwards the mail ports here; none publishes IPv6 only). An undecided public IPv4 stops the run before anything is read or published, naming the flag; a replace question no flag answered keeps the existing record and is named at the end. Run again on a configured zone, it doubles nothing. It reads the zone first and creates a record only where the zone holds none of its kind: a second _mta-sts TXT (which leaves the domain with no MTA-STS policy, RFC 8461 §3.1) or a second TLS-RPT record can no longer appear, and an MTA-STS record is kept whatever its id. MX records naming another server that you keep are the domain’s only MX: this server’s is not added beside them. The zone decides, not the checks, since a check reads a resolver cache that can lag behind a record just published. --dry-run reads the zone and lists each record: would create, already there, or differs and kept. The run ends naming the checks still failing. Exit codes: 0 done, 1 something left (a record kept for want of its flag, a check failing), 2 the public IPv4 left undecided.

DKIM key generation

There is also a top-level DKIM command (separate from oximail setup dkim), which is the one referenced in the Quick Start:
This generates the keypair under /etc/oximail/dkim/ and prints the DKIM DNS record to publish. It does not publish the record to Cloudflare; only the full wizard does that, and only when a token is supplied. Publish the printed v=DKIM1; k=rsa; p=... value at <selector>._domainkey.<domain> yourself.

Starting and verifying manually

If you skipped the wizard’s start step, or installed the unit by hand:
Re-run the wizard’s checks at any time:

What the wizard does not do

  • It does not install the binary. Get oximail onto the host first; see Installation.
  • It does not publish DNS without Cloudflare. With no Cloudflare token it prints the records (or check results) for you to add at your provider.
  • It does not publish DKIM outside the full wizard. The standalone setup-dkim and setup dkim commands generate and print the record but do not push it to DNS.
  • It does not configure ed25519 DKIM. Only RSA-2048 keys are generated and loaded at runtime.
  • It does not manage your firewall. The port preflight only reports blocked ports; opening them in the VPS firewall or security group is manual.
  • It does not arrange the backup. It records the plan you declare; the hypervisor or a scheduled oximail backup takes the copies, and doctor checks that they happen.
  • It does not create extra organizations or accounts. It provisions exactly one default tenant and one admin account. Add more with oximail tenant create and oximail account create.

Next steps