Laravel-5 Queueing mail - php

I'm trying to queue mail using Laravel-5. The code I am using is below. I was expecting the mail to get stored in the database in the 'jobs' table but it just gets sent instantly.
Mail::queue('emails.orderthankyou', ['first_name' => 'My Name'], function ($m) {
$m->to('me#myemail.com')->subject('Test');
});
Any idea what could be going wrong here?

You probably want to use Mail::later instead.
http://laravel.com/docs/5.1/mail
Mail::later(5, 'emails.orderthankyou', ['first_name' => 'My Name'], function ($m) {
$m->to('me#myemail.com')->subject('Test');
});

You might be using sync driver ( in config/queue.php ). sync stands for synchronous. Therefore, all jobs are executed instantly.

Delayed Message Queueing
If you wish to delay the delivery of a queued e-mail message, you may
use the later method. To get started, simply pass the number of
seconds by which you wish to delay the sending of the message as the
first argument to the method:
Mail::later(5, 'emails.welcome', $data, function ($message) {
//here 5 is the number of seconds
});

Related

Laravel - send mail to queue with different connection

I use this code to send invoice
Mail::send($email_template, $view_vars, static function ($m) use ($customer, $company, $email_subject, $users, $invoice, $invoice_name) {
$m->from($company->send_email, $company->name);
$m->to($users, $customer->getFullName())->subject($email_subject);
$m->attachData($invoice, $invoice_name);
});
Problem that it need some time to send email with atache.
Decision - to use Queue and change Mail::send() to Mail::queue()
But I use iron.io as as QUEUE Driver. And by default email with attached will go to iron.io - so it also need some time.
I whant to use database connection for this case.
can I use code:
Mail::queue(.......)->onConnection('database');
?
Table Jobs already exist.
After this need I some additional code that emails was realy sended in background ot it will be done automaticaly?
Tha best way is to use local connection method (databases) for all queues.
According to Laravel docs, you can use onConnection with Queues.
https://laravel.com/docs/8.x/queues#chain-connection-queue
To do extra processing after mail has been sent, you need to listen to Laravel's MailSent event.
https://laravel.com/api/5.5/Illuminate/Mail/Events/MessageSent.html
Define a listener in EventServiceProvider
'Illuminate\Mail\Events\MessageSent' => [
'App\Listeners\OnMessageSent',
]
Define handle function in the listener.
use Illuminate\Mail\Events\MessageSent;
class OnMessageSent {
// listen for MailSent event
public function handle(MessageSent $event)
{
if ($event) {
// do something here
dd($event);
}
}
}

Laravel 5.0 delayed execution

I need to delay execution of one method in Laravel 5.0, or to be more specific, I need it to be executed at special given time. The method is sending notification through GCM to mobile app, and I need to do it repeatedly and set it to a different time. As I found out, there is no way how to intentionally delay notification in GCM. I know basics from working with cron and scheduling in Laravel, but I cant find answer to my problem.
The method I need to execute with delay is this:
public function pushAndroid($receiver, $message, $data)
{
$pushManager = new PushManager(PushManager::ENVIRONMENT_DEV);
$gcmAdapter = new GcmAdapter(array(
'apiKey' => self::GCM_API_KEY
));
$androidDevicesArray = array(new Device($receiver));
$devices = new DeviceCollection($androidDevicesArray);
$msg = new GcmMessage($message, $data);
$push = new Push($gcmAdapter, $devices, $msg);
$pushManager->add($push);
$pushManager->push();
}
Information when (date+time) it should be executed is stored in table in database. And for every notification, I need to do it only once, not repeatedly.
If you take a look at https://laravel.com/docs/5.6/scheduling you can setup something that fits your needs.
Make something with the looks of
$schedule->call(function () {
// Here you get the collection for the current date and time
$notifications = YourModel::whereDate('datecolumn',\Carbon\Carbon::now());
...
})->everyMinute();
You can also use Queues with Delayed Dispatching, if this makes more sense. Since you hinted you only need to do it once.
ProcessJobClassName::dispatch($podcast)->delay(now()->addMinutes(10));

Updating config on runtime for a user

I am using Laravel 5.1
I created a function to get smtp info from db for a user $mail_config=STMPDetails::where('user_id',36)->first() and then I can just call config helper function and pass the array to set config value config($mail_config). and then I call Mail::queue function.
but before it reaches createSmtpDriver#vendor/laravel/framework/src/Illuminate/Mail/TransportManager.php where it reads the configuration again to send the mail, mail config are changed to the one specified in .env file.
Another thing to note is Mail sending function is in a Listener
I am not able to figure out where can I call the function such that configuration changes are retained before mail is sent.
Thanks,
K
This should work:
// Set your new config
Config::set('mail.driver', $driver);
Config::set('mail.from', ['address' => $address, 'name' => $name]);
// Re execute the MailServiceProvider that should use your new config
(new Illuminate\Mail\MailServiceProvider(app()))->register();
Since the default MailServiceProvider is a deferred provider, you should be able to change config details before the service is actually created.
Could you show the contents of $mail_config? I'm guessing that's the problem. It should be something like
config(['mail.port' => 587]);
Update - tested in 5.1 app:
Mail::queue('emails.hello', $data, function ($mail) use ($address) {
$mail->to($address);
});
->> Sent normally to recipient.
config(['mail.driver' => 'log']);
Mail::queue('emails.hello', $data, function ($mail) use ($address) {
$mail->to($address);
});
->> Not sent; message logged.

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

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?
Seems like only Mailtrap sends this error, so either open another account or upgrade to a paid plan.
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.
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.
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 :)
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.
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');
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
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));

Laravel 5.1 Mail::later

I am having trouble setting up my delayed mailing with laravel 5.1
In App\Exceptions\Handler in the render method I want to mail any occuring errors to myself: Like this
Mail::send('emails.servererror', ['exception' => $e, 'request' => $request] ,function($message)
{
...
});
This works perfectly, except that I would like to delay the sending of the email With Mail::later. E.G.
Mail::later(5, 'emails.servererror', ['exception' => $e, 'request' => $request] ,function($message)
{
...
});
The part where I am having trouble is the following:
This method will automatically take care of pushing a job onto the queue to send the mail message in the background. Of course, you will need to configure your queues before using this feature.
I have read the documentation several times. I still can't figure out what precisely I should do to make the Mail::later work.

Categories