toeverything/AFFiNE · error · EmailVerificationRequired
email_verification_required
email_verification_required
Error message
You must verify your email before accessing this resource.
What it means
Thrown by sendChangePasswordEmail when the signed-in user has user.emailVerified === false. The server refuses to issue a ChangePassword token for an unverified identity, since the reset flow assumes control of the address was already proven.
Solutions
- Complete email verification first: call sendVerifyEmail and then verifyEmail with the token from the link
- When seeding dev users, set emailVerified/emailVerifiedAt directly so test flows skip this gate
- Check earlier steps actually delivered the verification email (mail provider config, SMTP credentials)
Example fix
// before
await client.request(sendChangePasswordEmailMutation, { callbackUrl });
// after
const me = await client.request(currentUserQuery);
if (!me.me.emailVerified) {
throw new Error('Verify your email address first');
}
await client.request(sendChangePasswordEmailMutation, { callbackUrl }); Defensive patterns
Strategy: validation
Validate before calling
const { me } = await client.request(currentUserQuery);
if (!me.emailVerified) {
router.push('/verify-email');
} else {
await client.request(sendChangePasswordEmailMutation, { callbackUrl });
} Prevention
- Gate password-change UI on currentUser.emailVerified
- Send the verification email immediately after sign-up so the flag gets set early
- Seed dev/test users with emailVerified true unless the test targets this error
When it happens
Trigger: Calling the sendChangePasswordEmail mutation while authenticated as a user whose emailVerified flag is false (never completed verifyEmail).
Common situations: User registered but never clicked the verification link; dev/test seeded users created without the verified flag; the original verification email failed to send or landed in spam.
Related errors
AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18).
Data as JSON: /api/errors/46b7c214aae75642.
Report an issue: GitHub.
Appendix: source
Thrown at packages/backend/server/src/core/auth/resolver.ts:170
await this.auth.sendNotificationChangeEmail(email);
return user;
}
@Mutation(() => Boolean)
async sendChangePasswordEmail(
@CurrentUser() user: CurrentUser,
@Args('callbackUrl') callbackUrl: string,
@Args('email', {
type: () => String,
nullable: true,
deprecationReason: 'fetched from signed in user',
})
_email: string | undefined,
@Context() context: GraphqlContext
) {
if (!user.emailVerified) {
throw new EmailVerificationRequired();
}
const { token, expiresAt } =
await this.models.verificationToken.createWithExpiresAt(
TokenType.ChangePassword,
user.id
);
const url = this.url.safeLink(callbackUrl, { userId: user.id, token });
return await this.auth.sendChangePasswordEmail(
user.email,
url,
this.mailMetadata(context, expiresAt)
);
}
@Mutation(() => Boolean)View on GitHub (pinned to b4c8548c09)