koala73/worldmonitor · error · ConvexError
INVITE_EMAIL_MISMATCH
INVITE_EMAIL_MISMATCH
Error message
INVITE_EMAIL_MISMATCH
What it means
After token verification, `acceptBusinessInvite` requires the signed-in Clerk email to exactly match (case-insensitively, after trim+lowercase) the `inviteeEmail` stored on the grant. An exact mismatch throws INVITE_EMAIL_MISMATCH. This is the primary identity-binding check — the invite is personal to the invited address.
Source
Thrown at convex/payments/businessSeats.ts:478
throw new ConvexError({ kind: "INVITEE_EMAIL_UNAVAILABLE" });
}
const grant = await ctx.db.get(args.grantId);
if (!grant) {
throw new ConvexError({ kind: "GRANT_NOT_FOUND" });
}
if (grant.status !== "pending") {
throw new ConvexError({ kind: "INVITE_ALREADY_USED" });
}
const now = Date.now();
if (grant.expiresAt <= now) {
throw new ConvexError({ kind: "INVITE_EXPIRED" });
}
if (!(await verifyBusinessInviteToken(args.grantId, args.token))) {
throw new ConvexError({ kind: "INVALID_INVITE_TOKEN" });
}
if (grant.inviteeEmail !== inviteeEmail) {
throw new ConvexError({ kind: "INVITE_EMAIL_MISMATCH" });
}
if (!sameDomain(grant.inviteeEmail, inviteeEmail)) {
throw new ConvexError({ kind: "INVITE_EMAIL_MISMATCH" });
}
if (!isCorporateDomain(inviteeEmail)) {
throw new ConvexError({ kind: "INVITEE_DOMAIN_NOT_CORPORATE" });
}
const businessSub = await ctx.db
.query("subscriptions")
.withIndex("by_dodoSubscriptionId", (q) =>
q.eq("dodoSubscriptionId", grant.businessSubscriptionId),
)
.unique();
if (!businessSub || businessSub.planKey !== "api_business" || !isCoveringAt(businessSub, now)) {
throw new ConvexError({ kind: "BUSINESS_NOT_ACTIVE" });
}
View on GitHub (pinned to ffec79ac33)
Solutions
- Sign in with the exact email address the invite was sent to
- Ask the owner to re-invite the address you actually sign in with
Defensive patterns
Strategy: validation
Validate before calling
// Before accepting, ensure the signed-in email matches the invited one.
const invited = grant.inviteeEmail;
const signedIn = (clerk.user?.primaryEmailAddress?.emailAddress ?? '').trim().toLowerCase();
if (signedIn !== invited) {
// prompt user to sign in with the invited address
} Type guard
function emailMatchesInvite(invited: string, signedIn: string | null | undefined): boolean {
return Boolean(signedIn) && signedIn!.trim().toLowerCase() === invited.trim().toLowerCase();
} Try / catch
try {
await convex.mutation(api.payments.businessSeats.acceptBusinessInvite, { grantId, token });
} catch (err) {
if (err.data?.kind === 'INVITE_EMAIL_MISMATCH') {
// prompt to sign in with the exact invited address
} else { throw err; }
} Prevention
- Show which email the invite was sent to and require signing in with that exact address
- Avoid plus-addressing differences between invite and login
- Normalize (trim+lowercase) emails before comparison on the client
When it happens
Trigger: Accepting an invite sent to alice@corp.com while signed in as alice.alt@corp.com or any other distinct address. The token may be valid, but the signed-in identity differs from the invited one.
Common situations: Invite sent to one alias (e.g., a distribution list or HR address) but the user logs in with their personal corporate alias; plus-addressing differences (alice+pro@corp.com vs alice@corp.com).
Related errors
- INVITEE_EMAIL_UNAVAILABLE
- INVITE_ALREADY_USED
- INVITE_EXPIRED
- INVALID_INVITE_TOKEN
- OWNER_EMAIL_UNAVAILABLE
AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12).
Data as JSON: /api/errors/4e569a45fe4ceb3e.
Report an issue: GitHub.