How get response after email message send. Symfony 4.3 mailer component - php

How get provider response result after mail send.
For example i send message via symfony/mailgun and want to get message-uid from provider after sending message
$email = (new Email())
->from('test#mail.com')
->to('foo#bar')
->subject('Send email test')
->text('email text');
$this->mailer->send($email); // is there any way to return response result instead void

Facing the same issue I came up with following solution:
Create your own handler for SendEmailMessage (inject original handler and use it's result for your purpose)
<?php
namespace MessengerBundle\MessageHandler;
use Symfony\Component\Mailer\Messenger\MessageHandler;
use Symfony\Component\Mailer\Messenger\SendEmailMessage;
use Symfony\Component\Mailer\SentMessage;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
class SendEmailMessageHandler implements MessageHandlerInterface
{
/**
* #var MessageHandler
*/
private $defaultHandler;
/**
* SendEmailMessageHandler constructor.
* #param MessageHandler $defaultMessageHandler
*/
public function __construct(MessageHandler $defaultMessageHandler)
{
$this->defaultHandler = $defaultMessageHandler;
}
/**
* #param SendEmailMessage $message
* #return SentMessage|null
*/
public function __invoke(SendEmailMessage $message): ?SentMessage
{
$handler = $this->defaultHandler;
$sentMessage = $handler($message);
//your logic here
return $sentMessage;
}
}
Register it as a service:
MessengerBundle\MessageHandler\SendEmailMessageHandler:
autowire: true
autoconfigure: true
public: false
Replace original Symfony service with yours, otherwise mail will be sent twice (by original and your handlers):
mailer.messenger.message_handler:
class: MessengerBundle\MessageHandler\SendEmailMessageHandler
If this async approach don't fits to you, you can try this hack to extend mail service by adding getSentMessage into it.

Related

Throttle Laravel Exception email notification per Exception type

Does anyone know of a way to throttle email notifications for a certain Exception in Laravel?
I made my app send me an email when there is a DB error by check for the QueryException. This is a rough example of what I did in Exception handler in Laravel:
class Handler{
/**
* Report or log an exception.
*
* This is a great spot to send exceptions to Sentry, Bugsnag, etc.
*
* #param \Exception $exception
* #return void
*/
public function report(Exception $e)
{
if($e instanceof QueryException){
if( App::environment(['production']) ){
Notification::route('mail', 'myemail#test.com')
->notify(new DbErrorNotification($e));
}
}
parent::report($e);
}
}
Short of tracking in DB, is there a way I could throttle DB errors by exception type so that I don't end up getting thousands of emails if there is a consistent DB error.
I looked at Swift Mailer's anti-flood and throttling plugins but those affect the system globally which I don't want to do.
Thank you in advance
There is a few way how you can achieve that. Right before dispatching a job you can add delay on it. Example:
use App\Http\Request;
use App\Jobs\SendEmail;
use App\Mail\VerifyEmail;
use Carbon\Carbon;
/**
* Store a newly created resource in storage.
*
* #param Request $request
* #return \Illuminate\Http\RedirectResponse
* #throws \Symfony\Component\HttpKernel\Exception\HttpException
*/
public function store(Request $request)
{
$baseDelay = json_encode(now());
$getDelay = json_encode(
cache('jobs.' . SendEmail::class, $baseDelay)
);
$setDelay = Carbon::parse(
$getDelay->date
)->addSeconds(10);
cache([
'jobs.' . SendEmail::class => json_encode($setDelay)
], 5);
SendEmail::dispatch($user, new VerifyEmail($user))
->delay($setDelayTime);
}
Or if you don't like the idea about a job you can also delay it via Mail. Example:
Mail::to($user)->later($setDelayTime);
And finally via Redis Rate Limiting. Example:
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Redis;
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
Redis::throttle('SendEmail')
->allow(1)
->every(10)
->then(function () {
Mail::to($this->user)->send($this->mail);
}, function () {
return $this->release(10);
});
}
Allowing one email to be sent every ten seconds. The string SendEmail passed to the throttle() method is a name that uniquely identifies the type of job being rate-limited. You can set this to whatever you want.
The release() method is an inherited member of your job class and
instructs Laravel to release the job back onto the queue, with an
optional delay in seconds, in the event a lock cannot be obtained.
When the job is dispatched to the queue, Redis is instructed to only
run one SendEmail job every ten seconds.
Keep in mind that for all of this you need a Redis
Source: https://medium.com/#bastones/a-simple-guide-to-queuing-mail-in-laravel-f4ff94cdaa59

How to extend or make custom PasswordBroker sendResetLink() method in Laravel 5.8?

Currently the logic behind Resetting Password is that user must provide valid/registered e-mail to receive password recovery e-mail.
In my case I don't want to validate if the e-mail is registered or not due to security concerns and I want to just do the check in back-end and tell user that "If he has provided registered e-mail, he should get recovery e-mail shortly".
What I've done to achieve this is edited in vendor\laravel\framework\src\Illuminate\Auth\Passwords\PasswordBroker.php sendResetLink() method from this:
/**
* Send a password reset link to a user.
*
* #param array $credentials
* #return string
*/
public function sendResetLink(array $credentials)
{
// First we will check to see if we found a user at the given credentials and
// if we did not we will redirect back to this current URI with a piece of
// "flash" data in the session to indicate to the developers the errors.
$user = $this->getUser($credentials);
if (is_null($user)) {
return static::INVALID_USER;
}
// Once we have the reset token, we are ready to send the message out to this
// user with a link to reset their password. We will then redirect back to
// the current URI having nothing set in the session to indicate errors.
$user->sendPasswordResetNotification(
$this->tokens->create($user)
);
return static::RESET_LINK_SENT;
}
to this:
/**
* Send a password reset link to a user.
*
* #param array $credentials
* #return string
*/
public function sendResetLink(array $credentials)
{
// First we will check to see if we found a user at the given credentials and
// if we did not we will redirect back to this current URI with a piece of
// "flash" data in the session to indicate to the developers the errors.
$user = $this->getUser($credentials);
// if (is_null($user)) {
// return static::INVALID_USER;
// }
// Once we have the reset token, we are ready to send the message out to this
// user with a link to reset their password. We will then redirect back to
// the current URI having nothing set in the session to indicate errors.
if(!is_null($user)) {
$user->sendPasswordResetNotification(
$this->tokens->create($user)
);
}
return static::RESET_LINK_SENT;
}
This hard-coded option is not the best solution because it will disappear after update.. so I would like to know how can I extend or implement this change within the project scope within App folder to preserve this change at all times?
P.S. I've tried solution mentioned here: Laravel 5.3 Password Broker Customization but it didn't work.. also directory tree differs and I couldn't understand where to put new PasswordBroker.php file.
Thanks in advance!
Here are the steps you need to follow.
Create a new custom PasswordResetsServiceProvider. I have a folder (namespace) called Extensions where I'll place this file:
<?php
namespace App\Extensions\Passwords;
use Illuminate\Auth\Passwords\PasswordResetServiceProvider as BasePasswordResetServiceProvider;
class PasswordResetServiceProvider extends BasePasswordResetServiceProvider
{
/**
* Indicates if loading of the provider is deferred.
*
* #var bool
*/
protected $defer = true;
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
$this->registerPasswordBroker();
}
/**
* Register the password broker instance.
*
* #return void
*/
protected function registerPasswordBroker()
{
$this->app->singleton('auth.password', function ($app) {
return new PasswordBrokerManager($app);
});
$this->app->bind('auth.password.broker', function ($app) {
return $app->make('auth.password')->broker();
});
}
}
As you can see this provider extends the base password reset provider. The only thing that changes is that we are returning a custom PasswordBrokerManager from the registerPasswordBroker method. Let's create a custom Broker manager in the same namespace:
<?php
namespace App\Extensions\Passwords;
use Illuminate\Auth\Passwords\PasswordBrokerManager as BasePasswordBrokerManager;
class PasswordBrokerManager extends BasePasswordBrokerManager
{
/**
* Resolve the given broker.
*
* #param string $name
* #return \Illuminate\Contracts\Auth\PasswordBroker
*
* #throws \InvalidArgumentException
*/
protected function resolve($name)
{
$config = $this->getConfig($name);
if (is_null($config)) {
throw new InvalidArgumentException(
"Password resetter [{$name}] is not defined."
);
}
// The password broker uses a token repository to validate tokens and send user
// password e-mails, as well as validating that password reset process as an
// aggregate service of sorts providing a convenient interface for resets.
return new PasswordBroker(
$this->createTokenRepository($config),
$this->app['auth']->createUserProvider($config['provider'] ?? null)
);
}
}
Again, this PasswordBrokerManager extends the base manager from laravel. The only difference here is the new resolve method which returns a new and custom PasswordBroker from the same namespace. So the last file we'll create a custom PasswordBroker in the same namespace:
<?php
namespace App\Extensions\Passwords;
use Illuminate\Auth\Passwords\PasswordBroker as BasePasswordBroker;
class PasswordBroker extends BasePasswordBroker
{
/**
* Send a password reset link to a user.
*
* #param array $credentials
* #return string
*/
public function sendResetLink(array $credentials)
{
// First we will check to see if we found a user at the given credentials and
// if we did not we will redirect back to this current URI with a piece of
// "flash" data in the session to indicate to the developers the errors.
$user = $this->getUser($credentials);
// if (is_null($user)) {
// return static::INVALID_USER;
// }
// Once we have the reset token, we are ready to send the message out to this
// user with a link to reset their password. We will then redirect back to
// the current URI having nothing set in the session to indicate errors.
if(!is_null($user)) {
$user->sendPasswordResetNotification(
$this->tokens->create($user)
);
}
return static::RESET_LINK_SENT;
}
}
As you can see we extend the default PasswordBroker class from Laravel and only override the method we need to override.
The final step is to simply replace the Laravel Default PasswordReset broker with ours. In the config/app.php file, change the line that registers the provider as such:
'providers' => [
...
// Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
App\Extensions\Passwords\PasswordResetServiceProvider::class,
...
]
That's all you need to register a custom password broker. Hope that helps.
The easiest solution here would be to place your customised code in app\Http\Controllers\Auth\ForgotPasswordController - this is the controller that pulls in the SendsPasswordResetEmails trait.
Your method overrides the one provided by that trait, so it will be called instead of the one in the trait. You could override the whole sendResetLinkEmail method with your code to always return the same response regardless of success.
public function sendResetLinkEmail(Request $request)
{
$this->validateEmail($request);
// We will send the password reset link to this user. Once we have attempted
// to send the link, we will examine the response then see the message we
// need to show to the user. Finally, we'll send out a proper response.
$response = $this->broker()->sendResetLink(
$request->only('email')
);
return back()->with('status', "If you've provided registered e-mail, you should get recovery e-mail shortly.");
}
You can just override the sendResetLinkFailedResponse method in your ForgetPasswordController class.
protected function sendResetLinkFailedResponse(Request $request, $response)
{
return $this->sendResetLinkResponse($request, Password::RESET_LINK_SENT);
}
We'll just send the successful response even if the validation failed.

Laravel pass data into mail for different recipents

I just created a newsletter in Laravel, where people can register with their email-adress and then somebody can send a mail to those who are registered.
Then I save those registrations in a table, together with the relating id of the sender of the newsletter (in my case its a seller, and buyers register for a newsletter)....
This is the code I use then so send a newsletter:
public function sendNewsletter(Request $request){
$newsletterText = $request->newsletterText;
$seller = Auth::user();
$sellerID = $seller->id;
$newsletterMailAdresses = Newsletter::where('seller_id', $sellerID)->pluck('mailAdress');
Mail::bcc($newsletterMailAdresses)->send(new newsletterMail($newsletterText));
return "<p style='color:green;'>Newsletter erfolgreich versandt</p>";
}
Its called with an AJAX request, but shouldn't change anything...
What I need now is to give the users the possibility to unsubscribe from the newsletter as well, via a link in the mail.
This is the corresponding mail class:
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
class newsletterMail extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*
* #return void
*/
public $newsletterText;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct($newsletterText)
{
$this->newsletterText = $newsletterText;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
$subject = "Newsletter ShoppingPortal";
return $this->view('emails.newsLetter')->subject($subject);
}
}
And that's the mail view:
Newsletter vom Shoppingportal: <br/><br/>
<div>
{!!$newsletterText!!}
</div>
<br/>
To unsubscribe from Newsletter, use this link: localhost/unsubscribe/....
And here's the thing missing, I need to create a link to unsubscribe, therefore I would need the sellerID in the view (what would be possible, could just pass it in like the newsletterText, but HOW can I pass in or get the current mail adress the email is sent to? As this mail goes to some recipents, its different in each instance of the view... How can I do this, so how can I get the mail adress in the view?
I got it,
just changed the sending of the mail to this:
foreach($newsletterMailAdresses as $mailAdress){
Mail::to($mailAdress)->send(new NewsletterMail($newsletterText, $mailAdress, $sellerID));
}
Now I have the current mail adress while sending.

Best Practice: Where to Add Email Error Logging in a Laravel 5 App

I have the following code in my AppServiceprovider::boot method, whcih ensures I get an email whenever anything is logged with a warning or greater severity level.
$message = \Swift_Message::newInstance( 'An Error has Occurred in XXX' )
->setTo( env('ERROR_EMAIL_TO') )
->setFrom( env('ERROR_EMAIL_FROM') )
->setReplyTo( env('ERROR_EMAIL_REPLY_TO') )
->setContentType( 'text/html' );
$swiftMailer = \Mail::getSwiftMailer();
$handler = new SwiftMailerHandler( $swiftMailer, $message, Logger::WARNING );
$handler->setFormatter( new HtmlFormatter() );
\Log::getMonolog()->pushHandler( $handler );
But while this works, I can't help but feel that it's in the wrong place.
Where would you add this code to a Laravel web app?
How about using middleware? I've written some middleware to log all requests, and responses in the past for APIs that could just as easily dispatch e-mails to inform a user of errors (this was pretty much the use case of why I set it up).
Using the terminate() method in your middleware class, will allow you to perform logic after a response has been sent to the user - so your e-mails shouldn't slow down the experience for the end user.
namespace App\Http\Middleware;
use Closure;
class LogRequestAndResponseMiddleware
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
return $next($request);
}
public function terminate($request, $response)
{
// Send out an e-mail to you here
}
I think this could be also allow you to a good time to refactor the code, which will help move the logic outside of the middleware and into its own area of responsibility.
In this instance, I'm thinking that I currently want to be informed via e-mail, but I may at some point in the future want to send an event via a Websocket instead.
Therefore, I'd wrap up the logic using a contract and implement it accordingly:
interface ErrorNotificationContract
{
public function inform($user, $message)
}
class EmailErrorNotification implements ErrorNotificationContract
{
protected $mail;
public function __construct(Mail $mail)
{
$this->mail = $mail;
}
public function inform($user, $message)
{
// Your send e-mail logic.
}
}
You can then register this using a service provider. A side effect is that you get the added benefits of:
Dependency injection in the EmailErrorNotification (better testability)
Better decoupled code
Implementations that can be changed very easily - just create a new class that implements the ErrorNotificationContract
In your middleware you could then do:
public function terminate($request, $response)
{
// ...
$errorNotifier->inform('youremail#domain.com', 'something bad happened');
}

Customize Exception Template by Bundle

How to customize exception by bundle?
Example:
I have two bundles: BackendBundle and FrontEndBundle. I want this two bundles to be handled by two different templates when error 404 is thrown.
How can I do that?
I had read http://symfony.com/doc/current/cookbook/controller/error_pages.html but still got no clues.
Like in the cookbook article mentioned, extend the TwigBundle and the Symfony\Bundle\TwigBundle\Controller\ExceptionController:findTemplate. There you can decide (if it's not in debug) which 404 to show.
This example assumes all you backend routes are reachable under /backend. Change it to your needs, or use other things from the request to determine your backend 404s.
namespace Acme\ErrorBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Templating\TemplateReference;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\TwigBundle\Controller\ExceptionController as BaseController;
/**
* ExceptionController.
*/
class ExceptionController extends BaseController
{
/**
* #param Request $request
* #param string $format
* #param integer $code An HTTP response status code
* #param Boolean $debug
*
* #return TemplateReference
*/
protected function findTemplate(Request $request, $format, $code, $debug)
{
// find template for backend 404 errors
if (!$this->debug && 404 == $code && false !== strpos($request->getPathInfo(), '/backend')) {
$template = new TemplateReference('TwigBundle', 'Exception', 'backend404', $format, 'twig');
if ($this->templateExists($template)) {
return $template;
}
}
// the parent method finds the error404.html.twig for the frontend
return parent::findTemplate($request, $format, $code, $debug);
}
}
Also to mention, the ErrorBundle must inherit from the TwigBundle.
You can hook to the KernelEvents::EXCEPTION event and override the response that will be sent to the browser. I wrote a quick gist for you:
https://gist.github.com/bezhermoso/87716a9c72a1d12c5036
However, $event->getRequest()->get('_controller') will return null on 404 errors, obviously. So you have to account for that instance.

Categories