Skip to main content
An OxiMail instance stores everything in three places, all under your control: Plus /etc/oximail/ — config, DKIM keys, and the key material without which the two data stores are ciphertext. The three data locations plus /etc/oximail are the complete backup surface (operations).

How mail is laid out

A message is stored once, in two forms (ADR-009): the raw RFC 5322 bytes as a blob, and the parsed metadata (headers, structure, hasAttachment, preview, thread) as rows. Nothing re-parses on read. Blobs are addressed by the SHA-256 of their cleartext, so an attachment sent to ten colleagues is stored once per organization — deduplication happens below the mailbox level, and two copies of an email in two mailboxes are always two metadata rows pointing at one blob (never deduplicated at the message level; that class of bug is designed out). The blob directory is namespaced per organization and per account ({tenant}/{account}/…, with a reserved _tenant/ area for organization-wide content), which is what makes per-account crypto-erasure and per-organization isolation physical rather than logical.

Folders

Folders are JMAP Mailboxes with RFC 8621 roles (inbox, sent, drafts, trash, junk, archive), provisioned per account in the account’s language. Hierarchy, per-folder counts, and sharing (shareWith) are all data. The IMAP bridge shows the same tree. Duplicate folders left over from old imports are repaired with oximail account dedupe-folders (migration).

Trash, undelete, and retention

Destruction is two-stage with a visible trash (ADR-035): a destroyed object lands in deleted_items and can be restored — this backs the trash surfaces in every client and the recovery of destroyed DAV resources. Independent of user-facing trash, two retention windows matter operationally:
  • change_log: 90 days. The delta-sync history (/changes, sync-collection) is pruned past 90 days; a client absent longer than that does a full resync instead of an incremental one. This is by design — the change log is a sync mechanism, not an audit trail.
  • audit_log: years. The administrative audit trail has its own long horizon with an enforcing retention worker (compliance).
Blob garbage collection removes blobs no longer referenced by any row — with reference tracking that includes non-obvious holders like edit history, so GC never orphans content that something can still display.

Quotas

  • Per account: oximail account quota --account <email> --mb <n> (0 = no per-account limit). Exposed to clients via Quota/get.
  • Per collection: volume caps (ADR-072) bound what any single collection can grow to.
  • Chat capacity caps are separate (multi-tenancy).

The search index is disposable

The Tantivy index carries no primary data. If it is lost, corrupted, or you change its storage, rebuild it:
Reindexing streams every message through the same extraction used at ingestion; a background queue converges the work. Note the index is the one store whose content is not encrypted per account (it must be tokenized server-side to be searchable) — which is why account erasure sweeps it explicitly.

Database maintenance

None scheduled — this is deliberate. WAL checkpointing is automatic (bounded at ~64 MiB, checkpointed at shutdown), query-planner statistics are refreshed after migrations, and the connection pool needs no tuning below a few hundred concurrent users ([storage] pool_size, default 32, raise on read-heavy machines). The operational database story is: keep disk space, keep backups, and let the server run. oximail sql exists as a guarded escape hatch for support scenarios; day-to-day inspection goes through the CLI verbs (oximail mailbox, oximail email) and the admin API’s per-account data views.