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:doctoris the composite check: configuration, database, queue, DNS, DKIM, certificate, DANE, backup plan, key escrow, age of the last recovery drill. Read everystatus:ok,warn,fail, andskipped.skippedmeans nothing was measured, which is never a pass. Each entry also carries itsfamily, its datedfacts(observedAt,expiresAt) and, on awarnor afail, theremedy: the verb to propose, as typed (the doctor round).statuscounts accounts, messages, the queue, bans and storage.schema checksays whether the binary accepts the database on disk (accepts,newerorerror). Read it before a deploy or a rollback.
The operating guide ships with the binary
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--helpsays so. - Read verbs speak JSON; a verb whose output is a file does not.
print-systemd-unitprints a systemd unit, which is text by nature.--helpshows whether a verb takes--json. - A question is answered by a flag, never by a prompt on a pipe.
oximail setup --config-filerefuses, by name, a question its file does not answer.oximail setup dnsruns without a terminal and takes--replace-mx,--replace-spf,--replace-dmarcand--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.