Skip to main content
Every operation on an OxiMail box goes through one binary, oximail, and its command surface holds a few promises. Each verb that a machine reads speaks JSON. Each exit code means the same thing everywhere. Each write is shown before it is made. The diagnostics work while the server is down. Those are exactly the properties an automatic operator needs: a cron script, a nightly monitoring round, or an AI agent that runs the box for its administrator and reports back. None of them was designed for AI. They exist because operations demanded them, and a test in the server’s CI fails when a new verb breaks one. That is why you can build on them. This page is the contract for whoever writes that automation. The CLI reference maps the verbs; oximail <command> --help stays the authority on flags.

The contract

Where to start

Three commands give the state of a box, in this order:
  • doctor is the composite check: configuration, database, queue, DNS, DKIM, certificate, DANE, backup plan, key escrow, age of the last recovery drill. Read every status: ok, warn, fail, and skipped. skipped means nothing was measured, which is never a pass. Each entry also carries its family, its dated facts (observedAt, expiresAt) and, on a warn or a fail, the remedy: the verb to propose, as typed (the doctor round).
  • status counts accounts, messages, the queue, bans and storage.
  • schema check says whether the binary accepts the database on disk (accepts, newer or error). Read it before a deploy or a rollback.

The operating guide ships with the binary

It prints the operating guide for an agent: the order of the gestures, the contract above, and what to never do. The binary prints it itself, so the guide always matches the installed version. There is nothing to download, and no copy to go stale. An agent that connects to a box over ssh can read it first.

Delegation keeps a human in charge

The automaton proposes; a human applies. The dry run and the confirmation flags exist so that the person responsible reads what will be written before it is written. On a box that serves people, show the dry run before running --apply. The journal says who decided. A process that runs the verbs for an agent declares it in the environment of each verb it starts: OXIMAIL_CLI_CHANNEL=agent and OXIMAIL_CLI_DECIDED_BY=<the name of the person who opened the session>. The administrative acts of that verb are then journaled as cli-agent under that person’s name, never as a command they typed, and never in the agent’s name: the agent decides nothing. Any other channel value, agent without a name, or a name without agent is refused.

Limits, stated up front

  • The contract binds the verbs created after it. A few older one-shot migration verbs (backfill-* and their family) execute by default and take --dry-run. Their default is not flipped: changing a shipped default silently changes what a pinned script does. Their --help says so.
  • Read verbs speak JSON; a verb whose output is a file does not. print-systemd-unit prints a systemd unit, which is text by nature. --help shows whether a verb takes --json.
  • A question is answered by a flag, never by a prompt on a pipe. oximail setup --config-file refuses, by name, a question its file does not answer. oximail setup dns runs without a terminal and takes --replace-mx, --replace-spf, --replace-dmarc and --public-ipv4: a replace question left open keeps the existing record and ends with exit 1, an undecided public IPv4 stops the run before anything is published, with exit 2 (setup subcommands).
  • Human text is not a contract. Messages get clearer from one version to the next. Only the JSON output and the exit codes are stable.