This page describes what the server does, not what your regulator requires. Map these mechanisms onto your own legal obligations (GDPR, Swiss FADP, sector rules) with counsel.
Mail journaling
Journaling silently copies matching mail to a journal mailbox or an external archiver, per organization. It is the Postfixalways_bcc / sender_bcc_maps / recipient_bcc_maps feature set, done as data rather than as config file edits — the typical use is an Art. 30 GDPR record-keeping duty or a sector archiving rule.
Off by default. No rules means no journaling; nothing is copied until an administrator creates a rule.
Rules
A journaling rule has three dimensions:
The two destination forms differ in custody:
- Mailbox — the copy is re-ingested into a local journal mailbox, so it is encrypted at rest like all other mail and stays inside the organization’s storage boundary.
- External — a copy is relayed to an external archiver (a dedicated archiving service or another server you operate).
Invariants
- Relay traffic is never journaled. Mail relayed for
serviceaccounts keeps the ephemeral-retention promise — a smarthost does not archive its clients’ mail. - A journal copy is never re-journaled. An
X-Oximail-Journaledheader guards both destination forms, including across an external round-trip, so rules cannot create a mail loop. - Destinations are organization-isolated. A rule can only target a mailbox inside its own organization (the storage-layer tenant isolation, ADR-015).
- Journaling is best-effort and never blocks mail. A journaling failure is logged loudly but the delivered or submitted message is unaffected — record-keeping must not become a delivery outage (ADR-017).
- One evaluation per message. Inbound mail is evaluated once after the delivery loop; outbound mail once after a non-relay submission is enqueued.
Managing rules
By CLI:JournalingRule/get and JournalingRule/set under the urn:oximail:params:jmap:v2:admin capability (admin-only).
Account erasure (GDPR Art. 17 / DSAR)
Deleting an account is not aDELETE statement — it is a multi-step operation with legal weight, and a crash or an operator retry must never leave it half-done or run it twice. OxiMail runs every account erasure as a phase machine recorded in the database:
- At most one live erasure per account, by construction. A partial unique index makes a concurrent second erasure impossible across processes — an admin-API delete and a CLI delete on the same box cannot race.
- Durable and resumable. Each phase records its progress. A driver that dies mid-flight (crash, proxy timeout) leaves a job that a maintenance worker adopts and resumes, using a lease with heartbeat; a boot-time sweep catches orphans. The pass is decoupled from the HTTP connection that requested it, so a proxy timeout cannot cancel it.
- Visible when stuck. A job parked on a durable failure shows in
oximail erasure list --stuckand in theoximail_erasure_jobs_stuckmetric — an enqueued erasure reaches a terminal state or surfaces as stuck, never silently half-done. - Thin adapters. The admin API and the CLI drive the same orchestration; there is one erasure engine.
What each phase does
- Agency freeze. Before anything is erased, the account loses its ability to produce new data: it is disabled, its tokens are revoked, SMTP submission answers
550, vacation auto-replies are suppressed, and its scheduled / undo-window sends are cancelled at the source. The erasure then refuses to proceed while any undelivered outbound queue entry remains (fail-loud; the queue drains bounded by the retry lifetime, or an operator cancels entries via the queue API). Every path that emits mail on behalf of an account is named in a registry with the gate that stops it, enforced by a source audit (ADR-118). - Residue redaction. Copies of mail the leaver sent to colleagues keep naming them in the survivors’ mailboxes — in structured columns and inside the raw message headers. The pass rewrites those copies through the normal ingestion path: identity headers collapse onto a non-routable sentinel (
former-user@<domain>.invalid), address columns are redacted structure-aware (a homonymous recipient is never touched), and the message body is untouched — it is the recipients’ correspondence. Message-ID tokens, the survivors’ own To/Cc records, and trace-header-only appearances are documented out of scope. - Identity strip. The subject’s directory principals are PII-scrubbed and kept as tombstones — graph anchors that prevent chat and notification history from being silently misattributed, while carrying no identity. Every grant of the leaver is stripped from every
shareWithmap (each surviving owner gets a recorded change to resync on), actor snapshots in survivors’ notifications lose name and email, and a tombstoned principal is refused as a new grantee, so a re-share cannot defeat the strip. Resource and location principals the leaver merely created (meeting rooms) survive intact — they belong to the organization. - Data sweep. The account’s rows are deleted table by table, its documents are removed from the full-text search index (the index has no encryption to drop, so it is swept explicitly), and its blobs are crypto-erased by dropping the account keys. The sweep fails loud on a real error instead of declaring success over surviving rows.
- Completion. One durable, non-identifying record is written: outcome counters plus an optional DSAR ticket reference, keyed to the identity tombstone. Any error text on the job row is cleared at terminal, so no failure message can carry a personal address into the durable record.
The reversal buffer
Erasure is deliberately hard to undo — but an operator error (wrong account) needs an honest recovery path. During the identity strip, each stripped share grant’s key-delta (the leaver’s own bits per object, never the whole map) is captured encrypted to the organization’s master key into a reversal buffer with a disclosed TTL:- Default 7 days, anchored at enqueue time; per-organization override
erasure.reversal_ttl_days. oximail erasure undo <job-id>merges each delta back into the current map under compare-and-set — an owner’s intervening edit is never clobbered.--grantee <principal>restores the grants onto a different live principal: after a completed erasure this is the real recovery, re-provision the person and re-grant to their new identity (validated, provenance audited).- Past the deadline the restore is refused server-side, even while the rows physically remain; a reaper deletes expired rows unconditionally.
Proving it happened
Give the erasure a DSAR reference at delete time, and resolve it later:prove resolves the reference to the non-identifying completion record — the durable evidence that the Art. 17 erasure was performed (Art. 5(2) accountability). Exit code 1 means no record exists.
The audit log
Theaudit_log is append-only with a long retention horizon, and erasure must reach it too — without touching accountability:
- A retention worker purges rows past the configured horizon. The horizon has a single source of truth, shared with the
auditLogRetentionDayscapability the server advertises, so the two cannot drift; a last-sweep gauge feeds a staleness alert. - Account erasure de-identifies the audit rows it must retain: the fact survives (action, outcome, resource, timestamp) while the identity is scrubbed (client IP, user agent, identifying detail keys). The principal id remains as the tombstone anchor. Erase the identity, keep the fact.
CLI quick reference
See the CLI reference for every flag, and Operations for the metrics and workers involved.