koala73/worldmonitor · error

EMAIL_OWNERSHIP_REQUIRED

EMAIL_OWNERSHIP_REQUIRED

Error message

Verify your account email, then try again.

What it means

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.

Solutions

  1. Verify the account email: request/resend the Clerk verification email and click the verification link, then retry setEmailChannel()
  2. Check verification state via Clerk (user.emailAddresses[].verification.status === 'verified') before offering the email channel
  3. If the email was changed recently, complete the new address's verification before attaching notifications

Example fix

// before
await setEmailChannel(email);
// after
const user = useUser();
if (user.emailAddresses.some(a => a.verification?.status !== 'verified')) {
  showBanner('Verify your email first.');
  return;
}
await setEmailChannel(email);
Defensive patterns

Strategy: validation

Validate before calling

const verified = user?.emailAddresses?.some(a => a.verification?.status === 'verified'); if (!verified) { showVerifyEmailBanner(); return; }

Type guard

const isEmailVerified = (u: ClerkUser | null): boolean => !!u?.emailAddresses?.some(a => a.verification?.status === 'verified');

Try / catch

try { await setEmailChannel(email); } catch (e) { if (e.message.includes('Verify your account email')) { redirectToEmailVerification(); } else throw e; }

Prevention

When it happens

Trigger: 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'}.

Common situations: 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.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15). Data as JSON: /api/errors/d57b04371f647f71. Report an issue: GitHub.

Appendix: source

Thrown at src/services/notification-channels.ts:266

 *
 * `startSlackOAuth` / `startDiscordOAuth` keep the read semantics — they persist
 * nothing the user would miss, and abandoning a popup handoff on teardown is
 * correct.
 */
export async function setEmailChannel(
  email: string,
  expectedUserId?: string,
  signal?: AbortSignal,
): Promise<void> {
  const res = await authFetch('/api/notification-channels', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ action: 'set-channel', channelType: 'email', email }),
  }, expectedUserId, signal);
  if (!res.ok) {
    const failure = await res.json().catch(() => null);
    if (failure?.error === 'EMAIL_OWNERSHIP_REQUIRED') {
      throw new Error('Verify your account email, then try again.');
    }
    throw new Error('Could not connect email. Please try again.');
  }
}

export async function setSlackChannel(
  webhookEnvelope: string,
  signal?: AbortSignal,
): Promise<void> {
  const res = await authFetch('/api/notification-channels', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ action: 'set-channel', channelType: 'slack', webhookEnvelope }),
  }, undefined, signal);
  if (!res.ok) throw new Error(`set slack channel: ${res.status}`);
}

export async function setWebhookChannel(

View on GitHub (pinned to 7d06c8633d)