toeverything/AFFiNE · error · EmailAlreadyUsed

email_already_used

email_already_used

Error message

This email has already been registered.

What it means

Thrown by sendVerifyChangeEmail when getUserByEmail(email) finds an existing account whose id differs from the current user. The address is claimed by another account, so the change-email flow refuses to proceed before minting the VerifyEmail token for the new address.

Solutions

  1. Tell the user the address is taken and ask for a different one
  2. If the blocking account is a soft-deleted duplicate, an operator must free the address (delete/rename that account) before retrying
  3. For intentional merges, use the admin-side account tooling instead of the self-service change-email flow

Example fix

// before
await client.request(sendVerifyChangeEmailMutation, { token, email: newEmail, callbackUrl });

// after
try {
  await client.request(sendVerifyChangeEmailMutation, { token, email: newEmail, callbackUrl });
} catch (e) {
  if (gqlCode(e) === 'email_already_used') {
    setEmailError('That address is already registered to another account');
    return;
  }
  throw e;
}
Defensive patterns

Strategy: try-catch

Type guard

function isEmailAlreadyUsed(e: unknown): boolean {
  return (
    typeof e === 'object' &&
    e !== null &&
    'extensions' in e &&
    (e as { extensions?: { code?: string } }).extensions?.code === 'email_already_used'
  );
}

Try / catch

Catch extensions.code === 'email_already_used', keep the form open, and ask the user for a different address. Do not retry with the same email.

Prevention

When it happens

Trigger: Calling sendVerifyChangeEmail with an email already registered to a different user id.

Common situations: User typo-points at a family member's existing account; a soft-deleted or dormant account still holds the address; user forgot they already registered the target address; account-merge scenarios where two accounts legitimately need the same address.

Related errors


AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18). Data as JSON: /api/errors/6946934a9e263ce1. Report an issue: GitHub.

Appendix: source

Thrown at packages/backend/server/src/core/auth/resolver.ts:276

    validators.assertValidEmail(email);
    const valid = await this.models.verificationToken.verify(
      TokenType.ChangeEmail,
      token,
      {
        credential: user.id,
      }
    );

    if (!valid) {
      throw new InvalidEmailToken();
    }

    const hasRegistered = await this.models.user.getUserByEmail(email);

    if (hasRegistered) {
      if (hasRegistered.id !== user.id) {
        throw new EmailAlreadyUsed();
      } else {
        throw new SameEmailProvided();
      }
    }

    const { token: verifyEmailToken, expiresAt } =
      await this.models.verificationToken.createWithExpiresAt(
        TokenType.VerifyEmail,
        user.id
      );

    const url = this.url.safeLink(callbackUrl, {
      token: verifyEmailToken,
      email,
    });
    return await this.auth.sendVerifyChangeEmail(
      email,
      url,

View on GitHub (pinned to b4c8548c09)