{"record":{"id":"64ca3edb16c1772d","repo":"koala73/worldmonitor","slug":"could-not-connect-email-please-try-again","errorCode":null,"errorMessage":"Could not connect email. Please try again.","messagePattern":"Could not connect email\\. Please try again\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/services/notification-channels.ts","lineNumber":268,"sourceCode":" * nothing the user would miss, and abandoning a popup handoff on teardown is\n * correct.\n */\nexport async function setEmailChannel(\n  email: string,\n  expectedUserId?: string,\n  signal?: AbortSignal,\n): Promise<void> {\n  const res = await authFetch('/api/notification-channels', {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify({ action: 'set-channel', channelType: 'email', email }),\n  }, expectedUserId, signal);\n  if (!res.ok) {\n    const failure = await res.json().catch(() => null);\n    if (failure?.error === 'EMAIL_OWNERSHIP_REQUIRED') {\n      throw new Error('Verify your account email, then try again.');\n    }\n    throw new Error('Could not connect email. Please try again.');\n  }\n}\n\nexport async function setSlackChannel(\n  webhookEnvelope: string,\n  signal?: AbortSignal,\n): Promise<void> {\n  const res = await authFetch('/api/notification-channels', {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify({ action: 'set-channel', channelType: 'slack', webhookEnvelope }),\n  }, undefined, signal);\n  if (!res.ok) throw new Error(`set slack channel: ${res.status}`);\n}\n\nexport async function setWebhookChannel(\n  webhookUrl: string,\n  label?: string,","sourceCodeStart":250,"sourceCodeEnd":286,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/src/services/notification-channels.ts#L250-L286","documentation":"setEmailChannel() throws this generic fallback when the set-channel API call fails with any error other than the specific EMAIL_OWNERSHIP_REQUIRED code (or when the error body cannot be parsed). It deliberately hides server detail from the user and asks them to retry.","triggerScenarios":"Calling setEmailChannel(email) and the POST /set-channel request returning non-OK with an unexpected or unparseable error body — network/server 500s, validation failures with unknown codes, gateway errors.","commonSituations":"Backend outage or 5xx from the notifications API; request rejected with an error code the client doesn't recognize; response body not JSON (proxy/HTML error page); transient network failure mid-request.","solutions":["Retry the call after a short delay; the error is intentionally generic so transient failures often clear on retry","Inspect the network tab for the actual response status/body to diagnose the underlying cause","Confirm the notifications API is deployed and healthy if failures persist","Validate the email format client-side before the call to rule out request-level rejection"],"exampleFix":"// before\nawait setEmailChannel(email);\n// after\ntry {\n  await setEmailChannel(email);\n} catch (e) {\n  if (e.message.includes('Could not connect email')) {\n    await new Promise(r => setTimeout(r, 2000));\n    await setEmailChannel(email);\n  } else throw e;\n}","handlingStrategy":"try-catch","validationCode":"if (!/^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$/.test(email)) { showInvalidEmail(); return; }","typeGuard":null,"tryCatchPattern":"try { await setEmailChannel(email); } catch (e) { if (e.message.includes('Could not connect email')) { toast('Retrying…'); await withRetry(() => setEmailChannel(email), 2); } else throw e; }","preventionTips":["Validate email format client-side first","Distinguish EMAIL_OWNERSHIP_REQUIRED from generic failures (the service already does) and surface both distinctly","Add bounded retries and monitor the notifications API health"],"tags":["email","notifications","http-error","retry"],"backgroundTag":"api-error-response","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"}