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!
Related
There is currently 25k+ users we have in database. All users are subscribed to a common topic All.
I have two directories inside public_html. First is for API built in codeigniter. This API is used to provide data for all adnroid and iOS devices.
Second directory is for admin panel built in Laravel. We use it to upload data and also to send notification to firebase topics.
Both API and Admin Panel share same database.
If we send notification to topics which is not subscribed by much
users, there is no issue. But If I send notificaiton to a topic
which has much users It causes problems on our backend. The API
stops responding, or sometimes takes too long to respond.
Sometimes also admin panel stops responding too.
I am so confused because all the things are handled by firebase. I just make one API call.
Can anyone explain what's causing the problem?
Or any possible reason?
Update
use Kreait\Firebase\Messaging;
use Illuminate/Support/Http/Request;
trait UserTrait {
public function notify(Request $request, Messaging $messaging) {
$message_hi = array(
"notification_type" => $notification_type,
"notification_title" => $notification_title_hi,
"icon_image" => $icon_image,
"notification_description" => $request->notificationText_hi,
"image_url" => $request->image_url,
);
$message = array(
"notification_type" => $notification_type,
"notification_title" => $notification_title,
"icon_image" => $icon_image,
"notification_description" => $request->notificationText,
"image_url" => $request->image_url,
);
$commodityIdArray = $request->cId
//to send all
if($request->notification_type == 1) {
$messaging->sendAll([
['topic' => 'All', 'data' => $message],
['topic' => 'All_hi', 'data' => $message_hi],
]);
} else {
//to send to a fourite topic subscribed by some users
//Prepare Condition for both hindi and english users
$topic_condition = "";
$topic_condition_hi = "";
foreach($commodityIdArray as $topic) {
$topic_condition .="'".$topic."' in topics && ";
$topic_condition_hi.="'".$topic."_hi' in topics &&";
}
//send notification to hindi and english topics
$messaging->sendAll([
['condition' => substr($topic_condition, 0, -3), 'data' =>
$message],
['condition' => substr($topic_condition_hi, 0, -3), 'data' =>
$message_hi],
]);
}
}
Use Queue
You have to use a Queue which set your process in queue and when one process completes, the second one starts
you can also set number of retries of your process
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
]);
i'm using Gmail API to fetch messages. if i do like this
$labelIds = ['INBOX'];
$opt_params=[
'labelIds' => $labelIds,
];
$list = $gmail->users_messages->listUsersMessages('me',$opt_params);
it will work fine. and return messages. but if i mention SENT label with INBOX then it return nothing. what am i doing wrong?
$labelIds = ['INBOX', 'SENT'];
i want to fetch emails from both inbox and sentbox in one call.
Your code lists messages that has both the INBOX and SENT labels. You can list messages that has either one with the OR operator:
$opt_params=[
'maxResults' => 50,
'q' => 'in:inbox OR in:sent',
];
$list = $gmail->users_messages->listUsersMessages('me', $opt_params);
I'm using this lib to send two different messages with different collapse keys, but on my device I'm receiving the first and then the second is coming over the first.
I would like to have the two separately in the Android notification header on device.
For the record I'm using this Phonegap plugin to receive the push notification.
Here is my code:
$gcmApiKey = 'api key here';
$deviceRegistrationId = 'device regid here';
$numberOfRetryAttempts = 5;
$collapseKey = '1';
$payloadData = ['title' => 'First Message Title', 'message' => 'First message'];
$sender = new Sender($gcmApiKey);
$message = new Message($collapseKey, $payloadData);
$result = $sender->send($message, $deviceRegistrationId, $numberOfRetryAttempts);
// Sending Second message
$collapseKey = '2';
$payloadData = ['title' => 'Second Message Title', 'message' => 'Second Message'];
$sender = new Sender($gcmApiKey);
$message = new Message($collapseKey, $payloadData);
$result = $sender->send($message, $deviceRegistrationId, $numberOfRetryAttempts);
If I understand you right, your problem is that the first notification is replaced by the second after it was shown.
If that is the case, your mistake is not on the PHP-side here, but in your Java-code.
If you show a notification you call this method:
NotificationManager.notify(int id, Notification notification)
Most likely, you are setting the id parameter to the same value each time you call this method.
The effect of the id is that the system will only show one notification with the same ID - the newest. A typical use-case to use the same id as before would be to update a previous notification.
If you want to display multiple notifications, you need to set a different id each time. You could use a random number or better yet use a previously defined ID of your content.
The GCM collapse key has a different effect:
When you define a collapse key, when multiple messages are queued up in the GCM servers for the same user, only the last one with any given collapse key is delivered.
That means, for example, if your phone was off, you would only receive one message with the same collapse key. It doesn't do anything if your phone receives the first notification before you send the second.
To set it with your PhoneGap plugin
The plugin has a really messy documentation but if we look into the source code, we'll find this undocumented feature:
int notId = 0;
try {
notId = Integer.parseInt(extras.getString("notId"));
}
mNotificationManager.notify((String) appName, notId, mBuilder.build());
That means, if you change your payload to, for example:
$payloadData = ['title' => 'First Message Title', 'message' => 'First message', 'notId' => mt_rand()];
your notifications won't replace each other.
I am trying to create tickets on my Zendesk and that is working fine. However i do not want Zendesk to email the creator of the tickets (his or her email). Is this possible?
The idea is i have a contactForm widget on my site, i want the submits from this form to create tickets in my Zendesk.
Creating tickets is currently working using this code:
$zendesk = new zendesk(
$row->api_key,
$row->email_address,
$row->host_address,
$suffix = '.json',
$test = false
);
$arr = array(
"z_subject"=>"Offline Message",
"z_description"=> $r->contact_msg,
"z_recipient"=>$r->contact_email,
"z_name"=>$r->contact_name,
);
$create = json_encode(
array('ticket' => array(
'subject' => $arr['z_subject'],
'description' => $arr['z_description'],
'requester' => array('name' => $arr['z_name'],
'email' => $arr['z_requester']
))),
JSON_FORCE_OBJECT
);
$data = $zendesk->call("/tickets", $create, "POST");
Any ideas?
Totally possible! You need to add some conditions to the trigger "Notify requester of received request" in Zendesk - Trigger setting to prevent zendesk from sending email. For ex:
Ticket : Channel - Is Not - Webservice (API)
Ticket : Tags - Contains one of the following - "offline message"
You could use another API endpoint "Ticket Import" https://developer.zendesk.com/api-reference/ticketing/tickets/ticket_import/
It's do not send notifications