# Wiring Threema relay into the portal Drop-in files in this archive: ``` src/lib/packages.ts # add 'threema' to catalog + customProvisioning flag src/lib/threema-relay.ts # new — admin API client src/app/api/tenants/[name]/threema/route.ts # new — POST provision / DELETE deprovision src/app/api/tenants/[name]/threema/routes/route.ts # new — atomic add/remove of a single Threema ID src/components/channel-users/channel-users.tsx # branch threema through relay-managed endpoint src/components/packages/package-card.tsx # handle customProvisioning enable/disable deploy/patch-i18n-threema.mjs # idempotent i18n key injection ``` ## Manual steps after dropping in 1. `.env` (and `.env.example`) — add: ``` THREEMA_RELAY_URL=http://pieced-threema-gateway.threema-gateway.svc:8080 THREEMA_RELAY_ADMIN_TOKEN=__from_openbao__ ``` The portal pod's OpenBao client should also read `secret/data/threema-gateway/admin` and surface `token` as this env var (existing ESO pattern in the portal's Helm chart). 2. Patch the message files (one-time): ```bash node deploy/patch-i18n-threema.mjs ``` 3. Re-export `CHANNEL_PACKAGE_IDS` is unchanged in source; verify `tenants/[name]/page.tsx` still derives the enabled-channels list from it — it should now include `threema` automatically once a tenant has it in `spec.packages`. 4. Type-check: ```bash npx tsc --noEmit ``` ## Flow summary ### Enabling Threema for a tenant 1. Customer toggles the threema package card. 2. PackageCard sees `customProvisioning: true` → POSTs `/api/tenants//threema`. 3. Handler calls relay `POST /admin/tokens` → gets `{token, hmacSecret}`. 4. Handler writes them to OpenBao at `secret/data/tenants/tenant-/threema-relay`. 5. PackageCard then PATCHes `tenant.spec.packages` to include `threema`. 6. Operator reconciles: ExternalSecret syncs OpenBao → Secret; OpenClaw pod restarts with `THREEMA_RELAY_*` env vars; plugin registers `threema` channel. ### Customer adds a Threema ID 1. UI calls `POST /api/tenants//threema/routes` with `{threemaId}`. 2. Handler calls relay `POST /admin/routes` (uniqueness enforced at PK). 3. On 201 or 409-from-same-tenant: handler patches K8s `spec.channelUsers.threema`. 4. On 409-from-other-tenant: 409 to client with explanation. 5. On K8s patch failure after relay success: handler compensates by `DELETE /admin/routes/...` at the relay. ### Customer removes a Threema ID 1. UI calls `DELETE /api/tenants//threema/routes?threemaId=...`. 2. Handler patches K8s `spec.channelUsers.threema` to drop the ID. 3. Handler calls relay `DELETE /admin/routes/...` (404 = idempotent OK). 4. If relay drop fails: K8s already updated, surface warning but treat as success — relay deletes are idempotent on retry. ### Disabling Threema for a tenant 1. Customer disables the threema card. 2. PackageCard DELETEs `/api/tenants//threema`. 3. Handler calls relay `DELETE /admin/tokens/` (cascades to all routes for this tenant). 4. Handler deletes OpenBao secret at `secret/data/tenants/tenant-/threema-relay`. 5. PackageCard then PATCHes `tenant.spec.packages` to drop `threema`. 6. Operator reconciles: ExternalSecret targets a missing OpenBao path → Secret deleted → OpenClaw pod restarts without `threema` channel. There's a small window (between step 4 and the operator's reconcile) where the pod still thinks it has a relay token but the relay has revoked it. Outbound during that window returns 401 from the relay; inbound is blackholed at the relay because routes are gone. Both are graceful failures.