immich-app/immich · error · BadRequestException

Email is not available

Error message

Email is not available

What it means

updateMe rejects an email change when the requested email is already owned by a different user account. The service looks up the email in the user repository and compares the found duplicate's id with the authenticated user's id. If a different account holds the email, the change is refused with a 400 Bad Request.

Solutions

  1. Pick a different email address that is not registered on this Immich instance.
  2. Check the existing account that owns the email (admin Users page) and free it up by changing that account's email first.
  3. If you are re-submitting your own unchanged email, omit the email field from the request body.
  4. In client code, handle the 400 with message 'Email is not available' and prompt the user to choose another address.

Example fix

// before
await api.updateMyUser({ email: 'shared@example.com' });
// after
const taken = await isEmailTaken('shared@example.com');
if (taken) throw new Error('choose a different email');
await api.updateMyUser({ email: 'unique@example.com' });
Defensive patterns

Strategy: validation

Validate before calling

const isEmailTaken = async (email: string) => {
  const existing = await userRepo.getByEmail(email);
  return !!existing;
};
if (dto.email && await isEmailTaken(dto.email)) {
  throw new BadRequestException('Email is not available');
}

Try / catch

try {
  await api.updateMyUser({ email });
} catch (e) {
  if (e instanceof BadRequestException && e.message === 'Email is not available') {
    // prompt user to pick another email
  }
}

Prevention

When it happens

Trigger: PUT /users/me with an email in the body whose value matches another existing user's email (duplicate.id !== user.id). Note that resubmitting your own current email is allowed since duplicate.id === user.id.

Common situations: Shared/family servers where two accounts try to use the same address; a user changing email to one that was later re-registered by an admin; seeding scripts importing users with colliding emails; stale client forms that pre-filled an old email now owned by someone else.

Understand the failure class

Background: "User not found", "Invalid user", and "does not exist": what missing-user lookup errors mean across Rocket.Chat, LiteLLM, Phabricator, rustfs, and pnpm — this error's family across 10 libraries.

Related errors


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

Appendix: 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 e55ac299a4)