toeverything/AFFiNE · error · EmailAlreadyUsed
email_already_used
email_already_used
Error message
This email has already been registered.
What it means
Thrown by sendVerifyChangeEmail when getUserByEmail(email) finds an existing account whose id differs from the current user. The address is claimed by another account, so the change-email flow refuses to proceed before minting the VerifyEmail token for the new address.
Solutions
- Tell the user the address is taken and ask for a different one
- If the blocking account is a soft-deleted duplicate, an operator must free the address (delete/rename that account) before retrying
- For intentional merges, use the admin-side account tooling instead of the self-service change-email flow
Example fix
// before
await client.request(sendVerifyChangeEmailMutation, { token, email: newEmail, callbackUrl });
// after
try {
await client.request(sendVerifyChangeEmailMutation, { token, email: newEmail, callbackUrl });
} catch (e) {
if (gqlCode(e) === 'email_already_used') {
setEmailError('That address is already registered to another account');
return;
}
throw e;
} Defensive patterns
Strategy: try-catch
Type guard
function isEmailAlreadyUsed(e: unknown): boolean {
return (
typeof e === 'object' &&
e !== null &&
'extensions' in e &&
(e as { extensions?: { code?: string } }).extensions?.code === 'email_already_used'
);
} Try / catch
Catch extensions.code === 'email_already_used', keep the form open, and ask the user for a different address. Do not retry with the same email.
Prevention
- Normalize and double-check the typed address (autofill mistakes) before submit
- Surface the target email for explicit confirmation before starting the flow
- For merges, coordinate with an operator instead of self-service change
When it happens
Trigger: Calling sendVerifyChangeEmail with an email already registered to a different user id.
Common situations: User typo-points at a family member's existing account; a soft-deleted or dormant account still holds the address; user forgot they already registered the target address; account-merge scenarios where two accounts legitimately need the same address.
Related errors
- email_token_not_found
- same_email_provided
- action_forbidden
- email_already_used
- email_verification_required
AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18).
Data as JSON: /api/errors/6946934a9e263ce1.
Report an issue: GitHub.
Appendix: 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 b4c8548c09)