Operations & runbook

Deploys, DNS, and how to debug the failures we have actually hit.

Deploys

TargetTriggerNotes
Vercel (frontend)Push to main, or vercel --prodFramework 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

TypeNameValueProxy
A@76.76.21.21DNS only
Awww76.76.21.21DNS only
CNAMEapi1zz3qufy.up.railway.appDNS only
CNAMEdevelopercname.vercel-dns.comDNS 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.app

Every 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:

SymptomMeaningFix
No [telnyx] inboundTelnyx never reached usCheck the webhook URL on the messaging profile
signature verification failedPublic key does not match that profileRe-copy TELNYX_PUBLIC_KEY, redeploy
inbound but no repliedAgent or send threwSee [telnyx] handler error
replied but nothing arrivesCarrier dropped it — usually 10DLCCheck the delivery receipt line, then campaign assignment
delivery … status=delivery_failedCarrier rejection with an error codeLook 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/phoneNumberCampaign
assignment : PENDING_ASSIGNMENT | ASSIGNED
T-Mobile   : PENDING | ADDED
AT&T       : null | ADDED
other      : ADDED        ← Verizon etc. often land first

Mapping 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), so pkill -f "next start" silently misses it and you end up testing a stale server across rebuilds. Kill by PID from lsof -nP -iTCP:PORT -sTCP:LISTEN -t.
  • Preview-scoped Vercel env vars loop on git_branch_required in CLI v50.40.0 even when run exactly as the CLI suggests. Use the dashboard.
  • GPT-5 family + function tools requires reasoning_effort: "none" on chat.completions, which also re-permits temperature. Without it every call 400s.