Skip to content

Feat/request timeout retry - #1072

Open
Nithin0620 wants to merge 2 commits into
resend:canaryfrom
Nithin0620:feat/request-timeout-retry
Open

Feat/request timeout retry#1072
Nithin0620 wants to merge 2 commits into
resend:canaryfrom
Nithin0620:feat/request-timeout-retry

Conversation

@Nithin0620

@Nithin0620 Nithin0620 commented Aug 16, 2026

Copy link
Copy Markdown

Summary

Adds three optional request controls to the SDK, all opt-in and backward compatible
(no behavior change unless you set them):

  • timeoutMs — abort a request attempt after N ms (per attempt, so retries still get a fair shot)
  • retries — retry retryable failures (HTTP 429, 5xx, network errors) with exponential backoff, honoring the Retry-After header
  • signal — pass any AbortSignal to cancel a request
    Set them globally in the constructor or per call:
const resend = new Resend('re_xxx', { timeoutMs: 10_000, retries: 3 });
// per-call override
await resend.emails.send({ ... }, { timeoutMs: 30_000, retries: 1 });

Why

Currently a request can hang indefinitely and rate limits / transient 5xx fail
immediately with no retry. These options bring the SDK in line with how the other
official Resend SDKs and major API clients behave.

Notes

  • Timeouts and aborts are not retried.
  • retries defaults to 0 — retrying a POST (e.g. emails.send) is the caller's
    explicit choice, so idempotency-sensitive flows are unaffected by default.
  • No new dependencies, no API surface removed.

Tests

  • 5 new unit tests (src/resend.spec.ts): timeout abort, AbortSignal cancel,
    429 retry with Retry-After, 5xx retry, no retry on 4xx, per-request override.
  • pnpm test (394 passed), pnpm typecheck, pnpm lint all green.

Commits

  1. feat: add timeout, retries, and AbortSignal options to requests — the feature core (10 files)
  2. fix: forward per-request options in list methods — 21 list methods were silently
    dropping per-request options (get(url) → get(url, options)); this makes
    emails.list({ retries: 2 }) actually work. The bulk of the file count is this
    one-line mechanical change.

Summary by cubic

Adds opt-in request controls to the SDK—timeoutMs, retries, and signal—and fixes list methods to forward per-request options. Previously requests could hang and never retried 429/5xx/network errors; now, when configured, requests abort on timeout and retry resiliently while honoring Retry-After.

  • New behavior: per-attempt timeout via AbortSignal, retries for 429/5xx/network errors with exponential backoff and jitter; Retry-After respected; timeouts and aborts are not retried; default retries is 0.
  • API changes: new RequestOptions interface added and included in GetOptions, PostOptions, PutOptions, PatchOptions, and PaginationOptions. Options can be set in the constructor or per call; README documents usage.
  • Mechanical fix: all list methods now pass options to this.resend.get(...) so per-request controls apply to list endpoints.
  • Implementation notes: core logic is in src/resend.ts (fetchRequest, performRequest, buildErrorResponse, parseRetryAfter); review timeout handling and cleanup of abort listeners.
  • Tests: unit coverage for timeout, manual abort, 429 with Retry-After, 5xx retry, no retry on 4xx, and per-request override.
  • Migration: none required. To opt in, set timeoutMs, retries, or signal globally or per request.

Written for commit 27edc21. Summary will update on new commits.

Review in cubic

@Nithin0620
Nithin0620 requested a review from a team as a code owner August 16, 2026 10:58
@Nithin0620
Nithin0620 requested review from vieiralucas and removed request for a team August 16, 2026 10:58
@github-actions github-actions Bot added the linear-synced PR has been synced to Linear label Aug 17, 2026
@gabrielmfern
gabrielmfern changed the base branch from main to canary August 17, 2026 12:49
@gabrielmfern

gabrielmfern commented Aug 17, 2026

Copy link
Copy Markdown
Member

Thank you so much for this; this looks really interesting!

Three things that come to mind:

  • I think we can consider this a breaking change as-is, since it changes how certain requests are handled and would override whatever handling the users would be doing normally, like if they already have their own retry logic. We should be making this opt-in first, then opt-out on a major version
  • What's the use case for timeoutMs? If there isn't one, I think we'd rather not have it
  • There is more than one feature in this pull request: custom timeout, retrying, request canceling. Can you make separate pull requests for each so that it's easier to review?

@Nithin0620

Copy link
Copy Markdown
Author

Thanks for the feedback! Makes total sense.

  1. timeoutMs: I agree with dropping it. Supporting signal (AbortSignal) covers timeouts natively via AbortSignal. timeout(ms).
  2. Splitting the PR: I will update this PR to focus only on request cancellation (signal) and remove the other parts.
  3. Retries (Next PR): I'll open a separate PR for the retry feature and make it opt-in (e.g., autoRetry: false by default with a config option to enable it) so it's not a breaking change for existing users.

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

Labels

linear-synced PR has been synced to Linear

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants