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, and verifies it. The
wizard is the supported path for a first boot. Everything it does can also be
run 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
/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:
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 asks a small set of questions, then runs the steps below in order.Questions asked up front
- Primary email domain (for example
oximail.ch) — the domain part of your addresses. - Mail hostname (defaults to
mail.<domain>). - Deploy mode:
Primary mail server,Backup MX, orSmarthost relay (dedicated hub). (Backup MX and the smarthost hub each have their own flow, described later.) - Deployment topology:
Direct(OxiMail handles TLS on port 443) orBehind reverse proxy(Caddy, Nginx, and so on). Choosing the proxy option also asks for the local HTTP port OxiMail should listen on (default8080). - Outbound send mode:
DirectorThrough a relay. The wizard probes outbound port 25 and recommends relay when direct delivery is impossible (most clouds block the port). 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.
Step: port preflight check
The wizard tries to bind each required port locally and reportsOK 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.
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: logs go to stdout and are captured by journald.
Step: DNS
You are asked for a Cloudflare API token (leave empty to configure DNS manually). The wizard auto-detects your public IP (asking you to type it if detection fails), then checks the domain’s DNS records and prints a pass/fail report.- With a 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. It then re-checks DNS. - Without a token, it prints the check results and tells you to create the records yourself.
Step: DKIM
The wizard generates an RSA DKIM keypair (selectordefault) 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
You are asked for the admin email (defaults toadmin@<domain>) and, for a
direct deployment, the ACME (Let’s Encrypt) email (defaults to the admin
email). Behind a reverse proxy, ACME is skipped because the proxy handles TLS.
The wizard 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:127.0.0.1:<port>withtrusted_proxies).[storage]SQLite at/var/lib/oximail/data.db, blobs at/var/lib/oximail/blobs,encrypted = true.[auth] default_tenant = "default".[mode] role = "primary".[smtp]bind ports plus a[[smtp.dkim_keys]]block for the domain/selector.[tls]with ACME on (direct) or off (proxy).[legacy]IMAP/CalDAV/CardDAV enabled.[spam],[security](fail2ban + trusted IPs),[rate_limit],[network] contribute, and[logging](JSON to journald).
[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: 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.
Step: organization + admin account
The wizard creates the first organization (a tenant in the storage model: iddefault, 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.
The wizard also captures the organization’s default time zone (an IANA
name, defaulting to the host’s OS zone — the common case is a single Enter).
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. An unknown zone is never persisted — it degrades to detection. Change it
later with oximail tenant update --default-timezone.
Step: start the service
The wizard runssystemctl daemon-reload, systemctl enable oximail, and
systemctl start oximail, then polls port 443 for up to 90 seconds while the
ACME certificate is provisioned. It prints whether the server became ready, and
if not, points you at journalctl -u oximail -f.
Step: verification
It connects to ports 25, 587, 993, and 443, checks the JMAP endpoint athttps://<hostname>/.well-known/jmap (a 401 counts as up), and confirms the
DKIM directory and config file exist.
On success the wizard prints the login URL
(https://<hostname>/auth/login), the config path, and the log command.
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 withoximail migrate. See Migration for
the standalone import workflow.
Backup MX flow
If you choose Backup MX as the deploy mode, the wizard runs a dedicated flow: it asks for the primary MX hostname (defaults tomail.<domain>),
additional relay domains, and 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). It then creates the admin account, generates DKIM,
writes a role = "backup" config, installs the systemd unit, optionally creates
backup DNS records (MX priority 20, A, DKIM, and IMAP/submission SRV records via
Cloudflare), and verifies port 25 and the config.
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
profile = "smarthost"under[mode]. The profile is an audited declaration, never a behavior switch: at every boot the server compares it against the config and warns (as a boot-audit issue your monitoring can alert on) if mailbox surfaces are still enabled or the DNSBL self-monitor is empty. A combined mailbox-plus-hub server simply does not set the profile. - Mailbox surfaces are curated off (
[legacy] enabled = false) and[metrics] dnsbl_self_ipsis 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. - No relay account is created. Relay clients enter through minted tokens
only; the wizard prints the
oximail account relay mint-tokencommand 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:
[server] section (domain, hostname, optional
deploy_mode = "primary", "backup" or "smarthost", topology = "direct"
or "proxy", proxy_port), plus optional [dns] (Cloudflare token, replace_existing_mx),
[tls] (acme_email), [admin] (email plus password or password_file,
and an optional default_timezone),
[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).
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.
Use --dry-run with any of these to preview the generated config and actions
without writing or starting anything.
Individual setup subcommands
Every wizard step is also a standalone subcommand underoximail setup, useful
for re-running one piece:
--dry-run.
DKIM key generation
There is also a top-level DKIM command (separate fromoximail 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:What the wizard does not do
- It does not install the binary. Get
oximailonto 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-dkimandsetup dkimcommands 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 create extra organizations or accounts. It provisions exactly
one
defaulttenant and one admin account. Add more withoximail tenant createandoximail account create.
Next steps
- Configuration — tune the values the wizard wrote.
- TLS & ACME — certificate handling, direct and behind a proxy.
- Email authentication & security — SPF, DKIM, DMARC details.
- Migration — import mail from an existing server.
- CLI reference — the full command list.