koala73/worldmonitor · error · ConvexError
INVITEE_DOMAIN_MISMATCH
INVITEE_DOMAIN_MISMATCH
Error message
INVITEE_DOMAIN_MISMATCH
What it means
Thrown by `inviteSeats` per-email when an invitee's domain differs from the owner's domain (`sameDomain` returns false). Business seats are restricted to the SAME corporate domain as the owner, preventing cross-company seat sharing. Object-typed ConvexError (`{ kind: "INVITEE_DOMAIN_MISMATCH" }`). Fires after the corporate-domain check, so both addresses are corporate but on different companies.
Source
Thrown at convex/payments/businessSeats.ts:188
// Separate new invites from duplicates before the cap check so a duplicate
// re-invite is idempotent even when the cap is full.
const newEmails: string[] = [];
const results: Array<{
email: string;
grantId: string;
status: "created" | "already_pending" | "already_accepted";
}> = [];
for (const email of emails) {
if (email === normalizedOwnerEmail) {
throw new ConvexError({ kind: "CANNOT_INVITE_SELF" });
}
if (!isCorporateDomain(email)) {
throw new ConvexError({ kind: "INVITEE_DOMAIN_NOT_CORPORATE" });
}
if (!sameDomain(ownerEmail, email)) {
throw new ConvexError({ kind: "INVITEE_DOMAIN_MISMATCH" });
}
const existing = existingByEmail.get(email);
if (existing) {
if (existing.status === "accepted") {
results.push({ email, grantId: existing._id, status: "already_accepted" });
continue;
}
if (existing.status === "pending" && existing.expiresAt > now) {
results.push({ email, grantId: existing._id, status: "already_pending" });
continue;
}
}
newEmails.push(email);
}
const currentCount = await countActiveOrPendingGrants(
ctx,View on GitHub (pinned to ffec79ac33)
Solutions
- Only invite emails on the same domain as the owner's account email.
- Client-side, enforce `sameDomain(ownerEmail, inviteeEmail)` before submit.
- If cross-company collaboration is intended, that is intentionally unsupported — use a separate Business subscription per company.
Example fix
// before
await inviteSeats({ emails: ["teammate@othercorp.com"] }); // owner is @acme.com
// after
await inviteSeats({ emails: ["teammate@acme.com"] }); Defensive patterns
Strategy: validation
Validate before calling
const ownerDomain = ownerEmail.split("@").pop()!.toLowerCase();
for (const e of emails) {
const d = (e.split("@").pop() ?? "").toLowerCase();
if (d !== ownerDomain) {
showToast(`${e} must be on @${ownerDomain}.`);
return;
}
}
await inviteSeats({ emails }); Type guard
function sameDomain(owner: string, invitee: string): boolean {
const a = owner.split("@").pop()?.toLowerCase();
const b = invitee.split("@").pop()?.toLowerCase();
return !!a && !!b && a === b;
} Prevention
- Pre-fill the company domain and only allow emails matching it.
- Reject cross-domain invitees client-side before submit.
When it happens
Trigger: Owner on `@acme.com` invites `teammate@othercorp.com` — both are valid corporate domains but they differ. Any invitee whose domain doesn't match the owner's.
Common situations: Owner invites a contractor/partner at another company; a teammate who uses their previous employer's email; merge of two companies where emails still differ; typo in the domain portion.
Related errors
- OWNER_DOMAIN_NOT_CORPORATE
- INVITEE_DOMAIN_NOT_CORPORATE
- OWNER_EMAIL_UNAVAILABLE
- OWNER_NOT_BUSINESS
- NO_EMAILS_PROVIDED
AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12).
Data as JSON: /api/errors/dc70ccac1aeef7ec.
Report an issue: GitHub.