koala73/worldmonitor · error · ConvexError
NO_EMAILS_PROVIDED
NO_EMAILS_PROVIDED
Error message
NO_EMAILS_PROVIDED
What it means
Thrown by `inviteSeats` when, after trimming, lowercasing, deduplicating, and filtering empties, the `emails` array is empty. The handler normalizes the input first, so this fires only when every supplied email was blank. Object-typed ConvexError (`{ kind: "NO_EMAILS_PROVIDED" }`).
Source
Thrown at convex/payments/businessSeats.ts:154
if (!ownerDomain || !isCorporateDomain(ownerEmail)) {
throw new ConvexError({ kind: "OWNER_DOMAIN_NOT_CORPORATE" });
}
// Serialize concurrent inviteSeats / removeSeat calls for this Business
// subscription so the cap check below cannot be bypassed by a race.
await touchBusinessSeatLock(ctx, businessSub.dodoSubscriptionId, now);
const normalizedOwnerEmail = ownerEmail.toLowerCase();
// Dedupe: the existingByEmail check below only guards against emails
// that already had a grant BEFORE this call — a duplicate within the
// SAME args.emails array would otherwise slip past it (neither
// occurrence is in existingGrants yet) and create two grant rows for
// one invitee.
const emails = Array.from(
new Set(args.emails.map((e) => e.trim().toLowerCase()).filter((e) => e.length > 0)),
);
if (emails.length === 0) {
throw new ConvexError({ kind: "NO_EMAILS_PROVIDED" });
}
if (emails.length > MAX_SEATS) {
throw new ConvexError({ kind: "TOO_MANY_EMAILS" });
}
// Check existing grants for duplicates (active or pending).
const existingGrants = await ctx.db
.query("businessProGrants")
.withIndex("by_businessSubscriptionId", (q) =>
q.eq("businessSubscriptionId", businessSub.dodoSubscriptionId),
)
.collect();
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.View on GitHub (pinned to ffec79ac33)
Solutions
- Validate the emails array is non-empty before submitting the mutation.
- Disable the invite button until at least one non-blank email is entered.
- Filter blank entries client-side and require `length >= 1`.
Example fix
// before
await inviteSeats({ emails: rawEmails }); // rawEmails = ["", " "]
// after
const emails = rawEmails.map(e => e.trim()).filter(Boolean);
if (emails.length === 0) return;
await inviteSeats({ emails }); Defensive patterns
Strategy: validation
Validate before calling
const emails = rawEmails.map(e => e.trim().toLowerCase()).filter(Boolean);
if (emails.length === 0) {
showToast("Add at least one email.");
return;
}
await inviteSeats({ emails }); Type guard
function hasEmails(arr: string[]): boolean {
return arr.map(e => e.trim()).filter(e => e.length > 0).length > 0;
} Prevention
- Disable the invite button until at least one non-blank email is entered.
- Trim and filter blanks client-side before calling.
When it happens
Trigger: Calling `inviteSeats({ emails: [] })`, or with an array of only whitespace/empty strings (e.g. `["", " "]`).
Common situations: The invite form submitted with no recipients entered; a CSV paste of blank lines; the UI forwarded an unvalidated empty list.
Related errors
- OWNER_EMAIL_UNAVAILABLE
- OWNER_NOT_BUSINESS
- OWNER_DOMAIN_NOT_CORPORATE
- TOO_MANY_EMAILS
- CANNOT_INVITE_SELF
AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12).
Data as JSON: /api/errors/4dbdeafd392e4250.
Report an issue: GitHub.