{"record":{"id":"d57b04371f647f71","repo":"koala73/worldmonitor","slug":"email-ownership-required-verify-your-account-then-try-again","errorCode":"EMAIL_OWNERSHIP_REQUIRED","errorMessage":"Verify your account email, then try again.","messagePattern":"Verify your account email, then try again\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/services/notification-channels.ts","lineNumber":266,"sourceCode":" *\n * `startSlackOAuth` / `startDiscordOAuth` keep the read semantics — they persist\n * 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(","sourceCodeStart":248,"sourceCodeEnd":284,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/src/services/notification-channels.ts#L248-L284","documentation":"setEmailChannel() throws this with code EMAIL_OWNERSHIP_REQUIRED when the notification-channel API responds with that exact error, meaning the backend requires the account's email to be verified before an email notification channel can be attached. It is a policy check, not a transport failure.","triggerScenarios":"Calling setEmailChannel(email) (directly or via confirmBrief/attach) for an email address that is not verified on the account, and the API replying {error: 'EMAIL_OWNERSHIP_REQUIRED'}.","commonSituations":"Newly signed-up users whose Clerk email is unverified; users who changed their account email but haven't clicked the verification link; attaching a brief's email notifications right after signup.","solutions":["Verify the account email: request/resend the Clerk verification email and click the verification link, then retry setEmailChannel()","Check verification state via Clerk (user.emailAddresses[].verification.status === 'verified') before offering the email channel","If the email was changed recently, complete the new address's verification before attaching notifications"],"exampleFix":"// before\nawait setEmailChannel(email);\n// after\nconst user = useUser();\nif (user.emailAddresses.some(a => a.verification?.status !== 'verified')) {\n  showBanner('Verify your email first.');\n  return;\n}\nawait setEmailChannel(email);","handlingStrategy":"validation","validationCode":"const verified = user?.emailAddresses?.some(a => a.verification?.status === 'verified'); if (!verified) { showVerifyEmailBanner(); return; }","typeGuard":"const isEmailVerified = (u: ClerkUser | null): boolean => !!u?.emailAddresses?.some(a => a.verification?.status === 'verified');","tryCatchPattern":"try { await setEmailChannel(email); } catch (e) { if (e.message.includes('Verify your account email')) { redirectToEmailVerification(); } else throw e; }","preventionTips":["Require verified email before showing email-channel UI","Resend verification links from settings","Re-check verification after any account email change"],"tags":["email","verification","notifications","account"],"backgroundTag":"permission-denied","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"}