Exchange is excluded from this API/CLI release. Any historical Exchange API or CLI example in this guide is unavailable; native portal behavior is outside this exclusion. See [current release scope](/developers/guides/release-scope?lang=en).

# Adopt existing Workspace mailboxes

An internal administrator can link an existing domain and ordinary mailboxes on a configured Workspace mail server to a company. The portal and API use the same inspection and confirmation service. Inspection performs only bounded JMAP reads; confirmation atomically creates local bindings, billing agreements and tasks. The existing password is retained. No domain or mailbox is recreated and no DNS records are published.

This administration function requires a human administrator, `admin:read` for inspection, `admin:write` for confirmation, a recent passkey and enabled administration capabilities. A service client cannot use these rights. API availability describes the current release configuration; tests with simulated suppliers are not evidence of a completed production adoption.

## Portal

Open **Workspace → Adopt existing mailboxes**. Confirm with your passkey, select the existing mail server and enter the exact lowercase domain name. Select the ordinary, unlinked mailboxes, target company and billing. Then review the company, server, existing and future limits and the full monthly price. Confirm adoption within ten minutes. A changed mailbox identity, server configuration, company agreement or package price requires a new inspection.

A free adoption preserves the existing storage limit, even when no package is large enough. An explicit limit of `0` remains unlimited; an unknown inherited limit must be clarified first. An explicit free agreement with a zero price is recorded. For monthly billing, an actively offered package must cover both the existing limit and the amount of stored data. The company's existing payment agreement must allow monthly invoicing; administration rights do not bypass advance payment. Agreements start today and join the existing monthly company invoice. Adoption does not create a separate immediate invoice.

## API inspection

The company in the URL path is the current administration context. `account_id` in the input is the explicit target company and may differ from it. `server_id` is an existing mail server with a shared server binding or an exact private binding to that target company.

Prefer `POST /api/v1/accounts/{context}/admin/workspace/adoptions/inspect`, with JSON:

```json
{
  "account_id": 123,
  "server_id": 17,
  "domain": "example.test",
  "name": "Voorbeeld Workspace",
  "mailboxes": ["alice", "bob"],
  "billing": "none"
}
```

This POST is inspection only. No Idempotency-Key is required and no operation, billing or supplier mutation is created. Small selections can also use `GET .../adoptions/inspect` with the same fields as query parameters; use JSON for large selections. Inspection is bounded to 1,000 accounts, with pages and object reads of at most 100 and at most 2 MiB per supplier response.

The response contains `data.input`, `snapshot_version`, `reviewed_at`, `expires_at`, the explicit company and server, selected provider identities, current and future quotas, `system_mailbox` and monthly prices excluding VAT in EUR. The ETag is the same snapshot version. Preserve the approved input and show it to the administrator before confirmation. Supplier keys, passwords and raw supplier responses are not returned.

## Confirmation and recovery

Send `POST /api/v1/accounts/{context}/admin/workspace/adoptions`, with the unchanged `data.input`, supplemented by `reviewed_at` and a new `intent_id` (UUID). Send the snapshot ETag in `If-Match` and one durable `Idempotency-Key`. `intent_id` identifies the same inspected local adoption; retain it after an ambiguous response. A different body under the same intent ID is refused. Repeating confirmation with a different HTTP idempotency key also returns the original local adoption and task intent, without additional mailboxes or agreements.

`dry_run:true` checks the same current conditions without creating local bindings, agreements or tasks. Successful confirmation returns `workspace_id`, `account_id`, `domain_id`, `task_ids`, `task_intent_id`, the monthly price and billing choice. The local receipt and API operation are recorded in the same database transaction. Follow the original operation; do not create a new adoption after a lost response.

Ordinary portal workers then process the linked configuration. All tasks created through the API carry the original explicit administration approval for this Workspace. Revoked rights block those tasks. Use the existing **Workspace administration reapproval** contract for the same operation and task intent after a current administrator has reviewed the hold. This creates no new adoption or invoice.

## Details requiring clarification first

A provider identity already linked to a Workspace is never reassigned. Users with administration roles, nonstandard permissions, group or tenant bindings, customized additional quotas or disabled/foreign aliases require a separate explicit migration. A domain with independent `postmaster`, `abuse` or `mailer-daemon` accounts or split system addresses requires reconciliation first. If ordinary mailbox aliases already receive those system addresses, the same selected mailbox remains the system mailbox. An existing catch-all must point to a mailbox adopted in the same action.

`412` means inspection expired or the approved details changed. `409` means an identity, server binding, quota or payment agreement cannot be adopted safely. When strong verification is missing, the administrator must reconnect with a passkey. Do not change a package or payment agreement merely to bypass a refusal.

## CLI

Save the input example above as a private file (`0600` on Unix). The typed commands use the same portal service:

```sh
binadit admin workspace adoptions inspect --from-file adoption-input.json \
  --file adoption-review.json --account 1
binadit admin workspace adoptions commit --from-file adoption-review.json \
  --confirm-company 123 --account 1 --yes --no-input
```

The first command creates only a new private review file; existing files are never overwritten. The file contains the exact selection, portal and administration context, snapshot version, original intent and HTTP idempotency key. It contains no passwords, supplier keys or cached prices. The CLI displays the actual server review and rereads current quotas and prices from the portal before normal confirmation. The original `reviewed_at` keeps the same snapshot verifiable; a changed HMAC stops before confirmation. This optional time parameter supplies only another read check within the same ten-minute boundary and does not extend approval.

New Rust reviews use schema `binadit-workspace-adoption/v2` and retain the public personal `original_principal_id` from `/me`, using the same token as inspection. The final POST requires the same verifiable connection and uses the token checked at that moment. The original private review and exclusive durable dispatch marker are checked after authentication, immediately before dispatch and before displaying the result. The marker stores only fixed public intent metadata and the opaque server review version. It contains no free-form notes or review content. An ambiguous response retains this original request; a subsequent normal command stops before another supplier inspection.

A confirmed local receipt means the Workspace, agreements and tasks have been included atomically. It does not mean workers have finished. Rust shows this distinction through `local_adoption_confirmed`, `synchronization_status`, `synchronization_complete` and the safe original `operation`. Normal API confirmation may return `queued` or `running`. With `commit ... --wait --timeout 5m`, the CLI reads only that original operation until it finishes or needs attention. A timeout or held task does not remove the dispatch marker and does not start a new adoption.

`--confirm-company` is the actual target company, while `--account` remains the administration context. Automation requires both exact company confirmation and `--yes`. A preview with `commit ... --dry-run` performs current checks without adoption or a durable HTTP intent. `inspect ... --dry-run` also creates no review file and makes no claim of write-permission verification.

After an ambiguous response, the original file is retained. Investigate the original operation. Only an explicit recovery command repeats the same original request with the same selection and HTTP key:

```sh
binadit admin workspace adoptions commit --from-file adoption-review.json \
  --confirm-company 123 --recover --confirm-intent OORSPRONKELIJK-UUID \
  --account 1 --yes --no-input
```

This command can finish the originally approved adoption or return its earlier result. It selects no new price or intent and never runs automatically after a network error. Review a new selection in a new file when no original request has occurred yet. For Rust, recovery must also preserve the verifiable original personal connection. Ordinary token renewal does not change that principal; explicitly signing in again does. An old review with no recorded principal or a different active connection is not adopted for an original POST. Preserve the original review, UUID, key and operation for administrator investigation; do not change a stored principal or create a new adoption to bypass an unknown result. The general recovery limitation for other API functions remains separate from this adoption contract.

`--from-file -` reads trusted stdin. Windows requires input through stdin; the CLI makes no assumptions about the private ACL of an existing input path. The output file is created with the platform's existing private-file function. The [AI agent quickstart](/developers/guides/agents?lang=en) and [Workspace administration](/developers/guides/workspace-administration?lang=en) describe the other context, version and recovery rules.

## Sources and verification

The adapter uses the portal's existing Stalwart configuration and documented [Account objects](https://stalw.art/docs/ref/object/account/) and [Domain objects](https://stalw.art/docs/ref/object/domain/). Mandatory complete query counts, query state, exact object IDs and correlation tags are checked; an incomplete or unknown supplier response is refused. The repository also contains the original Stalwart 0.16 contract probe in the neighboring Workspace project; that mutating probe does not run automatically.

On 29 October 2026, a read-only probe on the existing configured production mail server confirmed this response shape: five fixed JMAP queries/gets with `limit:1`, `calculateTotal:true`, at most 2 MiB per response and no redirects. Query state, position, total count, exact GET identity and explicit role/permission fields had the required type. Output contained only booleans, types and page size; no keys, object IDs, names, addresses or supplier responses were displayed. This checks protocol compatibility and does not prove a completed production adoption.
