Operations

Deploy

Ship to Cloudflare Workers, Vercel, or your own VPS.

#Production deploy

End-to-end checklist to deploy this repo from a clean slate.

#1. Provisioning

Service What you need
Convex A production deployment (convex.dev hosted or self-hosted)
AWS SES Production access (out of sandbox), verified sending domains
Stripe Account + products for each plan; webhook signing secret
Cloudflare Workers Account, wrangler configured
SMTP Any transactional provider (AWS SES, Postmark, Resend, etc.) for magic links + invites

#2. Generate secrets

Before writing any env vars, generate the secrets that aren't externally issued:

# ENCRYPTION_KEY - 32 random bytes hex (AES-256-GCM for BYOSES + webhook secrets)
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

# Convex Auth keys (print JWT_PRIVATE_KEY and JWKS; do not commit)
pnpm auth:keys

# TRACKING_SECRET - optional; falls back to ENCRYPTION_KEY if unset
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

Store these in your Convex deployment env (npx convex env set KEY value) and Cloudflare Worker secrets (wrangler secret put KEY). They must match.

#3. Environment variables

Populate .env.local using .env.example as a template. CI (scripts/check-env-example.mjs) enforces that .env.example stays in sync with any process.env.X reference in code - so this file is authoritative.

Runtime-required:

  • VITE_CONVEX_URL, VITE_CONVEX_SITE_URL
  • SITE_URL, SITE_NAME
  • JWT_PRIVATE_KEY, JWKS
  • AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SES_REGION, SES_CONFIGURATION_SET
  • ENCRYPTION_KEY
  • STRIPE_API_KEY, STRIPE_WEBHOOK_SECRET, STRIPE_PRICE_*
  • EMAIL_HOST, EMAIL_USER, EMAIL_PASS, EMAIL_FROM (SMTP for magic links + invites)

Optional:

  • EMAIL_PORT (default 587; set to 465 for implicit TLS providers)
  • TRACKING_SECRET (falls back to ENCRYPTION_KEY)
  • ENCRYPTION_KEY_PREVIOUS (only set during key rotation - see key-rotation)
  • GOOGLE_CLIENT_*, GITHUB_CLIENT_* (OAuth)

#4. Auth keys

pnpm install
pnpm auth:keys
# set JWT_PRIVATE_KEY and JWKS on the Convex deployment

#5. Deploy Convex

npx convex deploy            # pushes schema + functions to production

On first deploy, ensure the SES Configuration Set named in SES_CONFIGURATION_SET exists and has an SNS event destination pointing at <VITE_CONVEX_SITE_URL>/api/webhooks/ses. The in-app SES Setup page does this automatically once an AWS account is connected.

#6. Deploy Cloudflare Worker

pnpm build
pnpm deploy                  # wrangler deploy

#7. Configure external webhooks

#Stripe

In Stripe dashboard → Developers → Webhooks:

  • Endpoint: https://<your-site>/api/webhooks/stripe
  • Events: checkout.session.completed, charge.refunded, payment_intent.payment_failed
  • Copy signing secret → STRIPE_WEBHOOK_SECRET

#SES via SNS

Handled by the in-app SES Setup flow (Settings → AWS SES). It creates the Configuration Set, SNS topic, and SNS subscription automatically.

#8. Post-deploy verification

curl https://<your-site>/api/health
# → {"status":"ok","checks":{"convexDb":"ok","postgres":"ok"},...}

In the app:

  1. Sign in with magic link (check inbox).
  2. Create a project.
  3. Add a domain; verify DKIM.
  4. Send a test email to yourself.
  5. Check /app/emails shows delivered + the email arrives.
  6. Check /app/analytics reflects the event.
  7. Trigger a bounce (send to bounce@simulator.amazonses.com) and confirm the suppression appears in Settings → Activity within seconds.

#9. Monitoring

  • /api/health - readiness probe for your load balancer / uptime monitor
  • Convex dashboard → Logs + Insights for query/mutation perf
  • Cloudflare → Analytics for edge latency + error rates
  • AWS SES Reputation Dashboard - bounce/complaint rate (also surfaced in-app)

See slos.md for recommended alert thresholds.

#10. Backups

Convex hosted snapshots are automatic. For self-hosted or Postgres, see disaster-recovery.md.