Environment variables
Every env var by group, where it lives (Convex deployment vs Vercel), and the dev-fallback principle that lets most integrations run unset.
This page lists variable names and purpose only — never values. Do not commit
real keys; per-feature runbooks (SETUP.md, DEPLOY.md, docs/billing-setup.md,
docs/email-setup.md, docs/domain-system-plan.md) own the full setup steps.
Where vars live
Two homes:
- Vercel /
.env.local— frontend and build vars (anythingNEXT_PUBLIC_*, plus the Clerk client/server keys read by the Next.js app and middleware). - The Convex deployment — server-only secrets used inside Convex actions (AI, Stripe, Resend, the Vercel domains API). Set them with:
npx convex env set NAME value # dev
npx convex env set NAME value --prod # production
CLERK_JWT_ISSUER_DOMAINis the one var that must be set in both places — the Next.js app and the Convex deployment — because Convex validates theconvexJWT template independently. See Auth & permissions.
The dev-fallback principle
Most integrations run in dev / mock mode until their credentials are set, so the whole app (and CI) works locally without secrets:
- No
OPENROUTER_API_KEY→ AI is disabled; the deterministic generator and the rest of the product still work fully. - No
RESEND_API_KEY→ transactional email is silently skipped (invites fall back to copy-link). - No Stripe keys → billing and payments are gated off (dev simulate paths).
- No
VERCEL_API_TOKEN/VERCEL_PROJECT_ID→ the domain connect/buy flow returns deterministic "connected" results (dev fallback), testable without credentials. - No
NEXT_PUBLIC_SITES_DOMAIN→ platform subdomains are dormant; sites stay reachable at the/site/<slug>path.
Core (Convex client)
| Var | Where | Purpose |
|---|---|---|
CONVEX_DEPLOYMENT | auto (convex dev) | Selects the dev deployment |
NEXT_PUBLIC_CONVEX_URL | auto (convex dev / deploy) | Convex client URL |
NEXT_PUBLIC_CONVEX_SITE_URL | optional | Override; otherwise derived from NEXT_PUBLIC_CONVEX_URL (lib/site/convexSiteUrl.ts) |
NEXT_PUBLIC_APP_URL | Vercel / .env.local (+ Convex for domains) | The app's own base URL |
NEXT_PUBLIC_APP_HOST | optional | The app's own host so middleware skips custom-domain lookups for it |
CONVEX_DEPLOY_KEY | Vercel (production build only) | Lets the Vercel build run convex deploy |
Clerk (auth — required)
| Var | Where | Purpose |
|---|---|---|
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY | Vercel / .env.local | Clerk client key |
CLERK_SECRET_KEY | Vercel / .env.local (secret) | Clerk server key |
CLERK_JWT_ISSUER_DOMAIN | .env.local and the Convex deployment | Issuer of the convex JWT template |
AI — OpenRouter (optional; on the Convex deployment)
OPENROUTER_API_KEY powers all AI (generation/polish, the chat assistant,
vision, dictation). The model overrides are all optional — defaults live in
convex/generation/models.ts.
| Var | Purpose |
|---|---|
OPENROUTER_API_KEY | The single key for all AI (server-side only) |
OPENROUTER_MODEL_HEAVY | Generation / FAQ / offers tier override |
OPENROUTER_MODEL_AGENT | AI chat "smart" override (default = heavy) |
OPENROUTER_MODEL_FAST | AI chat "fast" override (default = small) |
OPENROUTER_MODEL_SMALL | Single-field rewrite override |
OPENROUTER_MODEL_TINY | SEO meta / brief extraction override |
OPENROUTER_MODEL_SHORTEN | Optional override for the shorten action |
OPENROUTER_VISION_MODEL | Photo placement + chat image vision override |
OPENROUTER_STT_MODEL | Voice dictation (Whisper) override |
OPENROUTER_IMAGE_MODEL_PRO / _FREE | Paid / free image model override |
TAVILY_API_KEY | Enables the chat's read-only webSearch tool |
See the AI system.
Email — Resend (optional; on the Convex deployment)
| Var | Purpose |
|---|---|
RESEND_API_KEY | Enables transactional email (invites, lead + booking notifications) |
RESEND_FROM | The "from" identity for sent mail |
No key = email is silently skipped (docs/email-setup.md).
Billing & payments — Stripe (optional; on the Convex deployment)
| Var | Purpose |
|---|---|
STRIPE_SECRET_KEY | Stripe API key (billing + Connect payments) |
STRIPE_BILLING_WEBHOOK_SECRET | Verifies /api/stripe/billing-webhook |
STRIPE_CONNECT_WEBHOOK_SECRET | Verifies /api/stripe/connect-webhook (online card pay) |
STRIPE_WEBHOOK_SECRET | Verifies /api/stripe/domain-webhook (domain purchase) |
PAYMENTS_ALLOW_DEV_SIMULATE | Dev-only: simulate a successful payment (never in prod) |
Price ids and the operator runbook live in docs/billing-setup.md.
Domains & hosting — Vercel (optional)
| Var | Where | Purpose |
|---|---|---|
DOMAIN_PROVIDER | Convex | vercel to use the real registrar; otherwise the dev mock |
VERCEL_API_TOKEN | Convex | Connect / buy domains via the Vercel API |
VERCEL_PROJECT_ID | Convex | Target Vercel project for domain attach |
VERCEL_TEAM_ID | Convex | Vercel team scope |
NEXT_PUBLIC_SITES_DOMAIN | Vercel / .env.local | Subdomain zone for published sites (e.g. the platform domain) |
VERCEL_ENV | auto (Vercel) | Set to production only on the prod deployment; gates the landing lab |
See Deployment and
docs/domain-system-plan.md. No domain secrets are ever stored in Convex tables.
Misc & native shells (optional)
| Var | Purpose |
|---|---|
NEXT_PUBLIC_SUPPORT_EMAIL | Public support address shown in the UI |
NEXT_PUBLIC_DESKTOP_DMG_URL | Download link shown on /download |
SAJT_DESKTOP_URL | Web app the Tauri desktop shell loads |
APPLE_TEAM_ID / IOS_BUNDLE_ID | iOS build-time identifiers |
AI system
The one rule (validate before write), the OpenRouter-only provider, model tiers, generation flow, the chat assistant loop, and anti-fabrication guardrails.
Deployment
How Sajt deploys to Vercel + Convex, the post-deploy auth check, going live on gated integrations, and the dev-only routes that 404 in production.