immich-app/immich · error · BadRequestException

Email is not available

Error message

Email is not available

What it means

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.

Source

Thrown at server/src/services/base.service.ts:299

    return checkAccess(this.accessRepository, request);
  }

  async isSetupAvailable(): Promise<boolean> {
    const { setup } = this.configRepository.getEnv();
    return setup.allow && !(await this.userRepository.hasAdmin());
  }

  async requireSetupAvailable(): Promise<void> {
    if (!(await this.isSetupAvailable())) {
      throw new BadRequestException('Admin setup is not available');
    }
  }

  async createUser(dto: Insertable<UserTable> & { email: string }): Promise<UserAdmin> {
    const exists = await this.userRepository.getByEmail(dto.email);
    if (exists) {
      this.logger.debug('User creation rejected: user already exists');
      throw new BadRequestException('Email is not available');
    }

    if (!dto.isAdmin) {
      const localAdmin = await this.userRepository.getAdmin();
      if (!localAdmin) {
        throw new BadRequestException('The first registered account must the administrator.');
      }
    }

    const payload: Insertable<UserTable> = { ...dto };
    if (payload.password) {
      payload.password = await this.cryptoRepository.hashBcrypt(payload.password, SALT_ROUNDS);
    }
    if (payload.storageLabel) {
      payload.storageLabel = sanitize(payload.storageLabel.replaceAll('.', ''));
    }

    const user = await this.userRepository.create(payload);

View on GitHub (pinned to 199723261c)

Solutions

  1. Search existing users by email first and reuse/reactivate that account instead of creating a new one.
  2. If the existing row is a soft-deleted account, restore or purge it before re-adding.
  3. Normalize email case consistently before both lookup and insert to avoid false duplicates.

Example fix

// before
await userService.createUser({ email, ...dto });

// after
const existing = await userRepository.getByEmail(email.trim().toLowerCase());
if (existing) {
  throw new BadRequestException(`User already exists with email ${email}`);
}
await userService.createUser({ email: email.trim().toLowerCase(), ...dto });
Defensive patterns

Strategy: validation

Validate before calling

const email = dto.email.trim().toLowerCase();
if (await userRepository.getByEmail(email)) {
  throw new BadRequestException(`User already exists for ${email}`);
}

Type guard

function isEmail(v: unknown): v is string {
  return typeof v === 'string' && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v);
}

Prevention

When it happens

Trigger: Any user-creation call (registration, admin creating a user, import) supplying an email that is already present in the users table.

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

Related errors


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