koala73/worldmonitor · error · ConvexError
Valid email is required
Error message
Valid email is required
What it means
Input validation in the contactMessages submit handler: args.email fails the email-format check. Bouncing malformed addresses before insert prevents junk rows and protects downstream email/LLM processing.
Solutions
- Correct the email address in the form — the check requires a valid email shape
- Add inline email-format validation in the UI before submit
Defensive patterns
Strategy: validation
When it happens
Trigger: Thrown at convex/contactMessages.ts:69 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/6fd434d7a9a3622c.
Report an issue: GitHub.
Appendix: source
Thrown at convex/contactMessages.ts:66
organization: v.optional(v.string()),
phone: v.optional(v.string()),
message: v.optional(v.string()),
source: v.string(),
},
handler: async (ctx, args) => {
// Length / shape validation. Reject obviously-bogus input before
// it reaches the table — also a defence against prompt-injection
// payloads enormous enough to trip downstream LLM cost.
const name = clip(args.name, MAX_NAME);
const email = clip(args.email, MAX_EMAIL);
const organization = clip(args.organization, MAX_ORG);
const phone = clip(args.phone, MAX_PHONE);
const message = clip(args.message, MAX_MESSAGE, { preserveNewlines: true });
const source = clip(args.source, MAX_SOURCE) ?? "unknown";
if (!name) throw new ConvexError("Name is required");
if (!email || !EMAIL_RE.test(email)) {
throw new ConvexError("Valid email is required");
}
if (!isCorporateDomain(email)) {
throw new ConvexError({
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),
)View on GitHub (pinned to 7d06c8633d)