{"record":{"id":"5cbca9f34fc0e181","repo":"koala73/worldmonitor","slug":"rate-limited","errorCode":"rate_limited","errorMessage":"Too many recent submissions for this email; try again later.","messagePattern":"Too many recent submissions for this email; try again later\\.","errorType":"error_code","errorClass":"ConvexError","httpStatus":null,"severity":"error","filePath":"convex/contactMessages.ts","lineNumber":88,"sourceCode":"        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) {\n      throw new ConvexError({\n        kind: \"rate_limited\",\n        message: \"Too many recent submissions for this email; try again later.\",\n      });\n    }\n\n    await ctx.db.insert(\"contactMessages\", {\n      name,\n      email,\n      organization,\n      phone,\n      message,\n      source,\n      receivedAt: Date.now(),\n      normalizedEmail,\n    });\n    return { status: \"sent\" as const };\n  },\n});","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/convex/contactMessages.ts#L70-L106","documentation":"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.","triggerScenarios":"Thrown at convex/contactMessages.ts:91 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Wait for the throttle window to elapse before submitting again","If genuine users hit it, tune PER_EMAIL_LIMIT and PER_EMAIL_WINDOW_MS to the real traffic pattern"],"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"}