Status: Pre-launch
Epic: #651
Architecture doc: docs/architecture/support-raxx-app.md
Flag: FLAG_SUPPORT_PORTAL_API (default off)
Set these before flipping FLAG_SUPPORT_PORTAL_API=1. All values must be
silenced to avoid leaking credentials in Heroku's deploy output:
heroku config:set FREESCOUT_API_TOKEN=<value> >/dev/null 2>&1
heroku config:set FREESCOUT_BASE_URL=https://tickets.raxx.app >/dev/null 2>&1
heroku config:set FREESCOUT_SUPPORT_MAILBOX_ID=<value> >/dev/null 2>&1
SESSION_KEY is already required by #1028 (session auth middleware) and
does not need to be set again. Verify it exists:
heroku config:get SESSION_KEY | wc -c # should print > 1
| Config var | Source |
|---|---|
FREESCOUT_API_TOKEN |
Infisical /raxx/prod/freescout-api-token. See ADR-0046 for the secret path. |
FREESCOUT_BASE_URL |
https://tickets.raxx.app (fixed for prod). Use http://localhost:9999 in local dev only — the service auto-stubs this in FLASK_ENV=testing. |
FREESCOUT_SUPPORT_MAILBOX_ID |
Retrieve after #707 lands: GET https://tickets.raxx.app/api/mailboxes with Authorization: Bearer <FREESCOUT_API_TOKEN>. Use the id field of the "Support" mailbox. |
SESSION_KEY |
Already set (see #1028). |
Three migrations must be applied before the flag is flipped on:
backend_v2/db/migrations/005_support_customer_map.sql
backend_v2/db/migrations/006_support_audit_log.sql
backend_v2/db/migrations/007_support_pending_submissions.sql
The migration runner applies them in filename order. Confirm they ran:
SELECT version, applied_at FROM schema_migrations
WHERE version LIKE '00%support%'
ORDER BY version;
Flip the flag off immediately if issues are detected:
heroku config:set FLAG_SUPPORT_PORTAL_API=0 >/dev/null 2>&1
All /api/v1/support/* routes return 503 within one dyno restart. FreeScout
email intake (Postmark → FreeScout) is unaffected — customers can still submit
via email to support@raxx.app.
To fully roll back the schema (no customer data in these tables before GA):
DROP TABLE IF EXISTS support_pending_submissions;
DROP TABLE IF EXISTS support_audit_log;
DROP TABLE IF EXISTS support_customer_map;
When a customer requests erasure:
Raptor's GDPR DSR flow deletes the users row. The ON DELETE CASCADE
constraint on support_customer_map and support_pending_submissions
automatically removes all Raptor-side rows.
Manual step (operator): FreeScout conversations that contain PII in the
ticket body are NOT automatically deleted. The operator must:
a. Open tickets.raxx.app as an admin.
b. Search for all conversations with the customer's email.
c. Delete or anonymize each conversation within the GDPR 30-day window.
The support_audit_log rows for customer_id = <deleted_user_id> are
purged by the nightly GDPR DSR job (same job that handles the main audit_log).
Note: OQ-3 from the design doc is formally documented here. Attorney guidance on whether Raptor-automated FreeScout deletion is required is pending. Until that decision is made, this manual step is the DSR procedure.
A second FreeScout webhook endpoint is provisioned for support portal events:
POST /api/webhooks/freescout/support/raxx/prod/freescout-support-webhook-secretFREESCOUT_SUPPORT_WEBHOOK_SECRETThis endpoint is separate from the status-page webhook at /api/webhooks/freescout.
Configure it in FreeScout: Admin → Webhooks → Add webhook → URL:
https://api.raxx.app/api/webhooks/freescout/support
After flipping the flag on:
# 1. Get a valid session token (passkey flow)
SESSION_TOKEN="<bearer-token-from-POST-/api/sessions>"
# 2. Verify health endpoint returns 200
curl -s -H "Authorization: Bearer $SESSION_TOKEN" \
https://api.raxx.app/api/v1/support/health
# Expected: {"status": "ok"}
# 3. Verify session endpoint returns customer info
curl -s -H "Authorization: Bearer $SESSION_TOKEN" \
https://api.raxx.app/api/v1/support/session
# Expected: {"customer_id": "...", "freescout_linked": true|false}
support_customer_map row count should grow as customers authenticate.support_audit_log accumulates on each support request. Check for unexpected
error_code = 'privacy_violation' rows.support_pending_submissions rows with delivered_at IS NULL and
submitted_at < NOW() - INTERVAL '24h' indicate FreeScout was unreachable for
an extended period — investigate and notify affected customers.