PHP Queue stops working with Twilio invalid phone number - php

I am sending bulk email with Twilio using PHP/Laravel. I can't use Twilio notify because every phone number will use different text. Everything works fine if all phone numbers are valid, but if any invalid phone number occurs my queue stopped with error. Try catch block or failed function didn't work at all
My code:
public function handle()
{
$message = $this->client->messages->create(
$this->number,
[
'from' => $this->from,
'body' => $this->msg
]
);
}
(I have tried wrapping above function code inside try catch block also, but no luck)
Then I tried adding failed function
public function failed(\Exception $exception)
{
// Send user notification of failure, etc...
}
Still no luck.
I am getting following error:
array:4 [
"code" => 21211
"message" => "The 'To' number is not a valid phone number."
"more_info" => "https://www.twilio.com/docs/errors/21211"
"status" => 400
]
And i am using following queue command
php artisan queue:work --sleep=3 --tries=3 &
After getting Twilio error, If i stop command and run 2 more times, then only failed sms moved into failed_jobs table and execution continue to next jobs. Using laravel 7.x
Any help highly appreciated.
================================
Here is complete job
class BulkSms implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private $number;
private $msg;
private $client;
private $from;
/**
* Create a new job instance.
*
* #return void
*/
public function __construct($number, $from, $msg, $client)
{
$this->number = $number;
$this->from = $from;
$this->msg = $msg;
$this->client = $client;
}
/**
* Execute the job.
* #return void
*/
public function handle()
{
$message = $this->client->messages->create(
$this->number,
[
'from' => $this->from,
'body' => $this->msg
]
);
}
public function failed(\Exception $exception)
{
// Send user notification of failure, etc...
}
}
Above is called from controller something like
foreach ($candidateSMS as $number) {
BulkSms::dispatch($mobile, $this->from, $msgBody, $client)->delay(now()->addSecond(.2));
}

Related

Laravel mail queue subject not being applied

I always have lots of problems with Mail::queue and this time the subject is not being applied properly.
This is my class:
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class PlanExpiringOrExpired extends Mailable
{
use Queueable, SerializesModels;
private $payment = null;
public function __construct($payment)
{
$this->payment = $payment;
$this->subject($this->payment->subject);
\Log::debug("Subject: {$this->payment->subject}");
}
public function build()
{
$this->to($this->payment->email, $this->payment->name)
->view('mails/payment')
->with('payment', $this->payment);
return $this;
}
}
And I call it this way:
$payment = \App\Models\Payments::findOrFail($id);
$payment->subject = 'Your account has been canceled';
\Mail::queue(new \App\Mail\PlanExpiringOrExpired($payment));
The log saved correctly the following content:
[2023-02-12 11:00:04] local.DEBUG: Subject: Your account has been canceled
Yet the user received as subject: Plan Expiring or Expired (which is basically the class name).
Since I've done this change recently, do you think this might be a cache-related problem? If so, I'm using Supervisor to run queues, how do I clear the cache (through PHP) without messing up the production server?
I have used in the past something like this.
\Artisan::call('cache:clear');
But I'm not sure if this is correct, or if it has any implications for my production server.
Have you tried it this way to setup the proper subject?
private $payment = null;
public function __construct($payment)
{
$this->payment = $payment;
}
public function build()
{
$this->to($this->payment->email, $this->payment->name)
->subject($this->payment->subject)
->view('mails/payment')
->with('payment', $this->payment);
\Log::debug("Subject: {$this->payment->subject}");
return $this;
}
Move the subject set into build
iam doing like this in queue class, EmailContactForm is a mailable class.
public function handle()
{
$email = new EmailContactForm([
'locale' => $this->data['locale'],
'from_email' => $this->data['from_email'],
'name' => $this->data['name'],
'topic' => $this->data['topic'],
'subject' => $this->data['subject'],
'msg' => $this->data['msg']
]);
Mail::to($this->data['to_email'])
->bcc(config('app.mail_from_address'))
->send($email);
}
Solved.
It was indeed a cache problem, it is also necessary to restart the queue. My solution was to create a private endpoint like /superadmin/clear-cache and use it whenever I need.
Route::get('/superadmin/clear-cache', function()
{
\Artisan::call('cache:clear');
\Artisan::call('queue:restart');
});

Laravel Schedule not sending email

It's my first time trying to implement Task Scheduling, I'm trying to send automatic E-mails at a certain time:
Before implementing my cron I first tested my email sending code manually in a normal class to see if there is no error, and there was no error, the email was sent successfully.
After that, I started implementing the Task Scheduling
Democron.php
protected $signature = 'demo:cron';
protected $description = 'Command description';
public function __construct()
{
parent::__construct();
}
public function handle()
{
$tasks = Task::all();
$date = Carbon::now()->toDateTimeString();
foreach ($tasks as $task) {
if($task->completed_at != null){
$validad = $task->completed_at;
$receiver_id = User::findOrFail($task->user_id);
if($date > $validad){
$details = [
'task_id' =>$task->id,
'receiver_id' => $receiver_id
];
$subject = 'TeamWork - Você tem tarefas em atraso!';
$view = 'emails.project.delaydtask';
Mail::to($receiver_id->email)->send(new SendMail($details, $subject, $view));
Log::info('Email enviado com sucesso para '.$receiver_id->email);
}
}
}
}
Kernel.php
protected $commands = [
DemoCron::class,
];
protected function schedule(Schedule $schedule)
{
$schedule->command('demo:cron')
->twiceDaily(12, 15)
->timezone('Africa/Maputo');
}
protected function commands()
{
$this->load(__DIR__.'/Commands');
require base_path('routes/console.php');
}
I added to CRON JOBS on CPANEL
and set twiceDaily at 12 and 15
/usr/local/bin/php /.......myProjectPath/artisan schedule:run >> /dev/null 2>&1
I printed a LOG in my DemoCron.php to see if it really works
Result 1: when I select schedule once per minute it prints my LOG respecting all the conditions that are in my Democron.php , but it doesn't send the email.
Result 2: When I select a certain time (Twice per day or once a day) my LOG does not print anything and it does not send the email.
What am I doing wrong? Help me please!
UPDATE
my SendMail class that i use to send emails manually works perfectly,
but the scheduled emails are not going
class SendMail extends Mailable
{
use Queueable, SerializesModels;
public $details, $subject, $view;
public function __construct($details, $subject, $view)
{
$this->details = $details;
$this->subject = $subject;
$this->view = $view;
}
public function build()
{
return $this->subject($this->subject)
->view($this->view, ['details' => $this->details]);
}
}
After trying several times I found a workaround.
1- create a new controller
I created a new controller called MailController instead of using the Kernel.php and Democron.php classes that I generated through Laravel Scheduling
class MailController extends Controller
{
public function delayedtask(){
try {
$tasks = Task::all();
$date = Carbon::now()->toDateTimeString();
foreach ($tasks as $task) {
if($task->completed_at != null){
$validad = $task->completed_at;
$receiver_id = User::findOrFail($task->user_id);
if($date > $validad){
$details = [
'task_id' =>$task->id,
'receiver_id' => $receiver_id
];
$subject = 'TeamWork - Você tem tarefas em atraso!';
$view = 'emails.project.delaydtask';
Mail::to($receiver_id->email)->send(new SendMailQueue($details, $subject, $view));
Log::info('Email enviado com sucesso para '.$receiver_id->email);
}
}
}
return "Done!";
} catch (Exception $e) {
return "Something went wrong!";
}
}
}
2-add a new route
added a new route without Auth
Route::get('/delayedtask',[MailController::class, 'delayedtask']);
3-Added a cronjob on Cpanel
curl -s "https://myWebsiteURL/delayedtask">/dev/null 2>&1
First of all lets check all things:
Verify your mail configurations in your .env;
Verify in your email class if have implements ShouldQueue;
If you are implementing ShouldQueue, you must have to verify too your queue´s configuration in .env;
If is not implementing ShouldQueue, don´t miss time verifying queue´s config;
All right all things validated and still not sending email:
Add the Send mail in try catch and log the catch if something went wrong;
If don´t log nothing in try catch, try to create an command that just send a simple email;
If dosen´t work try to send an email by your mail in Cpanel, because this should be the problem;
Finally
In my cases using cPanel, I always create the croon task to all seconds like * * * * * and in the kernel of my laravel project I verify if some command must be executed with the laravel commands like ->twiceDaily(12, 15).
Try all things and if the error still, please update this thread!
I had the same problem,
i tried a new smtp email server
MAIL_HOST=pro.eu.turbo-smtp.com
MAIL_ENCRYPTION=ssl
instead of
MAIL_HOST=smtpauth.online.net
MAIL_ENCRYPTION=tls
I don't know if it's about the encryption or host features,
but it worked for me

SwiftMailer not sending mail in MessageHandler

I am using SwiftMailer in my Symfony 5 project to send emails.
I was using it in a controller to send a reset password e-mail, and everything was working.
I am now trying to use it in a MessageHandler, here is the code I am now using :
final class SendEmailMessageHandler implements MessageHandlerInterface
{
private $mailer;
public function __construct(\Swift_Mailer $mailer)
{
$this->mailer = $mailer;
}
public function __invoke(SendEmailMessage $message)
{
$mail = (new \Swift_Message())
->setFrom($message->getFrom())
->setTo($message->getTo())
->setBody($message->getBody(), $message->getContentType())
->setSubject($message->getSubject());
$response = $this->mailer->send($mail);
}
}
The response is ok, but the mail never reach my mailbox.
Here is how I am dispatching my SendEmailMessage :
class AskResetPassword extends AbstractController
{
use ResetPasswordControllerTrait;
private $resetPasswordHelper;
private $validator;
private $bus;
public function __construct(ResetPasswordHelperInterface $resetPasswordHelper, ValidatorInterface $validator, MessageBusInterface $bus)
{
$this->resetPasswordHelper = $resetPasswordHelper;
$this->validator = $validator;
$this->bus = $bus;
}
public function __invoke($data)
{
$emailConstraints = new Assert\Email();
$email = $data->getEmail();
if ($email) {
$errors = $this->validator->validate($email, $emailConstraints);
if (count($errors) === 0) {
return $this->processPasswordReset($email);
} else {
return new JsonResponse(['success' => false, 'error' => 'Invalid E-Mail format'], 404);
}
}
}
private function processPasswordReset($email)
{
$user = $this->getDoctrine()->getRepository(User::class)->findOneBy([
'email' => $email,
]);
$this->setCanCheckEmailInSession();
if (!$user) {
// Do not reveal whether a user account was found or not.
return new JsonResponse(['success' => true], 200);
}
try {
$resetToken = $this->resetPasswordHelper->generateResetToken($user);
} catch (ResetPasswordExceptionInterface $e) {
return new JsonResponse(['success' => false, 'error' => 'There was a problem handling your password reset request - ' . $e->getReason()]);
}
$message = new SendEmailMessage($email);
$message->setFrom('from.from#from.from');
$message->setBody(
$this->renderView('reset_password/email.html.twig', [
'resetToken' => $resetToken,
'tokenLifetime' => $this->resetPasswordHelper->getTokenLifetime()
])
);
$message->setSubject('Votre demande de changement de mot de passe');
$this->bus->dispatch($message);
return new JsonResponse(['success' => true], 200);
}
}
Here is my swiftmailer.yaml :
swiftmailer:
url: '%env(MAILER_URL)%'
spool: { type: 'memory' }
Can you help me ?
The answer is "DO NOT spool emails unless you want to process them later".
Check docs Spool Emails
A spooler is a queue mechanism which will process your message queue one by one. This was introduced when swift-mailer was rewritten and added back to symfony. In combination with Messenger Component which provides abstract interface MessageBusInterface, it would delegate to right backend service which can be smtp relay, push notification or any other type of RPC which may trigger actions on separate web services.
As symfony adds new capabilities to the message bus, this feature was added to utilize it for message queues & other services where transactional emails and notifications are processed separately.
To process your spool simply run :
APP_ENV=prod php bin/console swiftmailer:spool:send
In typical installation spooling is disabled, when you enable spooling in memory, it will wait till request is finished and kernel is about to exit. If you are using anything else to debug that terminates kernel halfway or there are other components & parts of application that keeps kernel in memory, events will not be triggered and mail will not be sent.
You can check whole documentation here : Sending Emails
I cant remember the exact reason why and at the time of posting this I'm struggling to find the answer but the swiftmailer type must be file instead of memory. This GitHub issue references this. You can also see how to change the type here.

Laravel broadcasting channel function won't fire

So I'm gonna use laravel broadcasting for a chat app,
I followed Laravel Broadcasting approach,
Uncommented App\Providers\BroadcastServiceProvider
from providers array inside config/app.php
Registered in pusher website, made a channel
and filled the fields below inside .env file with my pusher channel info
PUSHER_APP_ID
PUSHER_APP_KEY
PUSHER_APP_SECRET
PUSHER_APP_CLUSTER
Inside my broadcast.php config file where I set the default driver to pusher, I also added
'options' => [
'cluster' => 'us2',
'encrypted' => true,
],
to pusher array inside connections array based on my channel info in pusher panel
Installed pusher php package on my laravel project using composer require pusher/pusher-php-server "~3.0" command
Here is my event class
<?php
namespace App\Events;
use App\User;
use App\TherapyMessage;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use App\AppLog;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
class TherapyMessageSent implements ShouldBroadcastNow
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* User that sent the message
*
* #var User
*/
public $user;
/**
* Message details
*
* #var Message
*/
public $message;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct(User $user, TherapyMessage $message)
{
$this->user = $user;
$this->message = $message;
}
/**
* Get the channels the event should broadcast on.
*
* #return Channel|array
*/
public function broadcastOn()
{
$message_id = $this->message->id;
$user = $this->user;
AppLog::create([
'file_name' => __FILE__,
'message' => "broadcast before send with Message ID of $message_id from $user->full_name"
]);
return new PrivateChannel("therapy-chat.$message_id");
}
}
The AppLog is a model that I use for logging inside the project
I tried implementing ShouldBroadcast interface at first but that didn't work either
I also registered my event inside EventServiceProvider.php file and run php artisan event:generate command, here is the EventServiceProvider $listen array:
protected $listen = [
Registered::class => [
SendEmailVerificationNotification::class,
TherapyMessageSent::class
],
];
I also imported the event namespace next to other namespaces inside the file:
use \App\Events\TherapyMessageSent;
Here is the channel that I defined inside routes/channels.php file:
use App\AppLog;
Broadcast::channel('therapy-chat.{message_id}', function ($user, $message_id) {
AppLog::create([
'file_name' => __FILE__,
'message' => "broadcast sending with Message ID of $message_id to $user->full_name"
]);
if (!Auth::check()) return false;
$message = \App\TherapyMessage::find($message_id);
if (!$message) {
AppLog::create([
'file_name' => __FILE__,
'message' => "Message with ID of $message_id Not Found for broadcasting"
]);
return false;
}
$will_send = false;
if ($therapist = $user->therapist) {
$will_send = $therapist->id === $message->therapist_id;
} else if ($patient = $user->patient) {
$will_send = $message->patient_id === $patient->id;
}
if ($will_send) {
AppLog::create([
'file_name' => __FILE__,
'message' => "Message with ID of $message_id broadcasted to $user->full_name"
]);
}
return $will_send;
});
Finally, this is my controller method:
public function sendToTherapist(Request $request) {
$validation = \Validator::make($request->all(), ['message' => 'required']);
if ($validation->fails()) return $this->validationError($validation);
$user = \Auth::user();
$patient = $user->patient;
$therapist = $patient->therapist;
if (!$therapist) return $this->errorWithMessage('Patient does not have Therapist');
$message = \App\TherapyMessage::create([
'patient_id' => $patient->id,
'therapist_id' => $therapist->id,
'type' => TherapyMessageType::TEXT,
'text' => $request->message,
'sender_role' => TherapyMessageSenderRole::PATIENT
]);
broadcast(new TherapyMessageSent($user, $message))->toOthers();
return $this->success(['id' => $message->id]);
}
My controller class extends from BaseController which is a custom controller class with helper methods such as success(), validationError() and errorWithMessage()
As you see in the code above
I filled $user and $message with correct values and the request works without any error
I think the channel method won't even be fired,
as I check the AppLog table when I call broadcast method, only the log inside TherapyMessageSent event broadcastOn function is saved
and even the log that I save at the beginning of channels.php method, isn't saved so I think this method is never executed.
If anyone could help me with the problem, I'd be thankful.

Laravel: Actively listening events from external service

I'm developing a PBX monitoring app with Laravel. On PBX side, Asterisk is managing the calls.
I can use PAMI client to receive all events from Asterisk Manager Interface. If I simply run this script from console, all the events on Asterisk are printed on the screen in real time:
<?php
require __DIR__ . '/vendor/autoload.php';
use PAMI\Client\Impl\ClientImpl as PamiClient;
use PAMI\Message\Event\EventMessage;
use PAMI\Listener\IEventListener;
$pamiClientOptions = array(
'host' => '',
'scheme' => 'tcp://',
'port' => 5038,
'username' => '',
'secret' => '',
'connect_timeout' => 10000,
'read_timeout' => 10000
);
$pamiClient = new PamiClient($pamiClientOptions);
// Open the connection
$pamiClient->open();
$pamiClient->registerEventListener(function (EventMessage $event) {
var_dump($event);
});
$running = true;
// Main loop
while($running) {
$pamiClient->process();
usleep(1000);
}
// Close the connection
$pamiClient->close();
What I'm trying to do is implementing events&listeners in Laravel with PAMI. But it doesn't seem to work.
I have registered the event and listener on EventServiceProvider class:
protected $listen = [
'App\Events\AmiEventOccurred' => [
'App\Listeners\LogAmiEvent',
],
My event class:
<?php
namespace App\Events;
use Illuminate\Queue\SerializesModels;
use Illuminate\Foundation\Events\Dispatchable;
class AmiEventOccurred
{
use Dispatchable, SerializesModels;
public $message;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct()
{
$pamiClientOptions = array(
'host' => getenv('PAMI_HOST'),
'scheme' => getenv('PAMI_SCHEME'),
'port' => getenv('PAMI_PORT'),
'username' => getenv('PAMI_USERNAME'),
'secret' => getenv('PAMI_SECRET'),
'connect_timeout' => getenv('PAMI_CONNECT_TIMEOUT'),
'read_timeout' => getenv('PAMI_READ_TIMEOUT'),
);
$pamiClient = new \PAMI\Client\Impl\ClientImpl($pamiClientOptions);
// Open the connection
$pamiClient->open();
$pamiClient->registerEventListener(function (\PAMI\Message\Event\EventMessage $message) {
$this->message = $message;
});
$running = true;
// Main loop
while($running) {
$pamiClient->process();
usleep(1000);
}
// Close the connection
$pamiClient->close();
}
}
My listener class:
<?php
namespace App\Listeners;
use App\Events\AmiEventOccurred;
class LogAmiEvent
{
/**
* Create the event listener.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* #param AmiEventOccurred $event
* #return void
*/
public function handle(AmiEventOccurred $event)
{
Log::debug(print_r($event, true));
}
}
Is this a correct approach? Is it possible to actively listen to another service in real time with Laravel events, or should I develop another app for it to run in real time on the background and trigger Laravel events when necessary?
All of this code doesn't belong in an event. An event is just for announcing that something has happened, and containing all of the important information about what it was. Your code for running a client and sleeping and all of that should be somewhere else, likely an artisan command that your kernel keeps running. Then in that code, when something happens, you trigger a simple event with the message for any listeners to act on:
$pamiClient->registerEventListener(function (\PAMI\Message\Event\EventMessage $message) {
new AmiMessageReceived($message);
});

Categories