immich-app/immich · error · BadRequestException
Email is not available
Error message
Email is not available
What it means
Thrown by BaseService.createUser when userRepository.getByEmail(dto.email) already returns a user. Email is the unique identity, so a duplicate email is rejected before any insert is attempted.
Source
Thrown at server/src/services/base.service.ts:299
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: Insertable<UserTable> & { 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: Insertable<UserTable> = { ...dto };
if (payload.password) {
payload.password = await this.cryptoRepository.hashBcrypt(payload.password, SALT_ROUNDS);
}
if (payload.storageLabel) {
payload.storageLabel = sanitize(payload.storageLabel.replaceAll('.', ''));
}
const user = await this.userRepository.create(payload);View on GitHub (pinned to 199723261c)
Solutions
- Search existing users by email first and reuse/reactivate that account instead of creating a new one.
- If the existing row is a soft-deleted account, restore or purge it before re-adding.
- Normalize email case consistently before both lookup and insert to avoid false duplicates.
Example fix
// before
await userService.createUser({ email, ...dto });
// after
const existing = await userRepository.getByEmail(email.trim().toLowerCase());
if (existing) {
throw new BadRequestException(`User already exists with email ${email}`);
}
await userService.createUser({ email: email.trim().toLowerCase(), ...dto }); Defensive patterns
Strategy: validation
Validate before calling
const email = dto.email.trim().toLowerCase();
if (await userRepository.getByEmail(email)) {
throw new BadRequestException(`User already exists for ${email}`);
} Type guard
function isEmail(v: unknown): v is string {
return typeof v === 'string' && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v);
} Prevention
- Normalize email to lower-case before lookup and insert to avoid case-collision surprises.
- When re-onboarding, restore or purge soft-deleted accounts before re-adding the email.
- De-duplicate imports against existing emails first.
When it happens
Trigger: Any user-creation call (registration, admin creating a user, import) supplying an email that is already present in the users table.
Common situations: Re-registering an email that was soft-deleted (still present with deletedAt set); case-sensitivity surprises (the lookup is exact); importing users without de-duplicating first; a typo colliding with an existing account.
Related errors
- Email is not available
- The first registered account must the administrator.
- User not found
- Failed to verify SMTP configuration
- A tag with that name already exists
AI-assisted analysis of immich-app/immich@199723261c (2026-08-12).
Data as JSON: /api/errors/e23093e532f57ec2.
Report an issue: GitHub.