immich-app/immich · error · BadRequestException

Email is not available

Error message

Email is not available

What it means

createUser enforces unique email addresses. It checks userRepository.getByEmail first and throws BadRequestException('Email is not available') if a user already exists with that email, before any insert happens.

Solutions

  1. Use a different email address for the new user
  2. Have the existing account holder reset their password instead of creating a duplicate
  3. Normalize the email (trim/lowercase) before checking and retry with a distinct value
  4. Delete or rename the conflicting account first if it is stale

Example fix

// before
await service.createUser({ email: ' Bob@Example.com ', password });
// after
const email = ' Bob@Example.com '.trim().toLowerCase();
if (!(await service.userRepository.getByEmail(email))) await service.createUser({ email, password });
Defensive patterns

Strategy: validation

Validate before calling

const email = raw.trim().toLowerCase(); if (await userRepository.getByEmail(email)) throw new Error('email taken');

Try / catch

catch (e) { if (e.status === 400 && e.message === 'Email is not available') { /* offer password reset instead */ } }

Prevention

When it happens

Trigger: Admin user-creation, self-signup, or OAuth callback flows calling createUser with an email already registered (case/whitespace variants included).

Common situations: Duplicate signup attempts; OAuth accounts whose provider email matches an existing local user; bulk import re-run; trailing whitespace or case differences that still collide after normalization.

Related errors


AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15). Data as JSON: /api/errors/e23093e532f57ec2. Report an issue: GitHub.

Appendix: source

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

    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: Omit<Insertable<UserTable>, 'clusterGroupId'> & { 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: Omit<Insertable<UserTable>, 'clusterGroupId'> = { ...dto };
    if (payload.password) {
      payload.password = await this.cryptoRepository.hashBcrypt(payload.password, SALT_ROUNDS);
    }
    if (payload.storageLabel) {
      payload.storageLabel = sanitize(payload.storageLabel.replaceAll('.', ''));
    }

    const clusterGroup = await this.clusterGroupRepository.create();

View on GitHub (pinned to e55ac299a4)