On this page
This page is for the person who hosts the site, or for your AI agent. Most owners never need it.
Caution. Wrong environment values can take the site offline or send email from the wrong address. Back up first. Do not paste secret keys into chat logs. Ask your agent to do this with you.
Who should do this
- Turning on a domain
- Setting environment variables on Vercel (or another host)
- First-time Supabase, Resend, or Twilio setup
Owners still click merge. The agent should not dispatch production database jobs unless you say so.
Where the site runs
Vercel is the usual host. Merging to main deploys production. There are no pull-request preview deploys.
The app also runs on any host that can run Node 24 with pnpm build and pnpm start. Details for Docker and other hosts live in the internal docs/HOSTING.md in the product repo. The environment names are the same everywhere.
Health check: GET /api/health returns {"ok":true}.
Environment variables
Set these on the host (Vercel → Environment Variables). Names:
| Name | What it is |
|---|---|
NEXT_PUBLIC_SITE_URL | Public address, such as https://www.example.com. Baked in at build time. |
NEXT_PUBLIC_SUPABASE_URL | Supabase project URL. Build time. |
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY | Supabase publishable key. Build time. |
SUPABASE_SECRET_KEY | Supabase secret key. Runtime. |
NPM_TOKEN | GitHub token with read:packages, so the build can install @xylo/core. Never commit it. |
RESEND_API_KEY | Email. |
EMAIL_SENDER | From-address, such as Brightside Studio <hello@example.com>. |
STAFF_NOTIFY_EMAILS | Who hears about new leads, comma-separated. |
STAFF_NOTIFY_PHONES | Staff mobiles in E.164 (+1…), comma-separated. |
NEXT_PUBLIC_SMS_ENABLED | true only after Twilio is approved. Build time. |
TWILIO_ACCOUNT_SID / TWILIO_AUTH_TOKEN | Texting. |
TWILIO_MESSAGING_SERVICE_SID | MG…, preferred. |
TWILIO_FROM_NUMBER | Only if you are not using a messaging service. |
CRON_SECRET | Random, 32+ characters. Used by the follow-up job. |
STRIPE_SECRET_KEY | sk_test_… or sk_live_…. See Stripe setup. |
STRIPE_WEBHOOK_SECRET | whsec_…. |
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY | pk_…. Build time. |
NEXT_PUBLIC_CAPTCHA_SITE_KEY / CAPTCHA_SECRET_TOKEN | Optional Turnstile, only if spam gets through. |
CLIENT_IP_HEADER | Optional. Host’s visitor-IP header. |
XYLO_PUBLIC_SUPABASE_URL | Optional. Only if phones cannot use the public Supabase URL. |
POWERSYNC_URL | Optional. Only if the native app syncs offline. |
Leave Twilio and Stripe unset on Preview, so preview URLs never text real people or take real money.
NEXT_PUBLIC_* values are baked in at build time. Changing one means rebuild (a new deploy).
What a merge does
- You merge the pull request to
mainwith a merge commit. - Vercel builds and deploys.
- If the pull request added files under
supabase/migrations/, migrations start on their own: backup, rehearse, then apply.
Code can go live a minute before migrations finish. New code is written to work with the old schema for that window.
First-time launch extras
The full launch runbook is internal (docs/LAUNCH.md). In short: create the owner’s Supabase project, turn off public sign-up, set site URL and /auth/confirm, paste the email templates, add Resend, then deploy. Invite the first owner with pnpm owner:invite. Everyone else is invited from admin → Staff.
Follow-up job
Supabase pg_cron calls /api/cron/nurture every 15 minutes with CRON_SECRET. Vercel also calls it once a day as a fallback. Backups do not run on Vercel. They run in GitHub Actions. See Backups and database changes.