RocketChat/Rocket.Chat · error · Error
error-invalid-token
Error message
error-invalid-token
What it means
verifyEmail throws a plain Error('error-invalid-token') — not a Meteor.Error — at four sites. At line 5-7 the guard is user.services?.email?.verificationTokens?.length === 0: the account has an empty token array, so no verification token was ever issued and any supplied token is meaningless. Quirk: if verificationTokens is undefined (property missing), this guard passes and the failure surfaces at the next one instead.
Source
Thrown at apps/meteor/server/lib/users/verifyEmail.ts:6
import type { IUser } from '@rocket.chat/core-typings';
import { Users } from '@rocket.chat/models';
export async function verifyEmail(user: Pick<IUser, '_id' | 'services' | 'emails'>, token: string): Promise<boolean> {
if (user.services?.email?.verificationTokens?.length === 0) {
throw new Error('error-invalid-token');
}
const tokenRecord = await user.services?.email?.verificationTokens?.find((t) => t.token === token);
if (!tokenRecord) {
throw new Error('error-invalid-token');
}
const emailsRecord = user.emails?.find((e) => e.address === tokenRecord.address);
if (!emailsRecord) {
throw new Error('error-invalid-token');
}
const result = await Users.verifyEmailByAddress(user._id, tokenRecord.address);
if (result.modifiedCount === 0) {
throw new Error('error-invalid-token');
}
View on GitHub (pinned to b2c16d5842)
Solutions
- Mint a fresh token first: call Accounts.sendVerificationEmail(userId) and verify with the new link
- In tests, populate the fixture: services.email.verificationTokens = [{ token, address, when }]
- If users are provisioned programmatically, add token issuance to the provisioning script
Example fix
// before await verifyEmail(user, token); // user has verificationTokens: [] -> error-invalid-token // after await Accounts.sendVerificationEmail(user._id); const fresh = await Users.findOneById(user._id); const newToken = fresh.services.email.verificationTokens.at(-1).token; await verifyEmail(fresh, newToken);
Defensive patterns
Strategy: validation
Validate before calling
const tokens = user.services?.email?.verificationTokens;
if (!tokens || tokens.length === 0) {
// no token to verify against: trigger a resend instead of calling verifyEmail
} Type guard
const hasVerificationTokens = (u: Pick<IUser, 'services'>): boolean => Array.isArray(u.services?.email?.verificationTokens) && u.services!.email!.verificationTokens.length > 0;
Try / catch
try {
await verifyEmail(user, token);
} catch (error) {
if ((error as Error).message === 'error-invalid-token') {
// note: plain Error, not Meteor.Error — check .message, not .error
} else {
throw error;
}
} Prevention
- Ensure Accounts.sendVerificationEmail runs in your registration pipeline
- Test fixtures must include a populated verificationTokens array
- Check token existence before calling verifyEmail — the function gives no detail about which of its four guards fired
When it happens
Trigger: Calling verifyEmail for a user created without verification tokens: admin-created accounts, OAuth-provisioned accounts, users created before Accounts_EmailVerification was enabled, or tokens cleared by a later flow.
Common situations: Custom registration pipelines that skip Accounts.sendVerificationEmail; tests with hand-built user fixtures that have services.email but no verificationTokens array; migrations dropping nested services fields.
Related errors
- error-invalid-token
- error-action-not-allowed
- error-invalid-user
- error-invalid-payload
- error-invalid-user
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/ce659995d1b3f6ae.
Report an issue: GitHub.