immich-app/immich · error · BadRequestException

Email is not available

Error message

Email is not available

What it means

Thrown (as BadRequestException) by UserAdminService.update when dto.email is provided and userRepository.getByEmail returns a different user (id mismatch). Email must be unique across the instance, so a collision is rejected before the update is applied.

Source

Thrown at server/src/services/user-admin.service.ts:71

    return mapUserAdmin(user);
  }

  async update(auth: AuthDto, id: string, dto: UserAdminUpdateDto): Promise<UserAdminResponseDto> {
    const user = await this.findOrFail(id, {});

    if (dto.isAdmin !== undefined && dto.isAdmin !== auth.user.isAdmin && auth.user.id === id) {
      throw new BadRequestException('Admin status can only be changed by another admin');
    }

    if (dto.quotaSizeInBytes && user.quotaSizeInBytes !== dto.quotaSizeInBytes) {
      await this.userRepository.syncUsage(id);
    }

    if (dto.email) {
      const duplicate = await this.userRepository.getByEmail(dto.email);
      if (duplicate && duplicate.id !== id) {
        this.logger.debug('Email already in use by another account');
        throw new BadRequestException('Email is not available');
      }
    }

    if (dto.storageLabel) {
      const duplicate = await this.userRepository.getByStorageLabel(dto.storageLabel);
      if (duplicate && duplicate.id !== id) {
        throw new BadRequestException('Storage label already in use by another account');
      }
    }

    if (dto.password) {
      dto.password = await this.cryptoRepository.hashBcrypt(dto.password, SALT_ROUNDS);
    }

    if (dto.pinCode) {
      dto.pinCode = await this.cryptoRepository.hashBcrypt(dto.pinCode, SALT_ROUNDS);
    }

View on GitHub (pinned to 199723261c)

Solutions

  1. Pick a unique email not already associated with an account.
  2. If the email legitimately belongs to this user, delete or rename the conflicting account first.
  3. Normalize the email (trim/lowercase) before submit to avoid accidental collisions.

Example fix

// before
await usersApi.update(id, { email: 'taken@example.com' });
// after
const free = await pickUniqueEmail();
await usersApi.update(id, { email: free });
Defensive patterns

Strategy: validation

Validate before calling

const owner = await userRepository.getByEmail(dto.email.trim().toLowerCase());
if (owner && owner.id !== id) { /* show 'email taken' error, do not PUT */ }

Try / catch

try { await usersApi.update(id, dto); }
catch (e) {
  if (e instanceof BadRequestException && e.message === 'Email is not available') {
    // prompt for a different email
  }
}

Prevention

When it happens

Trigger: PUT /admin/users/:id with an email already claimed by another account.

Common situations: Merging duplicate accounts; user changed email to one already in the directory; case/whitespace differences that resolve to the same stored address.

Related errors


AI-assisted analysis of immich-app/immich@199723261c (2026-08-12). Data as JSON: /api/errors/a64d9f36ea2843c7. Report an issue: GitHub.