{"record":{"id":"1710619a6e3da0a3","repo":"koala73/worldmonitor","slug":"free-email-not-allowed","errorCode":"FREE_EMAIL_NOT_ALLOWED","errorMessage":"Please use a corporate email address.","messagePattern":"Please use a corporate email address\\.","errorType":"validation","errorClass":"ConvexError","httpStatus":null,"severity":"error","filePath":"convex/contactMessages.ts","lineNumber":69,"sourceCode":"    source: v.string(),\n  },\n  handler: async (ctx, args) => {\n    // Length / shape validation. Reject obviously-bogus input before\n    // it reaches the table — also a defence against prompt-injection\n    // payloads enormous enough to trip downstream LLM cost.\n    const name = clip(args.name, MAX_NAME);\n    const email = clip(args.email, MAX_EMAIL);\n    const organization = clip(args.organization, MAX_ORG);\n    const phone = clip(args.phone, MAX_PHONE);\n    const message = clip(args.message, MAX_MESSAGE, { preserveNewlines: true });\n    const source = clip(args.source, MAX_SOURCE) ?? \"unknown\";\n\n    if (!name) throw new ConvexError(\"Name is required\");\n    if (!email || !EMAIL_RE.test(email)) {\n      throw new ConvexError(\"Valid email is required\");\n    }\n    if (!isCorporateDomain(email)) {\n      throw new ConvexError({\n        kind: \"FREE_EMAIL_NOT_ALLOWED\",\n        message: \"Please use a corporate email address.\",\n      });\n    }\n\n    const normalizedEmail = email.toLowerCase();\n\n    // Throttle: cap recent submissions per email. Index lookup keeps this O(matches),\n    // which the limit caps at PER_EMAIL_LIMIT + 1.\n    const windowStart = Date.now() - PER_EMAIL_WINDOW_MS;\n    const recent = await ctx.db\n      .query(\"contactMessages\")\n      .withIndex(\"by_normalized_email_received\", (q) =>\n        q.eq(\"normalizedEmail\", normalizedEmail).gte(\"receivedAt\", windowStart),\n      )\n      .take(PER_EMAIL_LIMIT + 1);\n\n    if (recent.length >= PER_EMAIL_LIMIT) {","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/convex/contactMessages.ts#L51-L87","documentation":"Business gate in the contactMessages submit handler: the supplied email is on a free/consumer email domain (ConvexError kind FREE_EMAIL_NOT_ALLOWED with this human-readable message). The contact form is intended for corporate inquiries, so consumer addresses are rejected before insert.","triggerScenarios":"Thrown at convex/contactMessages.ts:72 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Submit with a corporate or work email address instead of a consumer one","If legitimate users are blocked, review the free-domain blocklist for false positives"],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"7d06c8633d256c18e38133030bc3613976a96ec9","analyzedAt":"2026-08-21T16:51:25.751Z","contentChangedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}