Operations & runbook
Deploys, DNS, and how to debug the failures we have actually hit.
Deploys
| Target | Trigger | Notes |
|---|---|---|
| Vercel (frontend) | Push to main, or vercel --prod | Framework pinned to nextjs in vercel.json — auto-detect wrongly picks 'services' and fails the build |
| Railway (backend) | railway up from backend/ | Not GitHub-connected — deploys are local uploads |
Railway is deployed by upload, not from git
A commit on main does not deploy the backend. Someone must run railway up. Connecting the repo would remove that footgun.
Env var changes also need a redeploy — the running container keeps its old environment. /health is the fastest way to confirm what is actually loaded.
DNS
| Type | Name | Value | Proxy |
|---|---|---|---|
| A | @ | 76.76.21.21 | DNS only |
| A | www | 76.76.21.21 | DNS only |
| CNAME | api | 1zz3qufy.up.railway.app | DNS only |
| CNAME | developer | cname.vercel-dns.com | DNS only |
Cloudflare's orange-cloud proxy in front of Vercel or Railway breaks their TLS provisioning — all four must be DNS only.
api. is registered on Railway but not in DNS yet
The custom domain exists on the Railway service, but the CNAME has not been added, so api.ingredientchecker.app does not resolve. Until it does, use the Railway hostname directly:
https://ingredient-checker-api-production.up.railway.appEvery api.ingredientchecker.app URL in these docs requires that record. That includes the MCP connector URL — adding the record later means updating the connector in ChatGPT too, so it is worth doing before wiring anything permanent to the Railway hostname.
Health check
curl -s https://api.ingredientchecker.app/health | jq{
"status": "ok",
"openai": true,
"supabase": true,
"telnyx": {
"api_key": true,
"phone_number": "+14015988402",
"messaging_profile_id": true,
"signature_verification": true
}
}These reflect genuine configuration — placeholders read as false.
Runbook: someone texted and got nothing
Run railway logs --service ingredient-checker-api and read for the first missing line:
| Symptom | Meaning | Fix |
|---|---|---|
No [telnyx] inbound | Telnyx never reached us | Check the webhook URL on the messaging profile |
signature verification failed | Public key does not match that profile | Re-copy TELNYX_PUBLIC_KEY, redeploy |
inbound but no replied | Agent or send threw | See [telnyx] handler error |
replied but nothing arrives | Carrier dropped it — usually 10DLC | Check the delivery receipt line, then campaign assignment |
delivery … status=delivery_failed | Carrier rejection with an error code | Look the code up in Telnyx docs |
A 2xx from Telnyx is not delivery
It means Telnyx accepted the message. We shipped for a while with logs saying replied for messages nobody received — the number was not assigned to a 10DLC campaign and carriers dropped every one. Trust the delivery line.
10DLC
Registration is not enough — the number must be assigned to a campaign, and carrier mapping propagates independently:
curl -sg -H "Authorization: Bearer $TELNYX_API_KEY" \
https://api.telnyx.com/v2/10dlc/phoneNumberCampaignassignment : PENDING_ASSIGNMENT | ASSIGNED
T-Mobile : PENDING | ADDED
AT&T : null | ADDED
other : ADDED ← Verizon etc. often land firstMapping takes hours to a day. Partial states are normal and mean delivery works on some carriers and not others.
Current campaign is a temporary shim
+14015988402 is assigned to an ACCOUNT_NOTIFICATION campaign registered for a different business (CRM notifications, phone-based opt-in). Our traffic is a consumer texting a number found on a website. That mismatch risks carrier audit and brand suspension — a campaign with a customer-care / conversational use case is still outstanding.
Runbook: the site built but pages are missing
generateStaticParams reads Supabase at build time and returns [] on failure rather than throwing — so a credential or RLS problem produces a green build with no pages. Check the count:
✓ Generating static pages using 11 workers (667/667)Gotchas that cost time
- Next renames its process to
next-server (v16.x), sopkill -f "next start"silently misses it and you end up testing a stale server across rebuilds. Kill by PID fromlsof -nP -iTCP:PORT -sTCP:LISTEN -t. - Preview-scoped Vercel env vars loop on
git_branch_requiredin CLI v50.40.0 even when run exactly as the CLI suggests. Use the dashboard. - GPT-5 family + function tools requires
reasoning_effort: "none"onchat.completions, which also re-permitstemperature. Without it every call 400s.