Raxx · internal docs

internal · gated ↑ index

Support Portal (support.raxx.app) — Operations Runbook

Status: Pre-launch
Epic: #651
Architecture doc: docs/architecture/support-raxx-app.md
Flag: FLAG_SUPPORT_PORTAL_API (default off)


Required Heroku config vars

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

Where to find each value

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).

Migrations

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;

Rollback

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;

GDPR / DSR procedure (Open Question OQ-3)

When a customer requests erasure:

  1. 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.

  2. 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.

  3. 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.


FreeScout webhook (support portal events)

A second FreeScout webhook endpoint is provisioned for support portal events:

This 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


Smoke test

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}

Monitoring