Raxx · internal docs

internal · gated

ADR 0141 — Support Portal + CRM Surface Topology

Status: Accepted
Date: 2026-07-17 UTC
Deciders: Kristerpher Henderson (operator), software-architect
Scope: support.raxx.app, console.raxx.app (CRM section), Raptor API, freescout_ticket_cache


Context

The operator decided on 2026-07-14 (#4250) that console.raxx.app would serve as the customer CRM. This was in tension with #651, which envisions a standalone support.raxx.app SPA as the customer-facing support portal. Both decisions were in flight simultaneously, with implementation cards blocked waiting for this reconciliation.

Two prior sub-cards complicated matters: - #652 (data model + API contract spec) was deferred and never written — its content is now folded into docs/architecture/support-raxx-app.md and this ADR. - #3286 (SC-A4 audit webhook) would populate freescout_ticket_cache, but that table's role was ambiguous — audit side-table or CRM primary store?

This ADR resolves the surface ownership question and the freescout_ticket_cache role, unblocking all downstream cards.


Decision

Both surfaces ship, serving different audiences.

support.raxx.app is the customer-facing self-service portal. Customers sign in with their Raxx passkey, see only their own tickets, and never encounter FreeScout by name. This is the surface #651 describes, and it remains the correct customer experience.

console.raxx.app gains a CRM section (/customers/:id/support) where operators view any customer's ticket history, see Raxx account context alongside support context, and link out to FreeScout for full ticket management. "Customer CRM" in #4250 refers to a tool the Raxx team uses to manage customers — not a portal customers log into.

freescout_ticket_cache is promoted from audit side-table to first-class CRM data store. It is populated by the FreeScout webhook (push, via #3286) and by a pull-on-demand hydration path at CRM customer-profile load time (for historical tickets that predate webhook registration). The customer-facing portal reads live from FreeScout on demand and does not use this cache.

The full data model, API contract, and migration spec live in docs/architecture/support-portal-crm-reconciliation.md.


Language choice rationale

This ADR does not introduce a new service. All new code runs within Raptor (Python, Tier 2) and the existing console.raxx.app (Antlers/Next.js). No language-tier classification is required.


Consequences

Positive

Negative / risks

Neutral


Alternatives considered

Alternative A: support.raxx.app only; no CRM section in console.raxx.app

Rejected because: the 2026-07-14 operator decision (#4250) explicitly named console.raxx.app as the customer CRM. Ignoring that direction would require re-re-deciding it and would leave operators without a unified customer view.

Alternative B: console.raxx.app absorbs support.raxx.app; customers log into console

Rejected because: console.raxx.app is operator-authed (console RBAC, SAML-ready). Mixing customer auth with operator auth on the same surface violates the privacy boundary: a misconfigured RBAC row could expose one customer's tickets to another. The two audiences need separate auth contexts and separate surfaces.

Alternative C: freescout_ticket_cache stays audit-only; CRM queries FreeScout live

Rejected because: live FreeScout API queries at every CRM customer-profile load would create rate-limit pressure and latency spikes when FreeScout is slow or unreachable. The cache gives the CRM a fast, stable read path. Customer portal remains unaffected (still reads live).


Security / GDPR checklist


Revisit when