immich-app/immich · error · BadRequestException

Email is not available

Error message

Email is not available

What it means

A BadRequestException (HTTP 400) thrown by UserService.updateMe when the requested new email is already owned by a different user. The service looks up the email and, if a duplicate exists whose id differs from the acting user, rejects the change. This is a uniqueness guard for the self-service profile update path.

Source

Thrown at server/src/services/user.service.ts:61

  async getMe(auth: AuthDto): Promise<UserAdminResponseDto> {
    const user = await this.userRepository.get(auth.user.id, {});
    if (!user) {
      throw new BadRequestException('User not found');
    }

    return mapUserAdmin(user);
  }

  getCalendarHeatmap(auth: AuthDto, dto: CalendarHeatmapDto): Promise<CalendarHeatmapResponseDto> {
    return getCalendarHeatmap(auth.user.id, dto, { asset: this.assetRepository });
  }

  async updateMe({ user }: AuthDto, dto: UserUpdateMeDto): Promise<UserAdminResponseDto> {
    if (dto.email) {
      const duplicate = await this.userRepository.getByEmail(dto.email);
      if (duplicate && duplicate.id !== user.id) {
        this.logger.warn('Email already in use by another account');
        throw new BadRequestException('Email is not available');
      }
    }

    const update: Updateable<UserTable> = {
      email: dto.email,
      name: dto.name,
      avatarColor: dto.avatarColor,
    };

    if (dto.password) {
      const hashedPassword = await this.cryptoRepository.hashBcrypt(dto.password, SALT_ROUNDS);
      update.password = hashedPassword;
      update.shouldChangePassword = false;
    }

    const updatedUser = await this.userRepository.update(user.id, update);

    return mapUserAdmin(updatedUser);

View on GitHub (pinned to 199723261c)

Solutions

  1. Prompt the user to pick a different email and re-submit.
  2. If the duplicate belongs to a soft-deleted account that should free the email, purge or hard-delete that account first.
  3. Verify case sensitivity expectations; trim and normalize the email client-side before sending.
  4. If the user believes the email is theirs, have an admin confirm ownership of the duplicate account before reassigning.

Example fix

// before
await api.updateMe({ email: 'taken@example.com' }); // 400

// after
const available = await api.checkEmailAvailable('taken@example.com');
if (!available) {
  showEmailTakenError();
  return;
}
await api.updateMe({ email: 'taken@example.com' });
Defensive patterns

Strategy: validation

Validate before calling

async function isEmailAvailable(email) {
  // call any email-availability or user-search endpoint the API exposes
  const taken = await api.searchUsers({ email });
  return !taken.some((u) => u.email.toLowerCase() === email.trim().toLowerCase());
}
if (!(await isEmailAvailable(newEmail))) { showEmailTaken(); return; }

Type guard

const isEmailNotAvailableError = (e: unknown): boolean =>
  typeof e === 'object' && e !== null && (e as any).status === 400 && (e as any).message === 'Email is not available';

Try / catch

try {
  await api.updateMe({ email: newEmail });
} catch (e) {
  if (isEmailNotAvailableError(e)) {
    setFieldError('email', 'This email is already in use');
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: PUT /users/me with an email that belongs to another account, including emails of soft-deleted users that still occupy the unique email column. Also triggered by case-variant collisions if the lookup is case-insensitive.

Common situations: User picks an email already registered; merging two accounts by renaming one to the other's email; an admin pre-reserved an email; a previously deleted user's email still in the table.

Related errors


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