{"record":{"id":"6946934a9e263ce1","repo":"toeverything/AFFiNE","slug":"email-already-used","errorCode":"email_already_used","errorMessage":"This email has already been registered.","messagePattern":"This email has already been registered\\.","errorType":"exception","errorClass":"EmailAlreadyUsed","httpStatus":400,"severity":"error","filePath":"packages/backend/server/src/core/auth/resolver.ts","lineNumber":276,"sourceCode":"\n    validators.assertValidEmail(email);\n    const valid = await this.models.verificationToken.verify(\n      TokenType.ChangeEmail,\n      token,\n      {\n        credential: user.id,\n      }\n    );\n\n    if (!valid) {\n      throw new InvalidEmailToken();\n    }\n\n    const hasRegistered = await this.models.user.getUserByEmail(email);\n\n    if (hasRegistered) {\n      if (hasRegistered.id !== user.id) {\n        throw new EmailAlreadyUsed();\n      } else {\n        throw new SameEmailProvided();\n      }\n    }\n\n    const { token: verifyEmailToken, expiresAt } =\n      await this.models.verificationToken.createWithExpiresAt(\n        TokenType.VerifyEmail,\n        user.id\n      );\n\n    const url = this.url.safeLink(callbackUrl, {\n      token: verifyEmailToken,\n      email,\n    });\n    return await this.auth.sendVerifyChangeEmail(\n      email,\n      url,","sourceCodeStart":258,"sourceCodeEnd":294,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/packages/backend/server/src/core/auth/resolver.ts#L258-L294","documentation":"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.","triggerScenarios":"Calling sendVerifyChangeEmail with an email already registered to a different user id.","commonSituations":"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.","solutions":["Tell the user the address is taken and ask for a different one","If the blocking account is a soft-deleted duplicate, an operator must free the address (delete/rename that account) before retrying","For intentional merges, use the admin-side account tooling instead of the self-service change-email flow"],"exampleFix":"// before\nawait client.request(sendVerifyChangeEmailMutation, { token, email: newEmail, callbackUrl });\n\n// after\ntry {\n  await client.request(sendVerifyChangeEmailMutation, { token, email: newEmail, callbackUrl });\n} catch (e) {\n  if (gqlCode(e) === 'email_already_used') {\n    setEmailError('That address is already registered to another account');\n    return;\n  }\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isEmailAlreadyUsed(e: unknown): boolean {\n  return (\n    typeof e === 'object' &&\n    e !== null &&\n    'extensions' in e &&\n    (e as { extensions?: { code?: string } }).extensions?.code === 'email_already_used'\n  );\n}","tryCatchPattern":"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.","preventionTips":["Normalize and double-check the typed address (autofill mistakes) before submit","Surface the target email for explicit confirmation before starting the flow","For merges, coordinate with an operator instead of self-service change"],"tags":["auth","email","registration","graphql"],"backgroundTag":"email-already-registered","analyzedSha":"b4c8548c09da21b2898443559a5b846f0ccf5dd8","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}