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.
Related
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()));
}
I am creating a leave request application for employee in an organization where user can create a leave request and admin(s) can approve/reject the request. The user and admin(s) should receive mail each time after making a leave request and after approval too. I am using event listener to send the mail and could send the mail using separate events and listeners(4 events:
making leave request to admin,
making leave request to user,
after accepting/rejecting the request to admin and
accepting/rejecting the request to user
and likewise 4 listeners too)
but it would be better if I could reduce the number of events and listeners in this case since the user mail and email template only are different.
I studied about event subscribers which subscribe to multiple events from within the subscriber class itself which is not what I want.
Is it possible to send multiple emails to multiple user/admin using less number of events/listeners?
I am using laravel 8
Code:
controller is:
public function store(Request $request)
{
DB::beginTransaction();
try{
$request->validate(
[
'leave_type' => 'in:halfday,fullday',
'start_date' => 'required',
'end_date' => 'required',
'assignee' => 'required',
]
);
$datetime1 = new \DateTime($request->start_date);
$datetime2 = new \DateTime($request->end_date);
$interval = $datetime1->diff($datetime2);
$days = $interval->format('%a');//total requested days
$leave_request= new LeaveRequest();
$leave_request->requested_by = auth()->user()->id;
$leave_request->leave_type_id = $request->leave_type_id ;
$leave_request->leave_type = $request->leave_type;
$leave_request->start_date = $request->start_date;
$leave_request->end_date = $request->end_date;
$leave_request->total_days = $days+1;
$leave_request->assignee = $request->assignee;
$leave_request->reviewer = $request->reviewer;
if($leave_request->save()){
$user = User::where('id', auth()->user()->id)->first();
$assignee = User::where('id',$request->assignee)->first();
$assignee->locale = App::currentLocale();
$assignee->requested_by = $user->name;
event(new SendLeaveRequestToAdminEvent($assignee));//send mail to admin asignee
if($request->reviewer !=null){
$reviewer = User::where('id',$request->reviewer)->first();
$reviewer->locale = App::currentLocale();
$reviewer->requested_by = $user->name;
event(new SendLeaveRequestToAdminEvent($reviewer));//send mail to admin reviewer(optional)
}
$user->locale = App::currentLocale();
event(new SendLeaveRequestToUserEvent($user));//send mail to user
DB::commit();
return response()->json([
'message'=> 'Leave requested successfully',
'status_code' => 200,
]);
}
else{
DB::rollback();
return response()->json([
'status_code' => '204',
'message' => 'Leave request failed',
]);
}
} catch (Exception $error) {
DB::rollback();
return response()->json([
'message' => 'Leave request failed',
'error' => $error
]);
}
}
first event to mail to admin(s) i.e; assignee and reviewer after sending leave request is
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use App\Models\User;
class SendLeaveRequestToAdminEvent
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $admin;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct(User $admin)
{
$this->admin = $admin;
}
/**
* Get the channels the event should broadcast on.
*
* #return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('channel-name');
}
}
second event to mail to user after sending leave request is
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use App\Models\User;
class SendLeaveRequestToUserEvent
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $user;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct(User $user)
{
$this->user = $user;
}
/**
* Get the channels the event should broadcast on.
*
* #return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('channel-name');
}
}
first listener to admin(s):
<?php
namespace App\Listeners;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Mail;
use App\Mail\LeaveRequestAdmin;
use App\Events\SendLeaveRequestToAdminEvent;
class SendLeaveRequestToAdminListener
{
/**
* Create the event listener.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* #param SendLeaveRequestToAdminEvent $event
* #return void
*/
public function handle(SendLeaveRequestToAdminEvent $event)
{
$email = $event->admin->email;
Mail::to($email)->locale($event->admin->locale)->send(new LeaveRequestAdmin($event->admin));
}
}
second listener to user:
<?php
namespace App\Listeners;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Mail;
use App\Mail\LeaveRequestUser;
use App\Events\SendLeaveRequestToUserEvent;
class SendLeaveRequestToUserListener
{
/**
* Create the event listener.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* #param SendLeaveRequestToUserEvent $event
* #return void
*/
public function handle(SendLeaveRequestToUserEvent $event)
{
$email = $event->user->email;
Mail::to($email)->locale($event->user->locale)->send(new LeaveRequestUser($event->user));
}
}
mail to admin:
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\URL;
class LeaveRequestAdmin extends Mailable
{
use Queueable, SerializesModels;
public $admin;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct($admin)
{
$this->admin = $admin;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
return $this->markdown('email.leaverequestadmin', [
'url' => URL::to('/home'),
'admin' => $this->admin,
])->subject('Incoming Leave Request');
}
}
mail to user:
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\URL;
class LeaveRequestUser extends Mailable
{
use Queueable, SerializesModels;
public $user;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct($user)
{
$this->user = $user;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
return $this->markdown('email.leaverequestuser', [
'url' => URL::to('/home'),
'user' => $this->user,
])->subject('Leave Request Received');
}
}
the above code is used after leave request is created which have two separate events/listeners/mail/ blade templates. Like wise I have similar another two events/listeners/mail/ blade templates each which are used after the leave request is accepted/rejected.
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 */);
}
I'm trying to pass variable $array from controller to mail blade, but whenever I run queue:listen. It always say failed.
Bellow is my code
In controller I have a variable named $array, I've putting it in dispatch
Controller
$array["view"] = "layouts.mail.order";
$array["subject"] = "Order Created";
$array["from"] = env('MAIL_USERNAME');
$array["data"] = "aaaaaaaaa";
$array["email"] = Auth::user()->email;
OrderEmailJob::dispatch($array);
OrderEmailJob
<?php
namespace App\Jobs;
use App\Mail\OrderMail;
use Illuminate\Bus\Queueable;
use Illuminate\Support\Facades\Mail;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
class OrderEmailJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $array;
/**
* Create a new job instance.
*
* #return void
*/
public function __construct($array)
{
$this->array = $array;
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
$email = new OrderMail();
Mail::to($this->array['email'])->send($array);
}
}
and this is code for the mailable
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class OrderMail extends Mailable
{
use Queueable, SerializesModels;
public $array;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct($array)
{
$this->array = $array;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
return $this->view($this->array['view'])
->from($this->array['from'], env('MAIL_FROM_NAME'))
->subject($this->array['subject'])
->with([
'data' => $this->array['data'],
]);
}
}
The result I want is I can use variable $array in view for my mail, because I've to printed out data from $array variable
Sorry about my english, thanks
try like this :
public $mailData;
public function __construct($mailData)
{
$this->mailData = $mailData;
}
public function build()
{
// Array for Blade
$input = array(
'action' => $this->mailData['action'],
'object' => $this->mailData['object'],
);
return $this->view('emails.notification')
->with([
'inputs' => $input,
]);
}
I'm not sure, the answer correct. But you can change the name variable $array to $data and check again. Maybe your variable name is a special case like array keyword
I am trying to get a mailable setup which has a collection of files. Mail controller looks like:
<?php
namespace App\Mail;
use App\Document;
use App\Order;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
use App\User;
class OrderComplete extends Mailable
{
use Queueable, SerializesModels;
public $user;
public $order;
public $documents;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct(User $user, Order $order, Document $document)
{
//
$this->user = $user;
$this->order = $order;
$this->documents = $document;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
return $this->markdown('emails.customers.complete');
}
}
Controller calling the mailable looks like;
use App\Document;
// other code...
$documents = Document::where('order_id', $orderId)
->where('product', 'like', '%response')
->get();
Mail::to($customer)
->send(new OrderComplete($customer, $order, $documents));
But I keep getting this error:
Type error: Argument 3 passed to App\Mail\OrderComplete::__construct() must be an instance of App\Document, instance of Illuminate\Database\Eloquent\Collection given, called in /Users/ap/sites/propair/app/Http/Controllers/OrderController.php on line 253
I'm pretty confused as I thought this should work?
thanks
This function declaration:
public function __construct(..., Document $document)
means PHP will enforce that $document is an instance of App\Document.
If you want to pass it a collection instead, you'll need to do:
public function __construct(..., \Illuminate\Database\Eloquent\Collection $documents)