koala73/worldmonitor · error · ConvexError

rate_limited

rate_limited

Error message

Too many recent submissions for this email; try again later.

What it means

Per-email rate limit in the contactMessages submit handler: the normalized email already has PER_EMAIL_LIMIT submissions within PER_EMAIL_WINDOW_MS (queried via the by_normalized_email_received index). The throttle caps spam and abuse from a single address.

Solutions

  1. Wait for the throttle window to elapse before submitting again
  2. If genuine users hit it, tune PER_EMAIL_LIMIT and PER_EMAIL_WINDOW_MS to the real traffic pattern
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at convex/contactMessages.ts:91 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/5cbca9f34fc0e181. Report an issue: GitHub.

Appendix: source

Thrown at convex/contactMessages.ts:88

        kind: "FREE_EMAIL_NOT_ALLOWED",
        message: "Please use a corporate email address.",
      });
    }

    const normalizedEmail = email.toLowerCase();

    // Throttle: cap recent submissions per email. Index lookup keeps this O(matches),
    // which the limit caps at PER_EMAIL_LIMIT + 1.
    const windowStart = Date.now() - PER_EMAIL_WINDOW_MS;
    const recent = await ctx.db
      .query("contactMessages")
      .withIndex("by_normalized_email_received", (q) =>
        q.eq("normalizedEmail", normalizedEmail).gte("receivedAt", windowStart),
      )
      .take(PER_EMAIL_LIMIT + 1);

    if (recent.length >= PER_EMAIL_LIMIT) {
      throw new ConvexError({
        kind: "rate_limited",
        message: "Too many recent submissions for this email; try again later.",
      });
    }

    await ctx.db.insert("contactMessages", {
      name,
      email,
      organization,
      phone,
      message,
      source,
      receivedAt: Date.now(),
      normalizedEmail,
    });
    return { status: "sent" as const };
  },
});

View on GitHub (pinned to 7d06c8633d)