Keep PHPMailler connection alive over Laravel's queue - laravel

I want to send many emails.
Currently I write basic code use PHPMailler to send mail using queue. It works, but everytime new queue is run, it have to connect to SMTP again, so i get bad perfomance.
I find SMTPKeepAlive property on PHPMailler documentation:
$phpMailer = New PHPMailer();
$phpMailer->SMTPKeepAlive = true;
Is it imposible and how to keep $phpMailler object for next queue? So PHPMailler have not to connect again by using previous connection.

If you are using Laravel then you have to use Laravel's feature inbuilt.
Please find below documents:
https://laravel.com/docs/5.6/mail
Please find a piece of code for send mail and adding in a queue:
use App\Mail\EmailVerifyMail;
\Mail::queue(new EmailVerifyMail($users));
EmailVerifyMail.php
<?php
namespace App\Mail;
use App\User;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class EmailVerifyMail extends Mailable
{
use Queueable, SerializesModels;
public $user;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct(User $user)
{
$this->user = $user;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
$this->to($this->user)->subject(__('mail.subjects.verification_link', ['USERNAME' => $this->user->name]));
return $this->view('mails/emailVerify', ['user' => $this->user]);
}
}

Related

Send large number of notifications to users - Laravel

I want to send a large number of notifications in Laravel.
The notification method is firebase and I'm using kutia-software-company/larafirebase package as Notifiable.
Currently, I have about 10000 fcms but I want to optimize sending notifications to 100000 fcms.
I implemented a system like below:
First, I created a notifiable class as described in the package
<?php
namespace Vendor\Core\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;
use Kutia\Larafirebase\Messages\FirebaseMessage;
class FirebasePushNotification extends Notification
{
/**
* Create a new notification instance.
*
* #return void
*/
public function __construct(protected $title, protected $message, protected array $fcmTokens = [])
{
}
/**
* Get the notification's delivery channels.
*
* #param mixed $notifiable
* #return array
*/
public function via($notifiable)
{
return ['firebase'];
}
public function toFirebase($notifiable)
{
return (new FirebaseMessage)
->withTitle($this->title)
->withBody($this->message)
->withPriority('high')
->asNotification($this->fcmTokens);
}
}
Second, Created a Job for notifications:
<?php
namespace Vendor\PushNotification\Jobs;
use Vendor\Core\Notifications\FirebasePushNotification;
use Vendor\Customer\Contracts\UserDeviceInfo;
use Vendor\Customer\Contracts\Customer;
use Illuminate\Bus\Batchable;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Notification;
class SendPushNotificationJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, Batchable;
public function __construct(public string $title, public string $body, public Customer $customer, public UserDeviceInfo $userDeviceInfo)
{
}
public function handle()
{
Notification::send($this->customer, new FirebasePushNotification($this->title, $this->body, [$this->userDeviceInfo->fcm]));
}
}
And using Bus::dispatch for sending jobs to queue:
$usersDeviceInfos = $usersDeviceInfos->with('customer')->get();
$bus = [];
for ($i = 0; $i < 100000; $i++){
foreach ($usersDeviceInfos as $usersDeviceInfo) {
$bus [] = new SendPushNotificationJob($data['notification_title'], $data['notification_body'], $usersDeviceInfo->customer, $usersDeviceInfo);
}
}
Bus::batch($bus)->name('push notification' . now()->toString())->dispatch();
Currenly, because of development environement I have just one fcm and use a loop to simulate 10000, It takes more than a minute to run this code and I'll get Maximum execution error.
Also, note that I configured my queue and it's not sync and I checked these two questions but didn't help:
fastest way to send notification to all user laravel
Laravel: Send notification to 1000+ users [closed]
Hope to find a faster method to batch those jobs and send them to queue
I found a solution that can handle sending notifications up to one million fcms in a few seconds
I decided to use kreait/laravel-firebase and use multicast that was mentioned here
First I chunk fcms then I use A job and dispatch them to the queue.
Here's an example of the solution:
<?php
namespace Vendor\PushNotification\Jobs;
use Illuminate\Bus\Batchable;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Kreait\Firebase\Messaging\CloudMessage;
use Kreait\Laravel\Firebase\Facades\Firebase;
class SendPushNotificationJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, Batchable;
public function __construct(public string $title, public string $body, public array $fcmTokens)
{
}
public function handle()
{
$message = CloudMessage::fromArray(YOUR_DATA);
Firebase::messaging()->sendMulticast($message, $this->fcmTokens);
}
}
$chunks = $usersDeviceInfos->with('customer')->get()->pluck('fcm')->chunk(500);
foreach ($chunks as $chunk) {
dispatch(new SendPushNotificationJob($data['notification_title'], $data['notification_body'], $chunk->toArray()));
}

Laravel 9 - How to get resolved instance of task in Queue::before event?

I have a multi-tenant project with multiple databases and a single queue. I need to switch between databases before running the job.
Here's the code I have:
Queue::before(function (JobProcessing $event) {
$costumer = DB::table('costumers')
->select('db_password', 'id')
->where('id', 11)
->first();
DB::disconnect('mysql');
config(
[
'database.connections.mysql.database' => 'costumer_'.$costumer->id.'_db',
'database.connections.mysql.username' => 'costumer_'.$costumer->id,
'database.connections.mysql.password' => Crypt::decryptString($costumer->db_password),
'costumer.code' => $costumer->id,
]
);
DB::reconnect('mysql');
});
It's working, but in the where clause, the id must be dynamically set.
So I pass the id in the Job::dispatch() method, but here's the problem: how do I get the job instance to return the data inside it?
I saw in another question the $event->job->instance and $event->job->getResolvedJob().
The first option is a protected property, so it doesn't work (it worked in Laravel 5). The second returns null.
You can set public property or getter in your job, so you can retrieve your id from the job instance, like here in getPodcastId:
<?php
namespace App\Jobs;
use App\Models\Podcast;
use App\Services\AudioProcessor;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ProcessPodcast implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* The podcast instance.
*
* #var \App\Models\Podcast
*/
protected $podcast;
/**
* Create a new job instance.
*
* #param App\Models\Podcast $podcast
* #return void
*/
public function __construct(Podcast $podcast)
{
$this->podcast = $podcast;
}
public function getPodcastId()
{
return $this->podcast?->id;
}
/**
* Execute the job.
*
* #param App\Services\AudioProcessor $processor
* #return void
*/
public function handle(AudioProcessor $processor)
{
// Process uploaded podcast...
}
}
But, to be honest, I think this is not really safe to change config on the go. The better solution would be to initialize another database connection inside your job and use it in your job dirrectly:
use Illuminate\Database\Connectors\ConnectionFactory;
// ...
public function __construct()
{
$factory = app(ConnectionFactory::class);
return $this->db = $factory->make(/* Config */);
}

Laravel 7 | Some recipients receive empty emails

I have an laravel 7 application and use smtp with google services to send my emails.
However some clients receive the emails with attachments, but no body. The content of the email is completely blank. Most mail platforms (hotmail, outlook, gmail, mail on mac) receive the complete email. It's just some mail providers receive the email without a body.
I believe this might have to do with some security measures or not supporting HTML emails.
How can I ensure that also these clients receive my emails?
I use .blade.php files to send my emails.
example of one of my email files:
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class CandidateMail extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct($message_details)
{
//
$this->message_details = $message_details;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
$email = $this->view('mail/candidate')
->subject($this->message_details['subject'])
->with(['message_details' => $this->message_details]);
if ($this->message_details['attachments']) {
$email->attachFromStorage($this->message_details['attachments']);
}
return $email;
}
}
Fix for outlook (desktop).
replaced my divs with table/tr/td. Now email content is displayed to clients with outlook (desktop).
you had to build your constructor the variable $message_details does not return anything,
public $message_details = "";
Need to declare public property public $message_details here details
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class CandidateMail extends Mailable
{
use Queueable, SerializesModels;
public $message_details;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct($message_details)
{
//
$this->message_details = $message_details;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
$email = $this->view('mail/candidate')
->subject($this->message_details['subject'])
->with(['message_details' => $this->message_details]);
if ($this->message_details['attachments']) {
$email->attachFromStorage($this->message_details['attachments']);
}
return $email;
}
}

Laravel 7 queued email very slow with attachment but fast without

I use Laravel 7 queues / jobs to send a newsletter to multiple addresses and it works well and rather fast. But when I send a single email with attachment (22ko PDF), it takes nearly 3 - 5 minutes to get through. Any clues? I use database driver and Mailgun API.
How can I see if the slow process time is from Laravel app or Mailgun or else?
I tried to use SendEmailJob::dispatchNow($data); but it does not speed up the process.
app\Http\controllers/EmailController.php
App\EmailController
use App\Job\SendEmailJob;
public function send()
{
$data = array(
'from_name'=>from_name',
'from_email'=>'admin#domain.com',
'to_name'=>$user->firstname." ".$user->name,
'to_email'=>$user->email,
'subject'=>'This is test email',
'reply_to'=>noreply#domain.com,
'model'=>$invoice,
'locale'=>app()->getLocale(),
'user'=>$user,
'view'=>'emails.invoice',
'filefullpath'=>$fullfilepath,
);
SendEmailJob::dispatch($data);
}
App\Jobs\SendEmailJob.php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use App\Mail\SendView;
use Mail;
class SendEmailJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $data;
/**
* Create a new job instance.
*
* #return void
*/
public function __construct($data)
{
$this->data = $data;
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
$email = new SendView($this->data);
$email->replyTo($this->data['reply_to']);
$email->subject($this->data['subject']);
if(isset($this->data['filefullpath']))
{
$email->attach($this->data['filefullpath']);
}
Mail::to($this->data['to_email'], $this->data['to_name'])->send($email);
}
}
App\Mail\SendView.php
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class SendView extends Mailable
{
use Queueable, SerializesModels;
protected $data;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct($data)
{
$this->data = $data;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
$view = $this->data['view'];
$user = $this->data['user'];
$model = $this->data['model'];
$locale = $this->data['locale'];
$array = $this->data['array'];
return $this->view($view, compact('user', 'model','locale','array'));
}
}
Please help! Thanks.

laravel job/notification failing

I am trying to set up a contact form on my site whereby when someone clicks send, then a job is run and in that job, a notification is sent to all admin users. I keep getting this error in my failed jobs table though:
Illuminate\Database\Eloquent\ModelNotFoundException: No query results for model [App\Contact]. in /var/www/html/leonsegal/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php:412
I have been all over my code and I can't see what I have done wrong. Would anyone be able to help please?
Here is my controller:
<?php
namespace App\Http\Controllers;
use App\Contact;
use App\Jobs\SendContactJob;
class ContactController extends Controller
{
/**
* #return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function create()
{
return view('contact');
}
public function store()
{
request()->validate([
'name' => 'required|max:255',
'email' => 'required|email|unique:contacts|max:255',
'message' => 'required|max:2000',
]);
$contact = Contact::create(
request()->only([
'name',
'email',
'message',
])
);
SendContactJob::dispatch($contact);
return back()->with('success', 'Thank you, I will be in touch as soon as I can');
}
}
my job:
<?php
namespace App\Jobs;
use App\Contact;
use App\Notifications\SendContactNotification;
use App\User;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Support\Facades\Notification;
class SendContactJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $contact;
/**
* Create a new job instance.
*
* #param Contact $contact
*/
public function __construct(Contact $contact)
{
$this->contact = $contact;
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
$users = User::all()
->where('admin', 1)
->where('approved', 1);
Notification::send($users, new SendContactNotification($this->contact));
}
}
my notification:
<?php
namespace App\Notifications;
use App\Contact;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
class SendContactNotification extends Notification implements ShouldQueue
{
use Queueable;
protected $contact;
/**
* Create a new notification instance.
*
* #param $contact
*/
public function __construct(Contact $contact)
{
$this->contact = $contact;
}
/**
* Get the notification's delivery channels.
*
* #param mixed $notifiable
* #return array
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* #param mixed $notifiable
* #return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
return (new MailMessage)
->line($this->contact->name)
->line($this->contact->email)
->line($this->contact->message);
}
/**
* Get the array representation of the notification.
*
* #param mixed $notifiable
* #return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}
The weird thing is that when I run a die dump in the handle method of the job, it never fires, but the artisan queue worker says it was processed correctly but the subsequent notification is where it is failing. I am not sure why that handle method in the job wouldn't be firing.
I have set my .env file to database queue driver.
I thought it might be that I didn't import the contact model, but you can see I have.
Any help would be appreciated.
Could be that because both the job and the notification are queued, the contact could be getting 'lost in transit' so to speak. try making the job non queueable, and only queue the notification (or the other way around). Or scrap the job altogether and just send the notification from the controller.
Did you check on your model path? Coz for newer laravel the path should be
use App\Models\Contact;

Resources