Laravel Notifications: Mail (Mailgun) Response on NotificationSent event - php

I am using Mailgun as a mail driver in my laravel application, as well as nexmo for SMS purposes.
What I am trying to achieve is to maintain the delivery status of the notifications that are sent either via Mailgun or Nexmo. Incase of Nexmo I am able achieve this, since I get the nexmo MessageId in the NotificationSent event that is fired after processing a notification.
However in the event instance for email, the response is empty.
Any idea what I am missing or, how I can retrieve the mailgun message-id?

I have found a workaround that does the job for now. Not as neat as I want it to be, but posting for future references incase anyone needs this.
I have created a custom notification channel extending Illuminate\Notifications\Channels\MailChannel
class EmailChannel extends MailChannel
{
/**
* Send the given notification.
*
* #param mixed $notifiable
* #param \Illuminate\Notifications\Notification $notification
* #return void
*/
public function send($notifiable, Notification $notification)
{
if (! $notifiable->routeNotificationFor('mail')) {
return;
}
$message = $notification->toMail($notifiable);
if ($message instanceof Mailable) {
return $message->send($this->mailer);
}
$this->mailer->send($message->view, $message->data(), function ($m) use ($notifiable, $notification, $message) {
$recipients = empty($message->to) ? $notifiable->routeNotificationFor('mail') : $message->to;
if (! empty($message->from)) {
$m->from($message->from[0], isset($message->from[1]) ? $message->from[1] : null);
}
if (is_array($recipients)) {
$m->bcc($recipients);
} else {
$m->to($recipients);
}
if ($message->cc) {
$m->cc($message->cc);
}
if (! empty($message->replyTo)) {
$m->replyTo($message->replyTo[0], isset($message->replyTo[1]) ? $message->replyTo[1] : null);
}
$m->subject($message->subject ?: Str::title(
Str::snake(class_basename($notification), ' ')
));
foreach ($message->attachments as $attachment) {
$m->attach($attachment['file'], $attachment['options']);
}
foreach ($message->rawAttachments as $attachment) {
$m->attachData($attachment['data'], $attachment['name'], $attachment['options']);
}
if (! is_null($message->priority)) {
$m->setPriority($message->priority);
}
$message = $notification->getMessage(); // I have this method in my notification class which returns an eloquent model
$message->email_id = $m->getSwiftMessage()->getId();
$message->save();
});
}
}
I am still looking for a solution to achieve this with NotificationSent event.

When looking at the code (MailgunTransport) it will do the following
$this->client->post($this->url, $this->payload($message, $to));
$this->sendPerformed($message);
return $this->numberOfRecipients($message);
Since the Laravel contract requires the implementation to send back the number of e-mails send.
Even if you would be able to get into the mail transport it doesn't store the response from for this reason it not possible to catch the message id.
What you could do is to implement your own (or look in packagist) to adapt mail client but this is not a perfect solution and will require some ugly instanceof checks.

Related

I can't read the body of an email

I'm using the laravel-imap package to read my PEC emails from a management system.
For the uninitiated, the PEC is the Certified Electronic Mail, "Posta Elettronica Certificata, (PEC)", it is the system that allows you to send e-mails with legal value equivalent to a registered letter with return receipt.
I can read the Subject thanks to the getSubject() method, I can count the number of attachments but I can't read the body of the email.
I also tried to use the getHTMLBody(), getTextBody() methods but I get nothing, i.e. I get a blank string.
I tried doing a
dd($message->getHTMLBody(true));
but I get null.
This is my code:
public function mail()
{
$oClient = Client::account('default');
$oClient->connect();
$folders = $oClient->getFolders();
/** #var \Webklex\PHPIMAP\Folder $folder */
foreach ($folders as $folder) {
/** #var \Webklex\PHPIMAP\Support\MessageCollection $messages */
//$messages = $folder->messages()->all()->get();
$messages = $folder->query()->since(now()->subDays(30))->get();
}
foreach($messages as $message){
echo $message->getSubject().'<br />';
echo 'Attachments: '.$message->getAttachments()->count().'<br />';
echo $message->getHTMLBody(true);
}
}
Can anyone help me please? I'm desperate.

Laravel: Get bad_domains from Mailable

I am sending mails with Laravel like this:
foreach ($users as $user) {
\Mail::to($user())->send(new Newsletter($user));
}
I would like to have an array of all the users who had a bad_domain response. I found in the docs that Laravel uses Swiftmailer which has a way to find bad_domain respones:
// Pass a variable name to the send() method
if (!$mailer->send($message, $failures))
{
echo "Failures:";
print_r($failures);
}
/*
Failures:
Array (
0 => receiver#bad-domain.org,
1 => other-receiver#bad-domain.org
)
*/
However, I want to use the a Mailable class. I am not sure how I can do this with the Swiftmailer (which I can access through \Mail::getSwiftMailer()).
Is there any easy way of getting the bad_domains when using Mailable from Laravel?
You may only access bad_domains, but not bounces with Swiftmailer (Swiftmailer 4 does not retrieve bounces as $failedRecipients).
One can get bad_domains it with
\Mail::to($user)->send(new \App\Mail\Hi());
dd(\Mail::failures());
See Illuminate\Mail\Mailer.php
/**
* Send a Swift Message instance.
*
* #param \Swift_Message $message
* #return void
*/
protected function sendSwiftMessage($message)
{
try {
return $this->swift->send($message, $this->failedRecipients);
} finally {
$this->forceReconnection();
}
}

How to know if a specific notification has been sent in laravel

I am building a site where users can buy a plan for whatever estimated time they require. After they buy the plan I created a middleware to check when the plan is almost finished (70% into the plan) then send a notification . Now the issue is I am trying to find out if the Notification in question has already been sent before sending another notification. I used database notification method.
To achieve this I had to create a Notification model to interact with my notification table.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Notification extends Model
{
protected $table= "notifications";
}
The middle ware in question
public function handle($request, Closure $next)
{
// dd(Carbon::now()->format("Y-m-d"));
$plan = Booking::where("user_id", Auth::id())
->where("approve", true)
->where("end_date", '>', Carbon::now()->format("Y-m-d"))
->get();
$i =0;
$save = [];
foreach($plan as $plan){
$save[$i] = (new Plan)->percentageMeter($plan->start_date, $plan->end_date);
if($save[$i] >= 70){
$realNotify= Notification::where("notifiable_id", $plan->user_id)
->get();
foreach($realNotify as $notify){
if($notify->data['plan_id'] == $plan->id && realNotify ==true ){
continue;
}
else{
ComNotify::planAboutToExpire(Booking::find($plan->id));
}
}
continue;
}
$i++;
}
// dd($save);
}
one of the major reason I have this problem is that I cant access the "plan_id" object because it does not have its own table column.
Is there a Laravel way to do this? a neat way.
public function toArray($notifiable)
{
return [
"type"=>"plan_about_to_expire",
"message"=>"Your plan will soon expire on " . $this->plan->end_date,
"plan_id"=>$this->plan->id,
];
}
My notification toArray method
Not sure what version you are using,
however in 5.3 and beyond you could use the notifications database and check if the user has that 'type' of notification.

Laravel: Invalid argument supplied foreach()

Working with a project that uses OAuth user registration through GitHub. Everything is working fine until the last step of confirming the account through my application.
Here is the function in question:
/**
* Get the primary, verified email address from the Github data.
*
* #param mixed $emails
* #return mixed
*/
protected function getPrimaryEmail($emails)
{
foreach ($emails as $email) {
if (! $email->primary) {
continue;
}
if ($email->verified) {
return $email->email;
}
throw new GithubEmailNotVerifiedException;
}
return null;
}
Anyone else ever experience this when working with OAuth and GitHub? Thank you
Not to be an arse, but it sounds like emails is not an array. Is it possibly a null?
Log the actual value with Log::debug('WTF IS THIS THEN?!!: '.print_r($emails, true)); and see.

RabbitMQ wait for multiple queues to finish

Ok here is an overview of what's going on:
M <-- Message with unique id of 1234
|
+-Start Queue
|
|
| <-- Exchange
/|\
/ | \
/ | \ <-- bind to multiple queues
Q1 Q2 Q3
\ | / <-- start of the problem is here
\ | /
\ | /
\|/
|
Q4 <-- Queues 1,2 and 3 must finish first before Queue 4 can start
|
C <-- Consumer
So I have an exchange that pushes to multiple queues, each queue has a task, once all tasks are completed, only then can Queue 4 start.
So message with unique id of 1234 gets sent to the exchange, the exchange routes it to all the task queues ( Q1, Q2, Q3, etc... ), when all the tasks for message id 1234 have completed, run Q4 for message id 1234.
How can I implement this?
Using Symfony2, RabbitMQBundle and RabbitMQ 3.x
Resources:
http://www.rabbitmq.com/tutorials/amqp-concepts.html
http://www.rabbitmq.com/tutorials/tutorial-six-python.html
UPDATE #1
Ok I think this is what I'm looking for:
https://github.com/videlalvaro/Thumper/tree/master/examples/parallel_processing
RPC with Parallel Processing, but how do I set the Correlation Id to be my unique id to group the messages and also identify what queue?
You need to implement this: http://www.eaipatterns.com/Aggregator.html but the RabbitMQBundle for Symfony doesn't support that so you would have to use the underlying php-amqplib.
A normal consumer callback from the bundle will get an AMQPMessage. From there you can access the channel and manually publish to whatever exchanges comes next in your "pipes and filters" implementation
In the RPC tutorial at RabbitMQ's site, there is a way to pass around a 'Correlation id' that can identify your messages to users in the queue.
I'd recommend using some sort of id with your messages into the first 3 queues and then have another process to dequeue messages from the 3 into buckets of some sort. When those buckets receive what I'm assuming is the completion of there 3 tasks, send the final message off to the 4th queue for processing.
If you are sending more than 1 work item to each queue for one user, you might have to do a little preprocessing to find out how many items a particular user placed into the queue so the process dequeuing before 4 knows how many to expect before queuing up.
I do my rabbitmq in C#, so sorry my pseudo code isn't in php style
// Client
byte[] body = new byte[size];
body[0] = uniqueUserId;
body[1] = howManyWorkItems;
body[2] = command;
// Setup your body here
Queue(body)
// Server
// Process queue 1, 2, 3
Dequeue(message)
switch(message.body[2])
{
// process however you see fit
}
processedMessages[message.body[0]]++;
if(processedMessages[message.body[0]] == message.body[1])
{
// Send to queue 4
Queue(newMessage)
}
Response to Update #1
Instead of thinking of your client as a terminal, it might be useful to think of the client as a process on a server. So if you setup an RPC client on a server like this one, then all you need to do is have the server handle the generation of a unique id of a user and send the messages to the appropriate queues:
public function call($uniqueUserId, $workItem) {
$this->response = null;
$this->corr_id = uniqid();
$msg = new AMQPMessage(
serialize(array($uniqueUserId, $workItem)),
array('correlation_id' => $this->corr_id,
'reply_to' => $this->callback_queue)
);
$this->channel->basic_publish($msg, '', 'rpc_queue');
while(!$this->response) {
$this->channel->wait();
}
// We assume that in the response we will get our id back
return deserialize($this->response);
}
$rpc = new Rpc();
// Get unique user information and work items here
// Pass even more information in here, like what queue to use or you could even loop over this to send all the work items to the queues they need.
$response = rpc->call($uniqueUserId, $workItem);
$responseBuckets[array[0]]++;
// Just like above code that sees if a bucket is full or not
I am a little unclear on what you are trying to achieve here. But I would probably alter the design somewhat so that once all messages are cleared from the queues you publish to a separate exchange which publishes to queue 4.
In addition to my RPC based answer I want to add another one which is based on EIP aggregator pattern.
The idea is next: Everything is async, no RPC or other sync things. Every task sends an even when it is done, The aggregator is subscribed to that event. It basically counts tasks and sends task4 message when the counter reaches expected number (in our case 3). I choose a filesystem as a storage for counters for the Sake of simplicity. You can use a database there.
The producer looks simpler. It just fires and forgets
<?php
use Enqueue\Client\Message;
use Enqueue\Client\ProducerInterface;
use Enqueue\Util\UUID;
use Symfony\Component\DependencyInjection\ContainerInterface;
/** #var ContainerInterface $container */
/** #var ProducerInterface $producer */
$producer = $container->get('enqueue.client.producer');
$message = new Message('the task data');
$message->setCorrelationId(UUID::generate());
$producer->sendCommand('task1', clone $message);
$producer->sendCommand('task2', clone $message);
$producer->sendCommand('task3', clone $message);
The task processor has to send an event once its job is done:
<?php
use Enqueue\Client\CommandSubscriberInterface;
use Enqueue\Client\Message;
use Enqueue\Client\ProducerInterface;
use Enqueue\Psr\PsrContext;
use Enqueue\Psr\PsrMessage;
use Enqueue\Psr\PsrProcessor;
class Task1Processor implements PsrProcessor, CommandSubscriberInterface
{
private $producer;
public function __construct(ProducerInterface $producer)
{
$this->producer = $producer;
}
public function process(PsrMessage $message, PsrContext $context)
{
// do the job
// same for other
$eventMessage = new Message('the event data');
$eventMessage->setCorrelationId($message->getCorrelationId());
$this->producer->sendEvent('task_is_done', $eventMessage);
return self::ACK;
}
public static function getSubscribedCommand()
{
return 'task1';
}
}
And the aggregator processor:
<?php
use Enqueue\Client\TopicSubscriberInterface;
use Enqueue\Psr\PsrContext;
use Enqueue\Psr\PsrMessage;
use Enqueue\Psr\PsrProcessor;
use Symfony\Component\Filesystem\LockHandler;
class AggregatorProcessor implements PsrProcessor, TopicSubscriberInterface
{
private $producer;
private $rootDir;
/**
* #param ProducerInterface $producer
* #param string $rootDir
*/
public function __construct(ProducerInterface $producer, $rootDir)
{
$this->producer = $producer;
$this->rootDir = $rootDir;
}
public function process(PsrMessage $message, PsrContext $context)
{
$expectedNumberOfTasks = 3;
if (false == $cId = $message->getCorrelationId()) {
return self::REJECT;
}
try {
$lockHandler = new LockHandler($cId, $this->rootDir.'/var/tasks');
$lockHandler->lock(true);
$currentNumberOfProcessedTasks = 0;
if (file_exists($this->rootDir.'/var/tasks/'.$cId)) {
$currentNumberOfProcessedTasks = file_get_contents($this->rootDir.'/var/tasks/'.$cId);
if ($currentNumberOfProcessedTasks +1 == $expectedNumberOfTasks) {
unlink($this->rootDir.'/var/tasks/'.$cId);
$this->producer->sendCommand('task4', 'the task data');
return self::ACK;
}
}
file_put_contents($this->rootDir.'/var/tasks/'.$cId, ++$currentNumberOfProcessedTasks);
return self::ACK;
} finally {
$lockHandler->release();
}
}
public static function getSubscribedTopics()
{
return 'task_is_done';
}
}
I can show you how you can do it with enqueue-bundle.
So install it with composer and register as any other bundle. Then configure:
// app/config/config.yml
enqueue:
transport:
default: 'amnqp://'
client: ~
This approach is based on RPC. Here's how you do it:
<?php
use Enqueue\Client\ProducerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/** #var ContainerInterface $container */
/** #var ProducerInterface $producer */
$producer = $container->get('enqueue.client.producer');
$promises = new SplObjectStorage();
$promises->attach($producer->sendCommand('task1', 'the task data', true));
$promises->attach($producer->sendCommand('task2', 'the task data', true));
$promises->attach($producer->sendCommand('task3', 'the task data', true));
while (count($promises)) {
foreach ($promises as $promise) {
if ($replyMessage = $promise->receiveNoWait()) {
// you may want to check the response here
$promises->detach($promise);
}
}
}
$producer->sendCommand('task4', 'the task data');
The consumer processor looks like this:
use Enqueue\Client\CommandSubscriberInterface;
use Enqueue\Consumption\Result;
use Enqueue\Psr\PsrContext;
use Enqueue\Psr\PsrMessage;
use Enqueue\Psr\PsrProcessor;
class Task1Processor implements PsrProcessor, CommandSubscriberInterface
{
public function process(PsrMessage $message, PsrContext $context)
{
// do task job
return Result::reply($context->createMessage('the reply data'));
}
public static function getSubscribedCommand()
{
// you can simply return 'task1'; if you do not need a custom queue, and you are fine to use what enqueue chooses.
return [
'processorName' => 'task1',
'queueName' => 'Q1',
'queueNameHardcoded' => true,
'exclusive' => true,
];
}
}
Add it to your container as a service with a tag enqueue.client.processor and run command bin/console enqueue:consume --setup-broker -vvv
Here's the plain PHP version.

Categories