1. Legal and GDPR
- Company info — fill
LEGAL_COMPANY_NAME,LEGAL_ADDRESS, andLEGAL_REGISTRATION_NUMBERin.env.local(and later in your production env). They render on legal pages and in invoices. - Legal pages — log in as admin, open
/admin-dashboard/cms, and finalize/terms,/privacy,/legal. Also review the built-in/privacy-choicespage so the public rights/opt-out copy matches your actual processors and advertising setup. - Cookie consent — the banner is wired by category (necessary / analytics / marketing). Verify the categories match what you actually load, and test that Global Privacy Control keeps marketing/targeted-advertising consent disabled.
- Account deletion — review the supported account types, ownership/dependency checks and terminal-erasure limitations in Data Privacy & GDPR. Make sure
process-account-deletionsand its scheduler/worker are operating; an enabled job alone is not proof of complete erasure.
2. Production env vars
Two categories — they live differently:
| Type | Examples | Where to set | To change later |
|---|---|---|---|
| Build-time | NEXT_PUBLIC_*, including the selected provider's public browser token when required | Set before pnpm run build — they are baked into the JS bundle | Requires a new build/deploy |
| Runtime | PAYMENTS_PROVIDER, the selected provider's API/webhook secrets, SUPABASE_SECRET_KEY, AI_LLM_TRANSPORT, selected AI credential | Set on the server / container env | Restart the container; no rebuild needed |
3. Deploy
Before publishing, run pnpm run qa:release and resolve its blockers. It checks
Database/Auth certification separately from application startup and Docker
builds. Follow Deployment for the full release gate.
Pick a deployment target:
4. Production payment provider and webhooks
Keep the provider selected during initialization. Do not switch an existing installation in place: persisted subscriptions, transactions, adjustments, customer bindings, and reconciliation retain their original provider identity. Stripe, Lemon Squeezy, and Paddle implement member checkout and customer portal locally. Production remains fail-closed until the selected installation has coherent mode-specific credentials, a complete catalogue, the exact webhook, all required local capabilities, and the required operator attestations. Paddle guest checkout remains intentionally unavailable.
- Confirm that production keeps the same
PAYMENTS_PROVIDERselected during initialization. Copy only that provider's production credentials and catalogue bindings; never expose the selection or external catalogue identifiers to the browser. - Create the provider's webhook endpoint at the exact query-free route listed below and configure the required event set documented in Payments & Billing.
- When Stripe is selected, explicitly pin API version
2026-07-29.dahlia. When Paddle is selected, configure its approved Default Payment Link. Lemon Squeezy and Paddle require their exact paid-customer portal attestations collected in sandbox. - Copy the endpoint signing secret into the matching server-only variable from
.env.example, restart, then runpnpm run check:payment-catalog. Runpnpm run test:stagingonly against the explicitly approved non-indexable test/sandbox origin; this workflow never sends Lemon Squeezy or Paddle readiness traffic with live credentials.
| Selected provider | Production webhook route |
|---|---|
| Stripe | /api/billing/webhooks/stripe |
| Lemon Squeezy | /api/billing/webhooks/lemon-squeezy |
| Paddle | /api/billing/webhooks/paddle |
Webhook-secret rotation uses the selected provider's *_WEBHOOK_SECRET_PREVIOUS key only during the bounded cutover. The readiness probe rejects query strings and fragments. After signed deliveries succeed on the new endpoint, disable the old endpoint, remove the previous secret, and restart.
5. Observability
- Error logs — set
LOGS_ENABLED=true, generate a strongLOGS_SALT, pick aLOGS_RETENTION_DAYS. Logs surface in/admin-dashboard/logs. - Rate limiting and AI resilience — create an Upstash Redis database and paste
UPSTASH_REDIS_REST_URL+UPSTASH_REDIS_REST_TOKEN. Production uses it for distributed limits and shared LLM closed/open/half-open circuit state; the app refuses to boot without the shared rate-limit store unless the explicit single-instance override is enabled. - Scheduler runtime — for
supabase_pg_cron, setapp_settings.jobs_api_urlto the public production endpoint. Forpostgres_pg_cron, no URL or HTTP secret is used; supervisepnpm jobs:workerbeside the application. Forexternal_runner, configure the runner to call the public endpoint with its Bearer secret. - Indexing — set
NEXT_PUBLIC_INDEXABLE=trueonly after legal pages are real and placeholder text is gone.
6. Final smoke tests
Walk through these on the live URL before announcing the launch:
- Landing page renders, no console errors, branding correct
GET /api/healthreturns 200- Magic link arrives within 30 seconds in a real inbox
- Admin can reach
/admin-dashboard - The selected provider's test/sandbox checkout, signed webhook convergence, and customer portal were validated on the approved non-indexable environment before production promotion; Lemon Squeezy/Paddle require no live purchase in this workflow
- A chat message streams from the LLM and credits decrement
- Cookie banner appears for new visitors and respects choices
- Footer links expose
/privacy,/privacy-choices,/terms,/legal, and Cookie settings - Legal pages have your real company info, not placeholders
- All smoke tests above pass for their documented production or approved sandbox target
- The selected provider's signed webhooks deliver successfully (check its dashboard log)
- Scheduled jobs ran at least once (check
job_runs) - You sent a magic link to a teammate and they got in
Where to next?
The onboarding is done. From here, dive into specific features as you need them:
- Multi-Tenancy (B2C vs B2B) — workspaces, roles, invitations
- AI Integration — agents, RAG, prompt caching
- Background Jobs — cron, webhooks, retries
- Security — OWASP checklist, CSP, headers
- Full category index — everything else