ADR 0006 — In-app enterprise support desk (tickets, SLA, email hooks, KB)
Status
Accepted — 2026-05-15
Context
The product needs a self-hosted support path (no external helpdesk as source of truth) with: threaded messages, agent workflow, SLA targets, optional outbound/inbound email, response macros, and a small knowledge base. Government operators must manage queues without broad data leaks; parents/drivers must only see their own tickets unless they are agents.
Decision
- Postgres data model (migrated via
ensureSchemainserver.ts):- Extend
support_ticketswithpriority,queue,assigned_user_id, SLA timestamps,last_activity_at. - Add
support_ticket_messages(public + internal),support_ticket_events(audit),support_sla_policies,support_macros,support_outbound_email_jobs,kb_categories,kb_articles.
- Extend
- Authorization — new permissions in
lib/authz.ts/src/authzClient.ts:feedback_support_agent— queue, PATCH ticket, internal notes, SLA policy API.feedback_support_macros_manage— macro CRUD.feedback_kb_admin— KB article CRUD.- Default government_operator receives agent + macros + KB admin; migration
INSERT … ON CONFLICT DO NOTHINGadds these for existing operators.
- API surface (Express in
server.ts):- Ticket detail
GET /api/feedback/support/:id(owner or agent);PATCH(reporter may only reopen/close own ticket);POST …/messages. - Agent queue
GET /api/feedback/support/queue(requires agent +feedback_support_read_all). - SLA CRUD under
/api/feedback/sla-policies. - Macros under
/api/feedback/support/macros. - KB under
/api/kb/*(authenticated read withvisible_rolesfilter). - Inbound email webhook
POST /api/webhooks/support-inboundwith raw JSON body andx-omnitrack-signature= HMAC-SHA256 hex over body usingSUPPORT_EMAIL_INBOUND_SECRET. Payload attributes customer replies to the ticket reporter (staff should use the app).
- Ticket detail
- Outbound email —
SUPPORT_SMTP_URL(orSMTP_URL) via nodemailer; jobs table +processSupportOutboundQueueon a 30s interval after HTTP listen. Message bodies stay plain text with server size caps. - Ticket attachments —
support_ticket_attachmentsstores up to 3 files per ticket (max 4 MB each) asBYTEAin Postgres; MIME allowlist JPEG, PNG, WebP, PDF.POST /api/feedback/supportaccepts optional JSONattachments[]with base64; download via authenticatedGET /api/feedback/support/:ticketId/attachments/:attachmentId/file(reporter or agent). For multi-tenant scale, plan object storage and signed URLs instead of largeBYTEAhot rows. - Reporter vs agent UI — Everyone with
feedback_support_createuses the Support nav tab to submit tickets (optional attachments, own ticket list, Help center/KB). Users withfeedback_support_agentandfeedback_support_read_allalso get sidebar Ticket desk for queue filters, full ticket table, macros, SLA summary, and GitHub export so handling stays on that dashboard.
Consequences
- Operators must be granted DB-backed permissions (not only UI);
governmentsuper-user still receives synthetic full permission list inuserJsonForClient. - Scaling outbound email requires a dedicated worker/queue later; the polling loop is acceptable for moderate volume.
PUBLIC_APP_URL/APP_ORIGINimproves email footers when set.
Related
docs/PLATFORM-MASTER.md§5 log..cursor/rules/overheid-operator-ux.mdc,feedback-product-github.mdc.