Laravel email with queue 550 error (too many emails per second)
Asked Answered
O

8

11

Our emails are failing to send using Laravel with a Redis Queue.

The code that triggers the error is this: ->onQueue('emails')

$job = (new SendNewEmail($sender, $recipients))->onQueue('emails');
$job_result = $this->dispatch($job);

In combination with this in the job:

use InteractsWithQueue;

Our error message is:

Feb 09 17:15:57 laravel: message repeated 7947 times: [ production.ERROR: exception 'Swift_TransportException' with message 'Expected response code 354 but got code "550", with message "550 5.7.0 Requested action not taken: too many emails per second "' in /home/laravel/app/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/AbstractSmtpTransport.php:383 Stack trace: #0 /home/laravel/app/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/AbstractSmtpTransport.php(281): 

Our error only happens using Sendgrid and not Mailtrap, which spoofs emailing sending. I've talked with Sendgrid and the emails never touched their servers and their service was fully active when my error occurred. So, the error appears to be on my end.

Any thoughts?

Ormuz answered 9/2, 2016 at 23:25 Comment(5)
that's a responce code from the mailserver, so if your using the server of Sendgrid, the error has to be from themSubtraction
Looking at the amount of emails you tried to send (7947) I think you should look into SendGrid's bulk email option. We kind of had the same issue at my old job and we ended up using MailChimp and used their API to sync up the subscriber list. I'm guessing SendGrid will probably have the same feature or something similar.Wheelwork
It was only 2 emails. It kept trying to resend 8000 times because it failed.Ormuz
I'm trying to solve this same problem. I wnoder if there is some kind of rate-limiting in Laravel that we can enable, like this in Node: dorelljames.com/web-development/…Heptameter
Here is something for us to explore: #30568569Heptameter
B
27

Seems like only Mailtrap sends this error, so either open another account or upgrade to a paid plan.

Bulahbulawayo answered 4/3, 2017 at 0:22 Comment(1)
or just delay your mail above 10 secondsTinnitus
H
10

I finally figured out how to set up the entire Laravel app to throttle mail based on a config.

In the boot() function of AppServiceProvider,

$throttleRate = config('mail.throttleToMessagesPerMin');
if ($throttleRate) {
    $throttlerPlugin = new \Swift_Plugins_ThrottlerPlugin($throttleRate, \Swift_Plugins_ThrottlerPlugin::MESSAGES_PER_MINUTE);
    Mail::getSwiftMailer()->registerPlugin($throttlerPlugin);
}

In config/mail.php, add this line:

'throttleToMessagesPerMin' => env('MAIL_THROTTLE_TO_MESSAGES_PER_MIN', null), //https://mailtrap.io has a rate limit of 2 emails/sec per inbox, but consider being even more conservative.

In your .env files, add a line like:

MAIL_THROTTLE_TO_MESSAGES_PER_MIN=50

The only problem is that it doesn't seem to affect mail sent via the later() function if QUEUE_DRIVER=sync.

Heptameter answered 14/12, 2018 at 16:13 Comment(3)
You can also use \Swift_Plugins_ThrottlerPlugin::MESSAGES_PER_SECOND if you need more granularityPetcock
Hi @Ryan, having in mind that SwiftMailer is now deprecated as of Laravel 9 and SymfonyMailer should be used do you have any idea how to imprement this there?Bonfire
@Bonfire I haven't worked on this in years; sorry.Heptameter
T
7

For debugging only!
If you don't expect more then 5 emails and don't have the option to change mailtrap, try:

foreach ($emails as $email) {
    ...
    Mail::send(... $email);                                                                      
    if(env('MAIL_HOST', false) == 'smtp.mailtrap.io'){
        sleep(1); //use usleep(500000) for half a second or less
    }
}

Using sleep() is a really bad practice. In theory this code should only execute in test environment or in debug mode.

Threepence answered 2/1, 2018 at 16:47 Comment(0)
C
3

Maybe you should make sure it was really sent via Sendgrid and not mailtrap. Their hard rate limit seems currently to be 3k requests per second against 3 requests per second for mailtrap on free plan :)

Carrero answered 1/6, 2017 at 14:21 Comment(1)
I may be mistaken - but isn't Sendgrid for sending out mail? Mailtrap is for NOT sending out mail, but catching it for test purposes. So this doesn't solve the problem for those trying to test mass mail sending.Ivories
N
2

I used sleep(5) to wait five seconds before using mailtrap again.

foreach ($this->suscriptores as $suscriptor)  {
    \Mail::to($suscriptor->email)
           ->send(new BoletinMail($suscriptor, $sermones, $entradas));
    sleep(5);
}

I used the sleep(5) inside a foreach. The foreach traverses all emails stored in the database and the sleep(5) halts the loop for five seconds before continue with the next email.

Neutrophil answered 16/10, 2019 at 16:27 Comment(1)
Where did you place that sleep?Merat
F
1

I achieved this on Laravel v5.8 by setting the Authentication routes manually. The routes are located on the file routes\web.php. Below are the routes that needs to be added to that file:

Auth::routes();

Route::get('email/verify', 'Auth\VerificationController@show')->name('verification.notice');
Route::get('email/verify/{id}', 'Auth\VerificationController@verify')->name('verification.verify');

Route::group(['middleware' => 'throttle:1,1'], function(){
    Route::get('email/resend', 'Auth\VerificationController@resend')->name('verification.resend');
});

Explanation:

  • Do not pass any parameter to the route Auth::routes(); to let configure the authentication routes manually.
  • Wrap the route email/resend inside a Route::group with the middleware throttle:1,1 (the two numbers represents the max retry attempts and the time in minutes for those max retries)

I also removed a line of code in the file app\Http\Controllers\Auth\VerificationController.php in the __construct function.

I removed this:

$this->middleware('throttle:6,1')->only('verify', 'resend');
Fruiterer answered 28/5, 2019 at 3:7 Comment(1)
This answer addresses the problem in testing the laravel 5.8 verify code.Cybill
W
0

You need to rate limit emails queue.

The "official" way is to setup Redis queue driver. But it's hard and time consuming.

So I wrote custom queue worker mxl/laravel-queue-rate-limit that uses Illuminate\Cache\RateLimiter to rate limit job execution (the same one that used internally by Laravel to rate limit HTTP requests).

In config/queue.php specify rate limit for emails queue (for example 2 emails per second):

'rateLimit' => [
    'emails' => [
        'allows' => 2,
        'every' => 1
    ]
]

And run worker for this queue:

$ php artisan queue:work --queue emails
Wroughtup answered 2/8, 2019 at 15:59 Comment(0)
Z
0

I had this problem when working with mail trap. I was sending 10 mails in one second and it was treated as spam. I had to make delay between each job. Take look at solution (its Laravel - I have system_jobs table and use queue=database)

Make a static function to check last job time, then add to it self::DELAY_IN_SECONDS - how many seconds you want to have between jobs:

public static function addSecondsToQueue() {
        $job = SystemJobs::orderBy('available_at', 'desc')->first();
        if($job) {
            $now = Carbon::now()->timestamp;
            $jobTimestamp = $job->available_at + self::DELAY_IN_SECONDS;
            $result = $jobTimestamp - $now;
            return $result;
        } else {
            return 0;
        }

    }

Then use it to make sending messages with delay (taking in to consideration last job in queue)

Mail::to($mail)->later(SystemJobs::addSecondsToQueue(), new SendMailable($params));
Zollie answered 5/2, 2020 at 15:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.