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
- Pick a unique email not already associated with an account.
- If the email legitimately belongs to this user, delete or rename the conflicting account first.
- 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
- Normalize (trim, lowercase) emails client-side before submit.
- Debounce-check email availability as the user types.
- Before account merges, rename the duplicate account's email first.
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
- Storage label already in use by another account
- Email is not available
- A tag with that name already exists
- password is required
- Admin status can only be changed by another admin
AI-assisted analysis of immich-app/immich@199723261c (2026-08-12).
Data as JSON: /api/errors/a64d9f36ea2843c7.
Report an issue: GitHub.