Skip to content

add configurable rate limit#53

Open
sethconvex wants to merge 1 commit intomainfrom
seth/configurable-rate-limit
Open

add configurable rate limit#53
sethconvex wants to merge 1 commit intomainfrom
seth/configurable-rate-limit

Conversation

@sethconvex
Copy link
Copy Markdown
Contributor

@sethconvex sethconvex commented Dec 5, 2025

Fixes #38
Allows users to specify if Resend has increased their rate limit.

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Summary by CodeRabbit

  • Documentation

    • Getting Started guide now includes rate limiting configuration details, examples, and default settings in the Advanced Usage section
  • New Features

    • Introduced configurable rate limiting to control request throughput. Users can now customize the number of requests per second to optimize performance and ensure compatibility with their application requirements (default: 2 requests per second)

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Dec 5, 2025

Walkthrough

This pull request introduces a configurable rateLimitPerSecond option to the Resend component, allowing users to customize the rate limit from the default 2 requests per second. The option is threaded through the configuration system, validator schema, and applied to delay calculations in the rate limiter.

Changes

Cohort / File(s) Summary
Configuration & Options
README.md, src/client/index.ts
Added rateLimitPerSecond?: number to ResendOptions interface; updated documentation with the new option, default value (2 r/s), and configuration example; getDefaultConfig() returns rateLimitPerSecond: 2 and configToRuntimeConfig() propagates the value to runtime config
Validator Schema
src/component/shared.ts
Extended vOptions validator with optional rateLimitPerSecond: v.optional(v.number()) field, expanding the RuntimeConfig type shape
Rate Limiter Logic
src/component/lib.ts
Replaced hard-coded per-call interval with configurable rate limiting; introduced DEFAULT_RATE_LIMIT_PER_SECOND = 2 constant; updated getDelay(ctx, rateLimitPerSecond) to compute period based on rate limit value; makeBatch() and getDelay() now use supplied rateLimitPerSecond with fallback to default for backward compatibility
Test Configuration
src/component/setup.test.ts
Added rateLimitPerSecond: 2 to test runtime configuration object

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • src/component/lib.ts: Review the delay calculation logic (periodMs = max(1, floor(1000 / rateLimitPerSecond))) and backward-compatibility fallback mechanisms in makeBatch() and getDelay() to ensure correct rate limiting behavior
  • Configuration threading: Verify that rateLimitPerSecond flows correctly from ResendOptions through getDefaultConfig() and configToRuntimeConfig() to the rate limiter implementation
  • Validator schema: Confirm that the optional schema addition in vOptions properly extends RuntimeConfig without breaking existing configurations

Poem

🐰 A tweak to the limit, oh what a sight,
From hard-coded chains to configurable flight!
Two hops per second, or a hundred more,
The Resend burrows now have choice galore! 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'add configurable rate limit' accurately summarizes the main change in the PR, which introduces a new rateLimitPerSecond configuration option throughout the codebase.
Linked Issues check ✅ Passed The PR fulfills issue #38 by implementing a configurable rateLimitPerSecond option that allows users to override the default 2 requests/second rate limit, addressing the request for external configurability.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the configurable rate limit feature; no unrelated modifications or out-of-scope changes were detected.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch seth/configurable-rate-limit

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Dec 5, 2025

Open in StackBlitz

npm i https://pkg.pr.new/get-convex/resend/@convex-dev/resend@53

commit: 8981cd1

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/component/shared.ts (1)

40-40: Consider adding validation for rateLimitPerSecond bounds.

The validator accepts any number, including zero, negative values, or extremely large values that could cause issues. In lib.ts, Math.floor(1000 / rateLimitPerSecond) would result in Infinity for zero, negative period for negative values, or a 0ms period for very large values.

Consider adding bounds validation here or in the client constructor:

-  rateLimitPerSecond: v.optional(v.number()),
+  rateLimitPerSecond: v.optional(v.number()),

And validate in the client:

if (rateLimitPerSecond !== undefined && rateLimitPerSecond <= 0) {
  throw new Error("rateLimitPerSecond must be a positive number");
}
src/client/index.ts (1)

238-239: Consider adding input validation in the constructor.

This is where invalid values (≤0) should be caught before they propagate to the rate limiter.

       rateLimitPerSecond:
         options?.rateLimitPerSecond ?? defaultConfig.rateLimitPerSecond,
     };
+    if (this.config.rateLimitPerSecond <= 0) {
+      throw new Error("rateLimitPerSecond must be a positive number");
+    }
     if (options?.onEmailEvent) {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9c380e6 and 8981cd1.

📒 Files selected for processing (5)
  • README.md (1 hunks)
  • src/client/index.ts (4 hunks)
  • src/component/lib.ts (5 hunks)
  • src/component/setup.test.ts (1 hunks)
  • src/component/shared.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/component/lib.ts (2)
src/component/_generated/api.ts (1)
  • components (54-55)
src/component/shared.ts (2)
  • RunMutationCtx (173-175)
  • RunQueryCtx (170-172)
🔇 Additional comments (8)
src/component/setup.test.ts (1)

153-159: LGTM!

The test configuration correctly includes the new rateLimitPerSecond field with the default value of 2, maintaining consistency with the production default configuration.

src/component/lib.ts (4)

40-40: LGTM!

Good addition of the default constant. This centralizes the default value and provides clear documentation of Resend's default rate limit.


83-84: LGTM!

The rate limiter initialization is simplified by removing hardcoded configuration, now relying on dynamic configuration passed to getDelay.


439-444: Good backward compatibility handling.

The fallback to DEFAULT_RATE_LIMIT_PER_SECOND ensures existing database records without rateLimitPerSecond continue to work correctly during migration.


708-729: Rate limiting implementation is correct and library supports this pattern.

The @convex-dev/rate-limiter v0.3.x fully supports the fixed window config with dynamic period values as implemented here. The code correctly uses Math.max(1, ...) to prevent zero periods, and the dynamic period calculation properly maps rate limits to millisecond intervals (e.g., 100 req/s → 10ms, 2 req/s → 500ms). The configuration with rate: 1 and dynamic period matches the documented API exactly, and omitting the optional capacity field allows sensible defaults. No edge case issues.

src/client/index.ts (2)

42-51: LGTM!

The default configuration correctly includes rateLimitPerSecond: 2, matching Resend's default rate limit.


98-106: Good documentation.

The JSDoc clearly explains the option's purpose, default value, and use case for accounts with higher rate limits.

README.md (1)

166-174: LGTM!

Clear documentation with a practical code example. The explanation of the default value and when to change it is helpful for users.

@ianmacartney
Copy link
Copy Markdown
Member

ianmacartney commented Dec 5, 2025 via email

Comment thread src/component/lib.ts
Comment on lines 35 to 36
const EMAIL_POOL_SIZE = 4;
const CALLBACK_POOL_SIZE = 4;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These might prove insufficient if folks are trying to get 100RPS - I think a heuristic might be that the workpool can do at most 10x the pool size per second, but could be lower like 4x.. but I think we wait to get asked to raise this, at which point hopefully we can have a shared workpool with different queues for email sending & receiving..

Comment thread src/component/lib.ts
Comment on lines +722 to +723
period: periodMs,
rate: 1,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will effectively cause serialization of these mutations, making it impossible to do more than ~10 per second, and could get much slower. Let's do something like

const shards = Math.ceil(rateLimitPerSecond / 6);
const config = {
  shards,
  rate: rateLimitPerSecond,
  period: SECOND,
}

rationale:

  • higher rate is helpful when dividing amongst shards
  • this way each shard will have 5-6 capacity per shard, which makes it more likely you'll land on a shard with capacity (esp. with the choose two logic)
  • their enforcement period is a second, so let's just leverage that instead of having a tiny period

Copy link
Copy Markdown
Member

@ianmacartney ianmacartney left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs a bit of work to actually achieve the advertised parallelism

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Configurable Rate Limit

2 participants