> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oximail.ch/llms.txt
> Use this file to discover all available pages before exploring further.

# Multi-tenancy & provisioning

> Organizations (tenants), storage-layer isolation, account roles and quotas, shared team accounts and their custodians, app passwords, and the per-organization policy knobs.

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

```bash theme={null}
oximail tenant create --tenant-id acme --name "ACME SA" ...
oximail tenant update --default-timezone Europe/Zurich
oximail tenant list
```

Per-organization state includes its accounts, domains, quotas, encryption keys (the at-rest key hierarchy is per organization — see [encryption at rest](./encryption-at-rest)), its [runtime config overrides](./configuration), and policy knobs such as:

* **Default time zone** — the last tier of the calendar fallback chain ([groupware](./groupware#time-zones)).
* **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](./compliance).

## Accounts and roles

```bash theme={null}
oximail account create --email jane@acme.tld --password ... --role member --tenant acme
oximail account set-role --account jane@acme.tld --role admin
oximail account disable|enable|delete ...
oximail account quota --account jane@acme.tld --mb 10240
```

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](./compliance), 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](./operations) 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:

```bash theme={null}
oximail account create-shared --email accounting@acme.tld --tenant acme
```

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](../developer/jmap-contacts#tenant-contact-directories).

## 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:

| Cap                       | Default | Over-limit error |
| ------------------------- | ------- | ---------------- |
| Rooms per account         | 100     | `overQuota`      |
| Participants per room     | 200     | `tooLarge`       |
| Message size (serialized) | 64 KiB  | `tooLarge`       |

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.
