resend / resend-laravel

Resend's Official Laravel SDK
https://resend.com/docs/send-with-laravel
MIT License
87 stars 5 forks source link

Ignored Resend rate limiting, Laravel failover configuration doesn't work #61

Open MrMarci666 opened 4 months ago

MrMarci666 commented 4 months ago

Intro

Hello there,

We experienced many issues with this package, because it is simply ignoring the rate limitation of Resend.

By default, Resend has a rate limit of 2 requests per second for API calls. 1 This rate limit is designed to manage the load on their API and ensure fair usage across all customers.

However, Resend understands that some users may need higher rate limits for their specific use cases. If you find that the default rate limit is not sufficient for your needs, you have the option to request an increase. To request a rate limit increase, you should contact Resend support. Here's what you need to know:

The default rate limit can be increased for trusted senders upon request. 2. If you have specific requirements that exceed the default limit, you should reach out to Resend support to discuss your needs. 3. You can contact Resend support through their website at https://resend.com/contact to request a rate increase. It's worth noting that Resend provides response headers that help you monitor your current rate limit usage. These headers include:

  • ratelimit-limit: Maximum number of requests allowed within a window
  • ratelimit-remaining: How many requests you have left within the current window
  • ratelimit-reset: How many seconds until the limits are reset
  • retry-after: How many seconds you should wait before making a follow-up request These headers can help you manage your API requests and avoid hitting the rate limit.

If you do exceed the rate limit, you'll receive a 429 response error code. To prevent this, Resend recommends implementing strategies such as introducing a queue mechanism or reducing the number of concurrent requests per second. Remember, while requesting a rate limit increase is possible, it's also important to optimize your API usage to work efficiently within the provided limits when possible.

We are using a "Failover" configuration in Laravel 11.

'failover' => [
  'transport' => 'failover',
  'mailers' => [
    'resend',
    'smtp',
    'log',
  ],
],

Expected behaviour

I expect when I want to send out E-Mails using Resend mail driver in Laravel, it should work. If Resend has a built-in 2 requests per second rate limit, and also rate limit information response headers, the package should be prepared to read and use this information, and send out the mails via Resend correctly.

Also, if the requests fails, Laravel should use the next E-Mail sending method configured in the failover configuration. I am not sure that this is a Laravel issue or a Resend package issue, but it doesn't work right now, if the response is 429 from Resend, Laravel won't try to send out with the next configured E-Mail sender.

Issue description, screenshots

So we just released a webshop with around 15.000 users and wanted to send them a Password Change E-Mail. However, this resulted with a disaster that the Resend API responded with 429 in many cases, and Laravel didn't try to send it out with the failover SMTP server, it just threw an error... 👎

Screenshot 2024-06-27 at 10 06 42 Screenshot 2024-06-27 at 16 23 02
jayanratna commented 3 months ago

Hey @MrMarci666, apologies for getting on to this one so late.

I believe this could be caused by versions of the library prior to PR #58 or prior to v0.12.0. The underlying mailing system of Laravel depends on the TransportException type to "failover" to the next mailer.

If you upgrade to the latest version, this should be fixed. Let me know how it goes.

MrMarci666 commented 3 months ago

@jayanratna Thanks for the reply. Sounds good! :) Yes, I can confirm we were using v0.11.0 at the time, so that was solved.

But the main issue still remains I assume... That the package is basically ignoring a very core function of the API, the rate limitation.

jayanratna commented 3 months ago

That is something I am currently investigating. I'm wondering what the best way to expose the rate limit information would be.

Currently there is a functionality to expose the ID of the sent email with the X-Resend-Email-ID, perhaps it could be done the same way. Though that header is only available on successful requests 🤔

MrMarci666 commented 3 months ago

I see, cool. Hmmm let me summon also @floodx92. (We faced this issue in a project together) What do you think @floodx92, what would be the best solution to this?

elinardo10 commented 2 months ago

I'm having this same problem and I'm using version 0.14.0. Is there a solution yet?

elinardo10 commented 2 months ago

I have a soluction for now. 1 create a job to send every emails and when call then define delay: SendEmailJob::dispatch($event)->delay(now()->addSeconds(3))->onQueue('send_emails');

  1. Get error and filter in My job:
    public function handle()
    {
        try {
            Mail::to($this->recipient)->send(new SendNewsletter($this->data));
        } catch (TransportException $e) {
            if (str_contains($e->getMessage(), 'Too many requests')) {
                // Handle the specific "Too many requests" exception
                $this->release(10); // Delay the job for 10 seconds before retrying
            } else {
                // Rethrow the exception if it's not related to rate limiting
                throw $e;
            }
        }
    }