I have table with 100K+ emails where I want to daily send some email:
I added schedule in app\Console\Kernel.php:
$schedule->job(new SendDailyEmails)->dailyAt('09:00');
Inside Job I have:
$users = User::all();
foreach($users as $user){
Maill:to($user->email)->send(new DailyMail($user));
$status = 'sent';
if( in_array($user->email, Mail::failures()) ){
$status = 'failed';
Log::error($user->email . ' was not sent.');
}else{
Log::info($user->email . ' was sent.');
}
SentMail::create([
'email' => $user->email,
'status' => $status
]);
}
This works fine, but after some time this stops probably because job timeout. In failed_jobs table I get MaxAttemptsExceededException with message that Job attempted too many times or run too long. Since I set queue tries max to 3 within supervisor it should and do go 3 times only. And by testing things it did not try to attempt again because I got one mail instead of 3.
So it comes to timeout and I am not sure what is default value but does it matter since I will not know how much time it would take to send all emails?
Should I divide mails into groups of 50 and call separate job instances for each group?
Anyone have a good working answer for this?
If you have a look at the official documentation you will find out that every single mail can be queued.
So, you should change your job from
Mail:to($user->email)->send(new DailyMail($user));
to
Mail:to($user->email)->queue(new DailyMail($user));
In this way you will push into the queue every single mail your job generates. I suggest you to create a specific queue and use system like laravel horizon for a better monitoring.
Also remember to chunk and delay the sending because your application can run a timeout error, but also providers like mailgun may block your sending if they see an unusual activity.
Instead of trying to send 100k+ emails all at once in a class, dispatch 100k+ instances of the Job to a queue worker
$users = User::all();
foreach($users as $user){
$schedule->job(new SendDailyEmails($user))->dailyAt('09:00');
}
Now Laravel will Stack a 100k+ jobs in a queue and attempt to send one email per one user at a time
class SendDailyEmails implements ShouldQueue
{
public $user;
public function __construct(User $user)
{
$this->user = $user;
}
Maill:to($this->user->email)->send(new DailyMail($user));
$status = 'sent';
if( in_array($this->user->email, Mail::failures()) ){
$status = 'failed';
Log::error($this->user->email . ' was not sent.');
}else{
Log::info($this->user->email . ' was sent.');
}
SentMail::create([
'email' => $this->user->email,
'status' => $status
]);
Related
how can I set time interval or time difference between the first time the user requested for the verification code and the second try which should be 30 seconds?
also how to display the time counter: 29:00 down to 0 seconds?
public function sendSms($request)
{
$apiKey = config('services.smsapi.ApiKey');
$client = new \GuzzleHttp\Client();
$endpoint = "https://www.sms123.net/api/send.php";
try
{
$response = $client->request('GET', $endpoint, ['query' => [
'recipients' => $request->contact_number,
'apiKey' => $apiKey,
'messageContent'=>'testSite.com verification code is '.$request->code,
]]);
$statusCode = $response->getStatusCode();
$content = $response->getBody();
$content = json_decode($response->getBody(), true);
return $content['msgCode'];
}
catch (Exception $e)
{
echo "Error: " . $e->getMessage();
}
}
Thankfully, Laravel gets you covered in this aspect. In Laravel, you can achieve rate-limiting using a middleware called throttle which comes out of the box in Laravel. You need to assign this throttle middleware to the route or group of routes.
The middleware basically accepts two parameters, specifically “number of requests” and “duration of time”, which determines the maximum number of requests that can be made in a given number of minutes.
Basic example
You can assign a throttle middleware to a single route like below
Route::get('admin/profile', function () {
//
})->middleware('auth', 'throttle:30,1');
As you can see, the above route configuration will allow an authenticated user access route 30 times per minute. If user exceed this limit within the specified time span, Laravel will return a 429 Too Many Requests with following response headers.
x-ratelimit-limit: 2
x-ratelimit-remaining: 0
x-ratelimit-reset: 1566834663
Then with vue or js on your frontend you can make a counter that will start counting the desired number so that the user knows how much time he has left.
When a certain event is fired, my Laravel based app has to send exactly one transactional email to each user in a mailing list.
Here is the loop code:
$users = User::where('notifiable', 1)->get();
foreach($users as $user) {
$info = [
'email' => $user->email,
'name' => $user->name
];
$data = [
'message' => 'Sample text'
];
Mail::send(['emails.html.notification', 'emails.text.notification',], $data, function($message) use ($info) {
$message
->to($info['email'], $info['name'])
->subject('example')
->from('admin#example.com','Example');
});
}
Unfortunately, several users are receiving the same mail multiple times.
I can't figure out what's happening:
When redirecting the emails to log, I see exactly one mail for each user as expected;
Every other events trigger emails to be sent to a single user. However, no one has received multiple emails from these events.
The app is using Sendinblue as an external SMTP service.
Some hints I got:
The hourly mail quota was very low -> the email were received 3 times (and the queue on Sendinblue was immediately filled)
The hourly mail quota was raised 10x -> no more queues on Sendinblue, but now users are receiving the same email up to 7 times!
Apparently, queuing the email and setting a delayed event has solved the problem.
Now a new job is requested every 10 seconds.
$users = User::where('notifiable', 1)->get();
$counter = 0;
foreach($users as $user) {
$payload = [
'email' => $user->email,
'name' => $user->name,
'message' => 'Sample text'
];
dispatch(new SendNotification($payload))
->delay(now()->addSeconds($counter * 10));
$counter++;
}
Thanks for your support!
We are currently using laravel Event listener to send emails for laravel. Basically this is a slot booking option, so sometimes we have to send emails to sender and sometimes we have to send to receiver and sometimes we have to send emails other partners of the slots. In the current case we are using a single Event Listner to send different emails fir the different actions users taking on the slot like cancel meeting, add one more member etc. But generally in the case the email templates would be different only the dunamic variables we need to change.
But in the new case we have to send 4 or 5 emails to different users with different email templates and different contents on a single action. If we plan this in a single event listner, how we can handle this?
$event_id=$event->user['XXXXX'];//event id
$slot_type=$event->user['XXXXX'];//slot type
$notification_type=$event->user['XXXXX']; //slot type
$scheduler_slot_info_ids=$event->user['XXXX'];
$data = $schedulerHelper->getOnetoOneNotificationContents($scheduler_slot_info_ids,$event_id,$slot_type);
$action_trigger_by=$event->user['XXXXX'];
//$data['subject'] = 'CARVRE SEVEN|MEETING CONFIRMED';
$data['subject'] = $event->user['XXXX'];
// $data['template'] = 'emailtemplates.scheduler.oneToOneMeetingConfirmed';
$data['template'] = $event->user['XXXX'];
$invitee_id=Crypt::encryptString($data['XXXX']);
$crypt_event_id=Crypt::encryptString($event_id);
$data['link'] = url('XXXX');
$data['email_admin'] = env('FROM_EMAIL');
$data['mail_from_name'] = env('MAIL_FROM_NAME');
// $data['receiver_email'] = 'XXXXXXX';//$invitee['email'];
//Calling mail helper function
MailHelper::sendMail($data);
Make either a table or hardcoded array with template renderers, then have those renderers render a twig/blade/php template based upon the variables you're supplying and all other variables you'd need for feeding into the mailer.
Then just loop through all your receiving candidates and render the appropriate emails with the correct renderer.
You'll have to make a few utility classes and all to accomplish this, but once you get it up and sorted it will be easy to manage and expand with more templates.
Just a rough outline of what I'd use
protected $renderers = [
'templateA' => '\Foo\Bar\BazEmailRender',
'templateB' => '\Foo\Bar\BbyEmailRender',
'templateC' => '\Foo\Bar\BcxEmailRender',
];
public function getTemplate($name)
{
if(array_key_exists($name, $this->renderers)) {
$clazz = $this->renderers[$name];
return new $clazz();
}
return null;
}
public function handleEmails($list, $action)
{
$mailer = $this->getMailer();
foreach($list as $receiver) {
if(($template = $this->getTemplate($receiver->getFormat()))) {
$template->setVars([
'action' => $action,
'action_name' => $action->getName(),
'action_time' => $action->created_at,
// etc...
]);
$mailer->send($receiver->email, $template->getSubject(), $template->getEmailBody());
}
}
}
I'm working on Bright Local API ( https://tools.brightlocal.com/ ) to get reviews of a business from Yelp ,Google+ etc.
I got some code of this API from GitHub with some examples.So I just register a free account in Bright Local and try those examples to get Reviews.
Below code is used to fetch the reviews of some business.After running this code i got a job id.But I dont know how to get reviews using this Job id.
$profileUrls = array(
'https://plus.google.com/114222978585544488148/about?hl=en',
'https://plus.google.com/117313296997732479889/about?hl=en',
'https://plus.google.com/111550668382222753542/about?hl=en'
);
// setup API wrappers
$api = new Api(API_KEY, API_SECRET, API_ENDPOINT);
$batchApi = new BatchApi($api);
// Step 1: Create a new batch
$batchId = $batchApi->create();
if ($batchId) {
printf('Created batch ID %d%s', $batchId, PHP_EOL);
// Step 2: Add review lookup jobs to batch
foreach ($profileUrls as $profileUrl) {
$result = $api->call('/v4/ld/fetch-reviews', array(
'batch-id' => $batchId,
'profile-url' => $profileUrl,
'country' => 'USA'
));
if ($result['success']) {
printf('Added job with ID %d%s', $result['job-id'], PHP_EOL);
}
}
// Step 3: Commit batch (to signal all jobs added, processing starts)
if ($batchApi->commit($batchId)) {
echo 'Committed batch successfully.'.PHP_EOL;
}
}
Anybody knows how to get reviews using this API ?
Thanks in advance.
It looks like you're missing the final step which is to poll for results. Our system works by adding jobs to a queue and then processing those jobs in parallel. Having created a batch, added jobs to that batch and committed it you then need to set up a loop, or come back and check for results periodically, until you see that the batch is marked as "Finished" and all jobs have returned data.
To do this call:
$results = $batchApi->get_results($batchId); // repeat this call until complete
$results will contain "status" which will be marked as "Finished" once all jobs have finished processing as well as the actual results associated with each job.
I have a Laravel 4 app where I need to send certain emails to users. I have no idea why but I keep getting this error on my test server (it does not happen on my local vagrant box)
Argument 1 passed to Illuminate\Mail\Mailer::getQueuedCallable() must
be of the type array, null given, called in
blablabla/vendor/laravel/framework/src/Illuminate/Mail/Mailer.php
on line 238 and defined
I tried resetting everything: database, clean clone from git. None of these worked. I also cleared my browser cache and cookies out of desperation. I am using Sync queue. So it's not even an actual queue.
Mail::send works just fine. Mail::queue throws the error above.
I literally started to pull my hair out so any help will be greatly appreciated.
Here is my code:
This is my BookingMailer class that extends Mailer class.
// partner bir arabanın rezervasyonuna onay verdiğinde müşteriye gönderilecek ödeme linki
public function sendPaymentLinkToCustomer($customer, $partner, $booking) {
$view = 'emails.bookings.request_confirmed';
$data = [
'user' => $customer->full_name // string,
'partner' => $partner->display_name // string,
'reference' => $booking->reference // integer,
'booking_id' => $booking->id // integer
];
return $this->sendTo($customer, 'Booking Request Confirmed', $view, $data, true, true);
}
And this is my Mailer class that actually queues the mail.
public function sendTo($user, $subject, $view, $data = array(), $admin_copy=false, $send_as_pm=false)
{
Mail::queue($view, $data, function ($message) use ($user, $view, $data, $subject, $admin_copy) {
$message = $message->to($user->email)->replyTo(Config::get('mail.from.address'), Config::get('mail.from.name'))->subject($subject);
return $message;
});
}
Here is the solution to this problem (at least in my case)
I was including the user first and last name to the mail. I was only showing the first letter of the last name, though. And I was using substr for a UTF-8 string, and that's why sometimes it returned problematic characters that get inside the data array and ultimately cause the queue to break.
When i used mb_substr instead of substr, my problem disappeared.
Phew, I thought I would never figure this one out on my own.