koala73/worldmonitor · error · ConvexError

INVITEE_DOMAIN_MISMATCH

INVITEE_DOMAIN_MISMATCH

Error message

INVITEE_DOMAIN_MISMATCH

What it means

Generic sentinel thrown by the business-seat invite loop when an invitee email fails the corporate-domain check (isCorporateDomain). The invite is rejected before any seat is granted because the workspace only accepts addresses on the company domain; the input at fault is the individual `email` element currently being iterated, not the whole batch.

Solutions

  1. Filter or correct the offending email addresses client-side so every invitee is on the corporate domain before submitting the batch
  2. Surface the offending email to the user so they know which invite entry to fix or remove
  3. If the domain should be allowed, update the corporate-domain configuration used by isCorporateDomain and retry
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at convex/payments/businessSeats.ts:188 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-08-21). Data as JSON: /api/errors/dc70ccac1aeef7ec. Report an issue: GitHub.

Appendix: source

Thrown at convex/payments/businessSeats.ts:187

    // 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" });
      }

      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,
      businessSub.dodoSubscriptionId,
      now,
    );

View on GitHub (pinned to 7d06c8633d)