toeverything/AFFiNE · error · EmailTokenNotFound

email_token_not_found

email_token_not_found

Error message

The email token provided is not found.

What it means

Thrown by sendVerifyChangeEmail when the token argument is falsy. This is a client-precondition failure, distinct from invalid_email_token (185): the mutation was invoked without any token at all, usually because the emailed link did not carry one.

Solutions

  1. Make sure the callbackUrl passed to sendChangeEmail contains the token placeholder so safeLink injects it
  2. Check the query param name the page reads matches what the link emits
  3. Guard client-side: refuse to call the mutation when the token param is missing and show a 'bad link' page

Example fix

// before
await client.request(sendVerifyChangeEmailMutation, { token: params.get('token') ?? '', email, callbackUrl });

// after
const token = params.get('token');
if (!token) throw new Error('Link is missing its token — check the callbackUrl template');
await client.request(sendVerifyChangeEmailMutation, { token, email, callbackUrl });
Defensive patterns

Strategy: validation

Validate before calling

const token = new URLSearchParams(location.search).get('token');
if (!token) {
  renderBadLinkPage('This link is missing its token. Request a new email.');
} else {
  await client.request(sendVerifyChangeEmailMutation, { token, email, callbackUrl });
}

Prevention

When it happens

Trigger: Calling sendVerifyChangeEmail with token: '' or undefined — typically the frontend parsed a URL query param named differently than the link template, or the callbackUrl template omitted the token placeholder so the emailed URL has no token.

Common situations: callbackUrl configured without the token substitution placeholder; router reads params.token but the link uses ?t=; a redirect/SSO wrapper strips query strings; user hand-edited the URL.

Related errors


AI-assisted analysis of toeverything/AFFiNE@2af30773ae (2026-08-18). Data as JSON: /api/errors/352692859c18c9d5. Report an issue: GitHub.

Appendix: source

Thrown at packages/backend/server/src/core/auth/resolver.ts:182

  ) {
    return await this.auth.prepareSecurityChallenge(
      'change_email',
      user.id,
      this.url.safeLink(callbackUrl),
      this.auth.requestSource(context.req)
    );
  }

  @Mutation(() => Boolean)
  async sendVerifyChangeEmail(
    @CurrentUser() user: CurrentUser,
    @Args('token') token: string,
    @Args('email') email: string,
    @Args('callbackUrl') callbackUrl: string,
    @Context() context: GraphqlContext
  ) {
    if (!token) {
      throw new EmailTokenNotFound();
    }

    validators.assertValidEmail(email);
    return await this.auth.prepareVerifyChangeEmail(
      user.id,
      token,
      email,
      this.url.safeLink(callbackUrl),
      this.auth.requestSource(context.req)
    );
  }

  @Mutation(() => Boolean)
  async sendVerifyEmail(
    @CurrentUser() user: CurrentUser,
    @Args('callbackUrl') callbackUrl: string,
    @Context() context: GraphqlContext
  ) {

View on GitHub (pinned to 2af30773ae)