koala73/worldmonitor · error · ConvexError
INVITEE_DOMAIN_NOT_CORPORATE
INVITEE_DOMAIN_NOT_CORPORATE
Error message
INVITEE_DOMAIN_NOT_CORPORATE
What it means
Thrown by `inviteSeats` per-email when an invitee's email is not on a corporate domain (`isCorporateDomain` returns false): missing domain, no dot, a free/consumer provider, or disposable. Business seats may only go to real company addresses. Object-typed ConvexError (`{ kind: "INVITEE_DOMAIN_NOT_CORPORATE" }`).
Source
Thrown at convex/payments/businessSeats.ts:185
const existingByEmail = new Map(
existingGrants.map((g) => [g.inviteeEmail, g]),
);
// 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);
}View on GitHub (pinned to ffec79ac33)
Solutions
- Replace the invitee address with their corporate-domain email (same company as the owner).
- Validate each invitee with `isCorporateDomain` client-side before submit.
- Reject disposable/free addresses in the form with a clear message.
Example fix
// before
await inviteSeats({ emails: ["teammate@gmail.com"] });
// after
await inviteSeats({ emails: ["teammate@acme.com"] }); Defensive patterns
Strategy: validation
Validate before calling
for (const e of emails) {
if (!isCorporateDomain(e)) {
showToast(`${e} is not a corporate email.`);
return;
}
}
await inviteSeats({ emails }); Type guard
function isCorporateDomain(email: string): boolean {
const domain = (email.split("@").pop() ?? "").toLowerCase();
return domain.includes(".") && !FREE_PROVIDERS.has(domain);
} Prevention
- Validate each invitee with the same corporate-domain rule the server uses.
- Reject free/disposable providers with a clear message.
When it happens
Trigger: The invite list contains a personal/free address (gmail, outlook, yahoo) or a disposable/temporary email, or a malformed address with no dotted domain.
Common situations: Owner typed a teammate's personal Gmail instead of their work address; a throwaway email; a typo producing an invalid domain; teammate uses a consumer provider.
Related errors
- OWNER_DOMAIN_NOT_CORPORATE
- INVITEE_DOMAIN_MISMATCH
- OWNER_EMAIL_UNAVAILABLE
- OWNER_NOT_BUSINESS
- NO_EMAILS_PROVIDED
AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12).
Data as JSON: /api/errors/015352ef981c16b1.
Report an issue: GitHub.