You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I need to update a contact's segments at once. Given that there's no request that allows me to set them all at once, I'm forced to call resend.Contacts.Segments.add(params) and resend.Contacts.Segments.remove(params) multiple times in a row, which will often cause a RateLimitError.
It would be nice to read the retry-after attribute from the response header in order to know how much I'll have to wait before retrying. But currently the SDK doesn't expose any of the header attributes in the exception object.
I see that #179 goes well in the direction of adding the response header to the returned objects. It would be nice have it added to the ResendError class as well.
I know this is a lot to ask, but even better would be to have the retry logic embedded in the SDK itself as an optional feature. The SDK could track the ratelimit-remaining and ratelimit-reset attributes from the responses of previously successful requests, and then avoid the rate limit error altogether by waiting in advance when the user runs out of remaining requests within the current window.
Actually, this problem can be nicely solved by using a custom HTTP client. It's possible to use an HTTP adapter that respects the retry-after interval in case of error 429. The custom client I came up with also tracks the ratelimit-remaining and ratelimit-reset to avoid that error altogether, as I had suggested.
importtimefromdataclassesimportdataclassfromhttpimportHTTPMethodfromtypingimportCollection, Dict, List, Mapping, Optional, Self, Tuple, Unionimportrequestsimportresendfromrequests.adaptersimportHTTPAdapterfromrequests.structuresimportCaseInsensitiveDictfromurllib3.util.retryimportRetry@dataclass(frozen=True)classRateLimitState:
limit: int=0remaining: int=0next_reset: float=0.0next_retry: float=0.0@propertydefwait_seconds(self) ->float:
returnself.get_wait_seconds()
defget_wait_seconds(self, now: Optional[float] =None) ->float:
now=nowortime.time()
ifself.next_retry>now:
returnself.next_retry-nowifself.remaining==0andself.next_reset>now:
returnself.next_reset-nowreturn0@classmethoddeffrom_headers(cls, headers: CaseInsensitiveDict, now: Optional[float] =None) ->Self:
limit=int(headers.get("ratelimit-limit", "0")) # Maximum number of requests allowed within a window.remaining=int(headers.get("ratelimit-remaining", "0")) # How many requests you have left within the current window.reset=float(headers.get("ratelimit-reset", "0")) # How many seconds until the limits are reset.retry_after=float(headers.get("retry-after", "0")) # How many seconds you should wait before making a follow-up request.now=nowortime.time()
next_reset=now+resetnext_retry=now+retry_afterreturncls(
limit=limit,
remaining=remaining,
next_reset=next_reset,
next_retry=next_retry,
)
classResendCustomClient(resend.HTTPClient):
timeout: intsession: requests.Sessionrate_limit_state: RateLimitStatedef__init__(
self,
timeout: int=30,
session: Optional[requests.Session] =None,
):
self.timeout=timeoutself.session=self._http_retry_session(session=session)
self.rate_limit_state=RateLimitState()
defrequest(
self,
method: str,
url: str,
headers: Mapping[str, str],
json: Optional[Union[Dict[str, object], List[object]]] =None,
) ->Tuple[bytes, int, Mapping[str, str]]:
wait_seconds=self.rate_limit_state.wait_secondsifwait_seconds:
time.sleep(wait_seconds)
try:
response=self.session.request(
method=method,
url=url,
headers=headers,
json=json,
timeout=self.timeout,
)
self.rate_limit_state=RateLimitState.from_headers(response.headers)
returnresponse.content, response.status_code, response.headersexceptrequests.RequestExceptionase:
# This gets caught by the request.perform() method# and raises a ResendError with the error type "HttpClientError"ife.response:
self.rate_limit_state=RateLimitState.from_headers(e.response.headers)
raiseRuntimeError(f"Request failed: {e}") frome@staticmethoddef_http_retry_session(
retries: int=3,
backoff_factor: float=0.3,
status_forcelist: Collection[int] = (500, 502, 503, 504),
session: Optional[requests.Session] =None,
):
session=sessionorrequests.Session()
retry=Retry(
total=retries,
read=retries,
connect=retries,
backoff_factor=backoff_factor,
status_forcelist=status_forcelist,
respect_retry_after_header=True,
allowed_methods=[
HTTPMethod.HEAD,
HTTPMethod.TRACE,
HTTPMethod.GET,
HTTPMethod.PUT,
HTTPMethod.OPTIONS,
HTTPMethod.DELETE,
HTTPMethod.POST,
],
)
adapter=HTTPAdapter(max_retries=retry)
session.mount("http://", adapter)
session.mount("https://", adapter)
returnsessionresend.default_http_client=ResendCustomClient()
@andre-vm thanks for opening this issue and sharing your use case, I will prioritize getting the header data exposed on responses as well as in ResendError so that would simplify things for you. As for the automatic retry on errors: there are some pros and cons to automatically retrying errors on the SDK level - I will bring that up with the core team
@drish it seems that the PR #179 has been merged, however, when I update the package, I can't seem to get access to the newly exposed rate limit headers.
Am I doing something wrong here?
Activity
andre-vm commentedon Feb 13, 2026
Actually, this problem can be nicely solved by using a custom HTTP client. It's possible to use an HTTP adapter that respects the
retry-afterinterval in case of error 429. The custom client I came up with also tracks theratelimit-remainingandratelimit-resetto avoid that error altogether, as I had suggested.drish commentedon Feb 16, 2026
@andre-vm thanks for opening this issue and sharing your use case, I will prioritize getting the header data exposed on responses as well as in
ResendErrorso that would simplify things for you. As for the automatic retry on errors: there are some pros and cons to automatically retrying errors on the SDK level - I will bring that up with the core teamhasancemcerit commentedon Feb 23, 2026
@drish it seems that the PR #179 has been merged, however, when I update the package, I can't seem to get access to the newly exposed rate limit headers.
Am I doing something wrong here?
drish commentedon Feb 23, 2026
hey! I have just released 2.23.0 which should have the rate limit headers, let me know if anything comes up!
thanks @andre-vm @hasancemcerit