Skip to main content
One OxiMail server can host several organizations (called tenants in the storage model). Isolation is not a permission check bolted on top — it is built into the storage layer: tenant_id is a mandatory parameter on every store method and every SQL query filters on it (ADR-015). A request handler physically cannot read another organization’s data, even through a bug. RBAC sits on top of that as defense in depth, not as the only line. A single-organization install is simply the degenerate case: the wizard provisions one tenant named default and everything on this page still applies.

Organizations

Per-organization state includes its accounts, domains, quotas, encryption keys (the at-rest key hierarchy is per organization — see encryption at rest), its runtime config overrides, and policy knobs such as:
  • Default time zone — the last tier of the calendar fallback chain (groupware).
  • Client offline persistence — whether the organization allows client applications to persist data locally for offline use (offlinePersistenceAllowed, settable over the admin Tenant/set surface). An organization with strict data-residency rules can turn it off.
  • Journaling rules and the erasure reversal TTL — see compliance.

Accounts and roles

Roles are admin, member, restricted, readonly, disabled (ADR-028). A disabled account keeps its data but loses agency: no login, no SMTP submission (550), no vacation replies — mail still delivers to it. Deletion is the erasure phase machine, not a row delete. Per-account storage quotas are set in MB (0 removes the per-account limit).

App passwords

Clients that cannot follow the web login flow (IMAP clients, DAV agents) authenticate with per-client app passwords, generated per account. They are independently revocable, their verification is constant-work (no timing oracle), and failures feed fail2ban like every listener. Every app password carries a scope, chosen at creation and required (no silent full-access default): full, mail, calendar (which includes tasks — one scheduling credential covers CalDAV VTODO clients), contacts (the CardDAV-sync credential), drive, or a custom list of capability URNs as a JSON array. A custom list is validated at creation on both surfaces: every element must be a capability this server actually serves, and the refusal names the offending entries — a credential claiming a grant the dispatcher would never honour cannot be minted. The CLI (oximail app-password create) validates against the built-in vocabulary and says so; scoping a credential to an extension capability registered at boot goes through POST /auth/app-password, which also knows those. Passkeys (WebAuthn) have the same management discipline: GET /auth/passkey and DELETE /auth/passkey/{id} let a signed-in user enumerate and revoke their credentials, and oximail passkey list|revoke is the operator’s out-of-band door — deliberate, because when the credential to revoke is the one that signs into the webmail, the revocation path must not be the webmail. The listing includes each credential’s discoverable hint (was it created usable by a usernameless login), recorded at enrolment.

Shared team accounts

A shared account is a non-loginnable team identity — accounting@, team@ — that owns a mailbox and team resources without being a person:
Nobody logs in to it; humans reach it through grants. Three invariants keep the model honest:
  • A shared account can never be a grantee. A shareWith entry whose principal is backed by a shared account is rejected at write time on every domain — a non-loginnable identity can never exercise a grant, so accepting one would create dead ACL entries.
  • Its drive always has a custodian. The shared account’s file root is not writable over JMAP; custody is an explicit CLI surface. oximail account custodian show lists the current custodians and whether the tenant human-admin fallback holds; custodian reattach --principal <p> [--replace] grants a human-backed principal full rights on the root — audited with the prior state kept in the audit entry, so a transfer is restorable. The account-role writers refuse to remove the organization’s last human admin while any shared drive lacks an explicit custodian.
  • In the directory it is typed honestly. A shared account’s principal is emitted as type: "other", not individual, so people-pickers do not offer it as a person.

Shared contact directories

An organization can publish shared contact sets — a client list, a staff list — that appear in every member’s address book without anyone copying a card around. They are an admin-plane object: they are created and named by a tenant admin, and day-to-day entries are maintained by the principals listed as editors, groups included. Two consequences matter for how you run this:
  • Members get contacts, not a folder. Entries reach a member as read-only cards marked as coming from the organization, on the same plane an external LDAP or SCIM feed would use. There is nothing for a user to accidentally rename, move or delete, and nothing to un-share when someone leaves.
  • The content is the organization’s, not the member’s. A directory is purged by removing its entries; it is not carried along by an individual account’s erasure, because it was never that person’s data.
The surface is served over JMAP for admin and editor sessions — there is no CLI verb for it, deliberately: it is directory content, not server configuration. The method and property details are on the contacts page.

Capacity caps

Chat capacity is bounded per organization via the [chat] TOML section, and the enforced values are advertised in the chat capability object so clients can respect them before hitting the error: These bound the fan-out write product (every room message writes one row per member).

One server, several organizations — the boundaries

  • Mail domains belong to organizations; an account’s address must be in a domain its organization owns.
  • Blobs are stored under per-organization paths and encrypted with per-organization key hierarchies; cross-organization deduplication does not exist by design.
  • Journaling destinations, share grants, and every JMAP object resolve strictly within the organization.
  • The admin API and CLI operate per organization (--tenant-id); there is deliberately no “all tenants” data view.