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
- Use a different email address for the new user
- Have the existing account holder reset their password instead of creating a duplicate
- Normalize the email (trim/lowercase) before checking and retry with a distinct value
- 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
- Normalize emails (trim/lowercase) before insert
- Check uniqueness in the UI before submit
- Prefer password reset over duplicate account creation
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
- Email is not available
- Email is not available
- A tag with that name already exists
- Cannot merge a person into themselves
- Duplicate items are not allowed
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)