{"record":{"id":"241d03ac986b0d00","repo":"koala73/worldmonitor","slug":"service-unavailable","errorCode":null,"errorMessage":"Service unavailable","messagePattern":"Service unavailable","errorType":"http","errorClass":"ApiError","httpStatus":503,"severity":"error","filePath":"server/worldmonitor/leads/v1/submit-contact.ts","lineNumber":154,"sourceCode":"  }\n  if (!organization || organization.trim().length === 0) {\n    throw new ValidationError([{ field: 'organization', description: 'Company is required' }]);\n  }\n  if (!phone || !PHONE_RE.test(phone.trim())) {\n    throw new ValidationError([{ field: 'phone', description: 'Valid phone number is required' }]);\n  }\n\n  const safeName = name.slice(0, MAX_FIELD);\n  const safeOrg = organization.slice(0, MAX_FIELD);\n  const safePhone = phone.trim().slice(0, 30);\n  const safeMsg = message ? message.slice(0, MAX_MESSAGE) : undefined;\n  const safeSource = source ? source.slice(0, 100) : 'enterprise-contact';\n\n  const convexUrl = (process.env.CONVEX_SITE_URL\n    ?? (process.env.CONVEX_URL ?? '').replace('.convex.cloud', '.convex.site')).replace(/\\/$/, '');\n  const secret = process.env.CONVEX_SERVER_SHARED_SECRET;\n  if (!convexUrl || !secret) {\n    throw new ApiError(503, 'Service unavailable', '');\n  }\n\n  let response: Response;\n  try {\n    response = await fetch(`${convexUrl}/leads/submit-contact`, {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n        'User-Agent': 'worldmonitor-leads/1.0',\n        'x-convex-shared-secret': secret,\n      },\n      body: JSON.stringify({\n        name: safeName,\n        email: email.trim(),\n        organization: safeOrg,\n        phone: safePhone,\n        message: safeMsg,\n        source: safeSource,","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/server/worldmonitor/leads/v1/submit-contact.ts#L136-L172","documentation":"submitContact requires Convex connection configuration before it can forward the contact form. If neither CONVEX_SITE_URL (or a derivable CONVEX_URL) nor CONVEX_SERVER_SHARED_SECRET is configured, it refuses to run and throws this 503.","triggerScenarios":"Calling the contact endpoint on an environment where process.env.CONVEX_SITE_URL and process.env.CONVEX_URL are unset, or where CONVEX_SERVER_SHARED_SECRET is missing.","commonSituations":"Deployed to a preview/production environment whose env vars were never set; Convex URL lacks the expected '.convex.cloud' suffix so the .convex.site derivation yields an empty/invalid value; secrets not synced to the hosting platform.","solutions":["Set CONVEX_SITE_URL (recommended, e.g. https://your-app.convex.site) in the serving environment","Or set CONVEX_URL with a '.convex.cloud' value so the '.convex.site' fallback derivation works","Set CONVEX_SERVER_SHARED_SECRET to the same secret configured in the Convex backend","Redeploy/restart the API after adding the env vars so they are picked up"],"exampleFix":"// before\n# environment\nCONVEX_URL=\n// after\n# environment\nCONVEX_SITE_URL=https://your-deployment.convex.site\nCONVEX_SERVER_SHARED_SECRET=<shared-secret>","handlingStrategy":"validation","validationCode":"const required = ['CONVEX_SITE_URL', 'CONVEX_SERVER_SHARED_SECRET'] as const;\nfor (const k of required) if (!process.env[k]) throw new Error(`Missing env var: ${k}`);","typeGuard":"null","tryCatchPattern":"try {\n  await submitContact(form);\n} catch (e) {\n  if (e instanceof ApiError && e.status === 503) {\n    alertOps('submit-contact misconfigured: Convex env vars missing');\n    showBanner('Contact form is temporarily unavailable.');\n  } else throw e;\n}","preventionTips":["Assert all required env vars at service startup, not per-request","Keep env var definitions in .env.example and CI config checks","Prefer CONVEX_SITE_URL explicitly over the .convex.cloud -> .convex.site string derivation"],"tags":["config","env","convex"],"backgroundTag":"missing-env-var","analyzedSha":"7d06c8633d256c18e38133030bc3613976a96ec9","analyzedAt":"2026-09-15T16:44:39.439Z","contentChangedAt":"2026-09-15T16:44:39.439Z","schemaVersion":2},"datasetVersion":"2026-09-15T18:17:12.389Z"}