--- id: USER-WP-ADHOC-2026-10-09 type: workplan title: "Scoped invitation mail client bindings" domain: communication repo: user-engine status: finished owner: claude created: "2026-10-09" updated: "2026-10-09" state_hub_workstream_id: "88817a88-4415-5400-be9a-939406e77831" --- ## Send an admitted tenant/client pair on invitation mail ```task id: USER-WP-ADHOC-2026-10-09-T01 status: done priority: medium state_hub_task_id: "d62b2f0e-7b62-5bf2-945d-7d8a9f51b002" ``` User-engine side of email-connect EMAIL-WP-0008 (caller policy draft 2026-10-08): current invitation envelopes omit `client_id`, so a scoped user-engine caller cannot be admitted. Opt-in `USER_ENGINE_MAIL_CLIENT_BINDINGS` maps tenant globs to one literal client id. Files: - `src/user_engine/mail_bindings.py` — strict loader and single-match resolver - `src/user_engine/adapters/delivery.py` — adds `client_id` to the mail envelope only; denial contacts neither lane - `src/user_engine/runtime.py` — reads the env var - `src/user_engine/service.py`, `src/user_engine/web.py` — keep `mail_binding_denied` as the recorded failure reason with an operator status line instead of collapsing it to `delivery_unavailable` - `docs/configuration.md`, `tests/test_mail_bindings.py`, `tests/test_service_operations.py` Acceptance: unset keeps legacy envelopes; invalid config stops startup; unmatched or conflicting tenants are denied; platform events and outbox payloads never carry `client_id`; `make test` and `make check-layer` pass. Not done here: activation. Deploy config, email-connect caller admission (EMAIL-WP-0008-T03) and token custody stay with their owners and the founder. ## Draft the deployment binding value ```task id: USER-WP-ADHOC-2026-10-09-T02 status: done priority: medium ``` Draft only, not applied. When email-connect admits the user-engine caller (EMAIL-WP-0008-T03), add this to the user-engine container env in `net-kingdom/sso-mfa/k8s/user-engine/runtime.yaml`, next to `USER_ENGINE_MAIL_URL`: ```yaml - name: USER_ENGINE_MAIL_CLIENT_BINDINGS value: '[["tenant:coulomb","coulomb-social"],["tenant:*-family","coulomb-social"],["tenant:family-*","coulomb-social"]]' ``` It mirrors `bindings` plus `binding_patterns` in email-connect `config/callers.user-engine.draft.json` one-to-one. Both sides match with case-sensitive whole-string `fnmatchcase`, so user-engine sends only pairs the receiver admits. Checked with the loader: `tenant:coulomb`, `tenant:worsch-family` and `tenant:family-*` resolve to `coulomb-social`; `tenant:platform` is denied before either lane. Order matters. Apply the env var in the same rollout that switches `USER_ENGINE_MAIL_TOKEN` to the scoped caller token. Before then the receiver runs legacy mode, so leave the variable unset. If either side's patterns change, change the other in the same review.