toeverything/AFFiNE · error · EmailAlreadyUsed
email_already_used
email_already_used
Error message
This email has already been registered.
What it means
In sendVerifyChangeEmail, after the ChangeEmail token verifies, the server checks whether the requested new email is already registered via models.user.getUserByEmail(email). If a user exists and its id differs from the current user.id, EmailAlreadyUsed is thrown — the new email belongs to someone else and cannot be claimed. (If it belongs to the same user, SameEmailProvided is thrown instead.) This prevents hijacking or merging onto an existing account.
Source
Thrown at packages/backend/server/src/core/auth/resolver.ts:276
validators.assertValidEmail(email);
const valid = await this.models.verificationToken.verify(
TokenType.ChangeEmail,
token,
{
credential: user.id,
}
);
if (!valid) {
throw new InvalidEmailToken();
}
const hasRegistered = await this.models.user.getUserByEmail(email);
if (hasRegistered) {
if (hasRegistered.id !== user.id) {
throw new EmailAlreadyUsed();
} else {
throw new SameEmailProvided();
}
}
const { token: verifyEmailToken, expiresAt } =
await this.models.verificationToken.createWithExpiresAt(
TokenType.VerifyEmail,
user.id
);
const url = this.url.safeLink(callbackUrl, {
token: verifyEmailToken,
email,
});
return await this.auth.sendVerifyChangeEmail(
email,
url,View on GitHub (pinned to 26c515e050)
Solutions
- Choose a new email address that is not registered to any other account.
- If the user owns the other account, sign into it instead, or have an admin delete/merge accounts out-of-band.
- Suggest the user verify the new email spelling before submitting.
- If the existing row is a stale duplicate, an admin can remove it to free the address.
Defensive patterns
Strategy: validation
Validate before calling
// client pre-check: warn if the new email looks taken (requires a lookup API)
// otherwise validate format and uniqueness intent before submit
if (newEmail === currentUser.email) {
showUser('This is already your email.');
return;
} Type guard
function isEmailAlreadyUsed(err: unknown): boolean {
return (
!!err &&
typeof err === 'object' &&
(err as { code?: string }).code === 'email_already_used'
);
} Try / catch
try {
await sendVerifyChangeEmail({ token, email: newEmail, callbackUrl });
} catch (err) {
if (isEmailAlreadyUsed(err)) {
showUser('That email is already registered to another account.');
return;
}
throw err;
} Prevention
- Prompt the user to pick a new email that is not already in use.
- If the user owns the other account, sign into it instead.
- Have admins remove stale duplicate rows to free addresses.
- Validate email format before submitting the change.
When it happens
Trigger: The user attempts to change their email to an address that is already the primary email of a DIFFERENT user account. The lookup returns a user whose id !== currentUser.id.
Common situations: User mistyped the new email as one already in use by another account. Two accounts with overlapping intended addresses. The new email was previously registered and abandoned but not deleted.
Related errors
AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12).
Data as JSON: /api/errors/6946934a9e263ce1.
Report an issue: GitHub.