> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oximail.ch/llms.txt
> Use this file to discover all available pages before exploring further.

# First boot & setup wizard

> Run the oximail setup wizard to provision the first organization, admin account, DKIM key, config file, and systemd service, then verify the server is up.

Once the [binary is installed](/installation), `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.

<Note>
  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.
</Note>

## Running the wizard

```bash theme={null}
oximail setup
```

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:

| Flag                   | Effect                                                                                               |
| ---------------------- | ---------------------------------------------------------------------------------------------------- |
| `--config <path>`      | Server TOML path. Default `/etc/oximail/oximail.toml`.                                               |
| `--config-file <path>` | Run **non-interactive** from a setup TOML file (no prompts).                                         |
| `--dry-run`            | Print what would be done and the generated config, without writing anything or starting the service. |

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

1. **Primary email domain** (for example `oximail.ch`) — the domain part of your
   addresses.
2. **Mail hostname** (defaults to `mail.<domain>`).
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 the local HTTP port OxiMail should listen on (default `8080`).
5. **Outbound send mode**: `Direct` or `Through 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](/operator/outbound-relay)
   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 reports `OK` or `FAIL`
with the reason (permission denied, already in use, cannot bind). The required
ports are:

| Port | Purpose                            |
| ---- | ---------------------------------- |
| 25   | SMTP inbound (receive mail)        |
| 80   | HTTP (ACME certificate challenges) |
| 443  | HTTPS / JMAP                       |
| 465  | SMTPS (encrypted submission)       |
| 587  | SMTP submission (send mail)        |
| 993  | IMAPS (read mail)                  |

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 (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=...`).

<Note>
  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](#dkim-key-generation) below).
</Note>

### Step: configuration file

You are asked for the **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, 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>` with `trusted_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).

For a reverse-proxy deployment the wizard prints a ready-to-use Caddyfile
snippet and notes that SMTP (25, 587, 465) and IMAP (993) bind directly and are
not proxied. 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](/operator/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](/installation) for the unit
details.

### 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`.

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`.

<Warning>
  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.
</Warning>

### Step: start the service

The wizard runs `systemctl 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 at
`https://<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 with `oximail migrate`. See [Migration](/operator/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 to `mail.<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_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.
* **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](/operator/outbound-relay) for the
  whole hub workflow.

## Non-interactive setup

For headless or automated installs, write a setup TOML file and pass it with
`--config-file`:

```bash theme={null}
oximail setup --config-file setup.toml
```

The file has a `[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 under `oximail setup`, useful
for re-running one piece:

```bash theme={null}
oximail setup dns --domain <d> --hostname <h> [--cloudflare-api-token <t>] [--replace-mx]
oximail setup dkim --domain <d> [--selector default]
oximail setup admin --email <e> [--password <p> | --password-file <f>] [--tenant-id default] [--config <path>]
oximail setup verify [--hostname <h>] [--config <path>]
```

All accept `--dry-run`.

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

```bash theme={null}
oximail setup-dkim --domain <domain> --selector <selector> --config <path>
```

| Flag          | Default                     | Notes                                                                                                               |
| ------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `--domain`    | (required)                  | Domain to sign for, for example `oximail.ch`.                                                                       |
| `--selector`  | (required)                  | DKIM selector, for example `default`.                                                                               |
| `--algorithm` | `rsa`                       | Only `rsa` (RSA-2048) is accepted at runtime. `ed25519` is rejected because the runtime signer loads RSA keys only. |
| `--config`    | `/etc/oximail/oximail.toml` | Used to determine the key storage directory.                                                                        |

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:

```bash theme={null}
systemctl daemon-reload
systemctl enable --now oximail
systemctl status oximail
journalctl -u oximail -f
```

Re-run the wizard's checks at any time:

```bash theme={null}
oximail setup verify --hostname mail.<domain>
```

## What the wizard does not do

* **It does not install the binary.** Get `oximail` onto the host first; see
  [Installation](/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 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

* [Configuration](/operator/configuration) — tune the values the wizard wrote.
* [TLS & ACME](/operator/tls-acme) — certificate handling, direct and behind a proxy.
* [Email authentication & security](/operator/email-auth-security) — SPF, DKIM, DMARC details.
* [Migration](/operator/migration) — import mail from an existing server.
* [CLI reference](/operator/cli) — the full command list.
