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_URLSITE_URL,SITE_NAMEJWT_PRIVATE_KEY,JWKSAWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_SES_REGION,SES_CONFIGURATION_SETENCRYPTION_KEYSTRIPE_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 toENCRYPTION_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:
- Sign in with magic link (check inbox).
- Create a project.
- Add a domain; verify DKIM.
- Send a test email to yourself.
- Check
/app/emailsshows delivered + the email arrives. - Check
/app/analyticsreflects the event. - 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.