{"record":{"id":"e23093e532f57ec2","repo":"immich-app/immich","slug":"email-is-not-available","errorCode":null,"errorMessage":"Email is not available","messagePattern":"Email is not available","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"server/src/services/base.service.ts","lineNumber":299,"sourceCode":"    return checkAccess(this.accessRepository, request);\n  }\n\n  async isSetupAvailable(): Promise<boolean> {\n    const { setup } = this.configRepository.getEnv();\n    return setup.allow && !(await this.userRepository.hasAdmin());\n  }\n\n  async requireSetupAvailable(): Promise<void> {\n    if (!(await this.isSetupAvailable())) {\n      throw new BadRequestException('Admin setup is not available');\n    }\n  }\n\n  async createUser(dto: Insertable<UserTable> & { email: string }): Promise<UserAdmin> {\n    const exists = await this.userRepository.getByEmail(dto.email);\n    if (exists) {\n      this.logger.debug('User creation rejected: user already exists');\n      throw new BadRequestException('Email is not available');\n    }\n\n    if (!dto.isAdmin) {\n      const localAdmin = await this.userRepository.getAdmin();\n      if (!localAdmin) {\n        throw new BadRequestException('The first registered account must the administrator.');\n      }\n    }\n\n    const payload: Insertable<UserTable> = { ...dto };\n    if (payload.password) {\n      payload.password = await this.cryptoRepository.hashBcrypt(payload.password, SALT_ROUNDS);\n    }\n    if (payload.storageLabel) {\n      payload.storageLabel = sanitize(payload.storageLabel.replaceAll('.', ''));\n    }\n\n    const user = await this.userRepository.create(payload);","sourceCodeStart":281,"sourceCodeEnd":317,"githubUrl":"https://github.com/immich-app/immich/blob/199723261c6ffa897fec8ccdaea6359e39c37cc3/server/src/services/base.service.ts#L281-L317","documentation":"Thrown by BaseService.createUser when userRepository.getByEmail(dto.email) already returns a user. Email is the unique identity, so a duplicate email is rejected before any insert is attempted.","triggerScenarios":"Any user-creation call (registration, admin creating a user, import) supplying an email that is already present in the users table.","commonSituations":"Re-registering an email that was soft-deleted (still present with deletedAt set); case-sensitivity surprises (the lookup is exact); importing users without de-duplicating first; a typo colliding with an existing account.","solutions":["Search existing users by email first and reuse/reactivate that account instead of creating a new one.","If the existing row is a soft-deleted account, restore or purge it before re-adding.","Normalize email case consistently before both lookup and insert to avoid false duplicates."],"exampleFix":"// before\nawait userService.createUser({ email, ...dto });\n\n// after\nconst existing = await userRepository.getByEmail(email.trim().toLowerCase());\nif (existing) {\n  throw new BadRequestException(`User already exists with email ${email}`);\n}\nawait userService.createUser({ email: email.trim().toLowerCase(), ...dto });","handlingStrategy":"validation","validationCode":"const email = dto.email.trim().toLowerCase();\nif (await userRepository.getByEmail(email)) {\n  throw new BadRequestException(`User already exists for ${email}`);\n}","typeGuard":"function isEmail(v: unknown): v is string {\n  return typeof v === 'string' && /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(v);\n}","tryCatchPattern":null,"preventionTips":["Normalize email to lower-case before lookup and insert to avoid case-collision surprises.","When re-onboarding, restore or purge soft-deleted accounts before re-adding the email.","De-duplicate imports against existing emails first."],"tags":["user","email","duplicate","bad-request"],"backgroundTag":null,"analyzedSha":"199723261c6ffa897fec8ccdaea6359e39c37cc3","analyzedAt":"2026-08-12T04:54:27.085Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}