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

  1. Complete email verification first: call sendVerifyEmail and then verifyEmail with the token from the link
  2. When seeding dev users, set emailVerified/emailVerifiedAt directly so test flows skip this gate
  3. 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

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)