feat(inbox-ui): Settings → Inbox → Providers from each provider plugin's configSchema (G-P4-2)

Merged
EV
9d ago
+3465-65

What

G-P4-2 (UI): Settings → Inbox → Providers renders each provider plugin's configSchema — plan 06-gauzy-implementation-plan.md G-P4-2, 04-inbox-and-email.md §5.2 (Gauzy ↔ Ever Works integration, ever-co/homelab docs/superpowers/specs/2026-10-15-gauzy-ever-works-integration/). Part 1 of 2; Mailboxes + My mailbox (G-P4-1 UI) follow in the next PR.

@gauzy/plugin-inbox-ui

  • /pages/settings/inbox-providers (a settings-sections child of the settings shell, INBOX_MAILBOX_MANAGE): the provider plugins this server loads (capabilities, "Add account") and the accounts of the selected organization and of the tenant.
  • Generic JSON-schema → Nebular form (InboxProviderConfigFormComponent + pure helpers): text (email / URL / host name), multi-line text, number (min / max / integer), on/off, enum, required marks, the plugin's titles and help texts; the same checks as the API's splitProviderConfig.
  • Secrets are write-only: a stored secret is never put back into the form (the API answers only secretsSet, the card says "Set"); left empty it is kept, typed it replaces, an optional one can be cleared, a generated one (Postmark's webhook credential) is made by the server and shown once, in a dialog that only an acknowledgement closes (closeOnEsc / backdrop off); one-time values are never kept on the page.
  • Create / edit (an edit sends config only when it changed, so a rename never touches the sealed secrets), Test connection (verifyAccount), enable / disable, make outbound default, delete (the API's 409 "a mailbox receives through it" is shown). The webhook URL shown is the API's answer (webhookUrl, built on the API's own public URL) — no host in the UI. A tenant-wide account is read-only without ALL_ORG_EDIT.
  • Gating: the Settings → Inbox nav entries are added only where the API serves the Inbox (the same InboxAvailabilityService check as the nav entry and badge) and its email features (InboxEmailAvailabilityService: GET /api/inbox/mailboxes/mine once per user; 404/401/403 final, temporary failures retried) and hidden again after a user switch; the route always exists and says "email is not switched on" on a 404. Plugin-owned i18n (INBOX.SETTINGS.*), OnPush + signals, takeUntilDestroyed.

@gauzy/plugin-inbox (additive, for the UI)

  • DELETE /api/inbox/provider-accounts/:id — soft (kept, marked deleted, disabled, no longer the outbound default), under the row lock; 409 while a mailbox receives through the account; tenant-wide accounts need ALL_ORG_EDIT as for every write.
  • GET /api/inbox/mailboxes/:id/verification — a custom domain's TXT record and last status, no DNS lookup.
  • GET/PUT /api/inbox/mailboxes/settings also answer platformDomain (GAUZY_INBOX_DOMAIN or null).
  • Contracts: IInboxProviderAccountRemoved, IInboxOrganizationEmailSettings.platformDomain?. EMAIL.md routes updated. tools/egress-audit/ui-routes.json: the new manual route.

Checks (no local test runs, per the overnight rules — specs written, not run)

  • tsc strict (packages/plugins/inbox/tsconfig.lib.json + tsconfig.spec.json, workspace path map) 0 errors in plugin-inbox; loose (root tsconfig.json, what the API image compiles) 0 in plugin-inbox (7 pre-existing in core/bootstrap/tracer.ts, shared node_modules).
  • inbox-ui: tsc (sources + specs) 0 errors in the plugin; ngc with strictTemplates over the plugin's sources: 0 template errors in the new components (verified the check runs by planting an error).
  • prettier, cspell (20 files, 0 issues), tools/egress-audit/static-hostnames.mjs ok; the diff names no Ever host.

🤖 Generated with Claude Code


Summary by cubic

Adds Settings → Inbox → Providers, which renders each provider plugin's configSchema as a generic Nebular form and manages the email provider accounts of the selected organization and tenant.

New Features

  • Lists provider plugins with their capabilities; creates, edits, tests, enables/disables, sets outbound default, and deletes accounts (a delete is refused with 409 while a mailbox receives through the account).
  • Secrets are write-only: stored values never go into the form, and generated ones are shown once in a dialog only an acknowledgement closes — it ignores Escape and the backdrop so a save in flight reaches its one-time values.
  • The Settings → Inbox nav entries appear only where the API serves the Inbox email features; the routes always exist and say email is off when they are not.
  • Tenant-wide accounts are read-only without ALL_ORG_EDIT; edits send config only when changed so renaming never touches sealed secrets.
  • Account removal and pointing a mailbox at an account share the account's lock, so removing and attaching at once can never both succeed; refreshes answered for a no-longer-selected organization are dropped.

API additions

  • DELETE /api/inbox/provider-accounts/:id — soft delete, refused with 409 while a mailbox receives through the account.
  • GET /api/inbox/mailboxes/:id/verification — the custom-domain TXT record and last status, no DNS lookup.
  • Mailbox settings answer platformDomain (GAUZY_INBOX_DOMAIN or null).