Override setGlobalTo of Laravel Mailer - php

I am having trouble with sending mails from Laravel to SendGrid API in my development environment.
The thing is that I have a global TO set on my mail.php config file so that the all the emails that gets sent, go to that adress.
But the setGlobalTo() function of the Mailer class actually set the same email to the TO, the CC, and the BCC.
SendGrid on his endpoint doesn't accept duplicated emails so it throws an error
It gets fixed if I comment the 2 lines that sets the CC and the BCC like:
protected function setGlobalTo($message){
$message->to($this->to['address'], $this->to['name'], true);
//$message->cc($this->to['address'], $this->to['name'], true);
//$message->bcc($this->to['address'], $this->to['name'], true);
}
But it is a vendor file, so, the question is...
How can I easily override that method or unset the cc and bcc before sending my emails on a development environment?
Thank you!

I solved it listening for the Illuminate\Mail\Events\MessageSending event (which is raised just before actually sending the mail) and unsetting the CC and BCC if there is a globalTo address set
namespace App\Listeners;
use Illuminate\Mail\Events\MessageSending;
use Illuminate\Support\Facades\Config;
class MessageSendingListener {
public function handle(MessageSending $swiftMessage) {
$globalTo = Config::get('mail.to.address');
if (isSet($globalTo)) {
$swiftMessage->message->setBcc([]);
$swiftMessage->message->setCc([]);
}
}
}

Related

Pass variables to custom mail transport in laravel

Laravel provides the possibility to implement your own mail transport in order to send email over APIs that they aren't implemented in the framework itself by extending Illuminate\Mail\Transport\Transport. I have done so for the gmail API as this provides some benefits over sending over SMTP. There is however a scenario which is giving me some difficulties: the API requires you to impersonate a given user to send mail as that user. Currently, I'm using the 'from' address to perform that action.
use Illuminate\Mail\Transport\Transport;
use Google\Client as GoogleClient;
class MailTransport extends Transport
{
public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null)
{
$client = new GoogleClient;
// initialise the client further, code omitted
$client->setSubject(
array_key_exists(0, array_keys($message->getFrom()))
? array_keys($message->getFrom())[0]
: self::DEFAULT_SUBJECT
);
// send the email here using the impersonated $client
}
}
This works fine, however I'm running into an issue when I want to send a mail from a mail alias of that user. In that case, the from address doesn't match the google account to impersonate and sending the mail doesn't work. I'm looking for a way to pass the google account to impersonate to the Mailtransport. As an instance of this class is automatically created by the framework and only Swift_Mime_SimpleMessage can be used, my best solution right now is to add a custom header to the email which is read and then removed by the MailTransport class. This feels like a dirty workaround however, does anybody know a cleaner solution?

How to also send BCC to specific address during development with Symfony Mailer?

Symfony provides a way to send all emails to a specific email address during debugging and development but addressees in the BCC still receive the e-mail. This is very dangerous because you don't want to send out any emails from your local dev environment.
Is there a way to also deliver BCCs to a specific email address?
I wouldn't discount having your own wrapper service for Mailer. I have to admit I usually do that, since more often than not I consider the sending of emails too close to the application concerns, and I may want more freedom and flexibility than simply coupling myself to the frameworks package, as good as it may be.
That being said, Symfony's method of changing the recipients does not work with Bcc, because Bcc is part of the message, while the listener that changes the recipients manipulates the envelope.
You could create your own EventListener to manipulate the bcc header:
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Mailer\Event\MessageEvent;
use Symfony\Component\Mime\Message;
class ManipulateBcc implements EventSubscriberInterface
{
private bool $removeExisting;
private array $forcedBcc;
public function __construct(bool $removeExisting = false, array $forcedBcc = [])
{
$this->removeExisting = $removeExisting;
$this->forcedBcc = $forcedBcc;
}
public static function getSubscribedEvents(): array
{
return [
MessageEvent::class => ['onMessage', -224],
];
}
public function onMessage(MessageEvent $event): void
{
if ( ! $this->removeExisting) {
return;
}
$message = $event->getMessage();
if ( ! $message instanceof Message) {
return;
}
$headers = $message->getHeaders();
if ($headers->has('bcc')) {
$headers->remove('bcc');
}
if ( ! empty($this->forcedBcc)) {
$headers->addMailboxListHeader('bcc', $this->forcedBcc);
}
}
}
By default, this does nothing. With the default configuration the eventlistener will be run but since removeExisting will be false, the listener will return without doing anything.
To enable it, you could add the following to services_dev.yaml, so it's only enabled during development:
# config/services_dev.yaml
services:
App\EventDispatcher\ManipulateBcc:
autoconfigure: true
arguments:
$removeExisting: true
$forcedBcc:
- 'fake.email#mailinator.com'
- 'even.faker#mailinator.com'
This was hastily written, and you cannot force BCCs without removing BCCs, which may be sufficient for many purposes but maybe not to your own. Use this as a starting point until it does what you need.
The only way which I think of right now is to create your own wrapper service for the mailer and check the environment if it’s dev just remove the BCC... you don’t need them anyway.

Sending bulk emails using different credentials

I need to send hundreds of emails using different credentials from laravel.
Each customer of mine has his/hers mail list and needs to provide their own SMTP server. I process that list and send emails on customer's behalf.
This is what I have so far. It is working, but it is very slow and I don't have many emails so far. I see a problem when I get more emails.
Any suggestions on how to improve?
PS- I use cron Console Command and use Kernel to schedule the job.
public function sendMailings($allMailings) {
foreach ($allMailings as $email) {
Config::set('mail.host', $email['smtpServer']);
Config::set('mail.port', $email['smtpPort']);
Config::set('mail.username', $email['smtpUser']);
Config::set('mail.password', $email['smtpPassword']);
Config::set('mail.encryption', $email['smtpProtocol']);
Config::set('mail.frommmail', trim($email['fromEmail']));
Config::set('mail.fromuser', trim($email['fromUser']));
Config::set('mail.subject', trim($email['subject']));
Config::set('mail.toEmail', trim($email['toEmail']));
Config::set('mail.toName', trim($email['toName']));
Config::set('mail.pretend', false);
$email_body = $email['emailBody'];
Mail::send('emails.availability, compact('email_body')
, function($message) {
$message->from(config('mail.username'), config('mail.fromUser'));
$message->replyTo(config('mail.frommmail'), config('mail.fromUser'));
$message->to(config('mail.toEmail'), config('mail.toName'))->subject(config('mail.subject'));
});
Log::info('Mail was sent');
}
}
You can not change email provider configs on-the-fly, so you must make new instance of mailer in service container. I did it before, i wrote a method in my own class to get new mailer instance:
/**
* #return Mailer
*/
protected function getMailer()
{
// Changing mailer configuration
config(['mail.driver' => static::getName()]);
// Register new instance of mailer on-the-fly
(new MailServiceProvider($this->container))->register();
// Get mailer instance from service container
return $this->container->make('mailer');
}
Sending e-mail messages directly in web app can drastically slow down the responsiveness of your application. You should always queue your messages.
Instead of Mail::send You can use Mail::queue
and then from cron or manually call
php artisan queue:work
That will process the next item on the queue. This command will do nothing if the queue is empty. But if there’s an item on the queue it will fetch the item and attempt to execute it.

Laravel pretend is not showing cc mail address

I had change pretend as true in mail config. please check http://laravel.com/docs/4.2/mail#mail-and-local-development
Now log is showing to address like this.
[2014-11-22 17:12:49] production.INFO: Pretending to mail message to: dinukathilanga#gmail.com, bbelekkaya#gmail.com [] []
But i need debug cc emails also. How can i do it?
This is my code.
Mail::send($view, $data, function($message) use ($to, $cc, $subject)
{
foreach ($to as $toUser) {
$message->to($toUser['email'], $toUser['first_name'] . ' ' . $toUser['last_name']);
}
foreach ($cc as $ccUser) {
$message->cc($ccUser['email'], $ccUser['first_name'] . ' ' . $ccUser['last_name']);
}
$message->subject($subject);
});
Allow me to be frank here. I am new to Laravel, but I will try my best to explain this.
I do know how to debug email platforms though, the easiest process is by doing it through C.
I will try to be succint as possible.
Firstly, try using the laravel-debugbar (the latest version is 4).
This is an application that is capable of filtering the PHP Debug Bar.
By using this application you will be able to attach and edit output functions (here is the link for more information: Debug bar.
If this won't work, try debugging through C.
Fristly, you will compile the C program by inserting the debug option, option -g.
Afterwards, you'll launch the gdb into the platform.
Thirdly, you'll break the point in the C program, specifically, insert the break line_number function.
After that, you'll print the variable values in the gdp debugger.
For instance, you'll type commands such as print j and (gdp) p i (I will post the website where I've got this information from; it will give you a more broader walkthrough).
There are various operation for this process.
I strongly advise you visiting:Debug C program using gdb. Hope this helped.
Create a new class named ExtendedMailer for the sake of this example and save the file somewhere the autoloader is able to find it. Depending on where you put the file, you may need to run composer dump-autoload once you've saved it.
<?php
use Illuminate\Mail\Mailer;
class ExtendedMailer extends Mailer
{
protected function logMessage($message)
{
parent::logMessage($message);
$emails = implode(', ', array_keys((array) $message->getCc()));
$this->logger->info("Pretending to mail message to: {$emails}");
}
}
Create a new service provider, somewhere your application is able to load classes. As above, you may need to run composer dump-autoload
The below code simply extends the original MailServiceProvider but allows us to bind a different class to in the IoC, you'll notice the new ExtendedMailer; the class we created earlier. Obviously if you namespaced the class, reflect that change here.
<?php
use Illuminate\Mail\MailServiceProvider;
class ExtendedMailServiceProvider extends MailServiceProvider
{
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
$me = $this;
$this->app->bindShared('mailer', function($app) use ($me)
{
$me->registerSwiftMailer();
// Once we have create the mailer instance, we will set a container instance
// on the mailer. This allows us to resolve mailer classes via containers
// for maximum testability on said classes instead of passing Closures.
$mailer = new ExtendedMailer(
$app['view'], $app['swift.mailer'], $app['events']
);
$this->setMailerDependencies($mailer, $app);
// If a "from" address is set, we will set it on the mailer so that all mail
// messages sent by the applications will utilize the same "from" address
// on each one, which makes the developer's life a lot more convenient.
$from = $app['config']['mail.from'];
if (is_array($from) && isset($from['address']))
{
$mailer->alwaysFrom($from['address'], $from['name']);
}
// Here we will determine if the mailer should be in "pretend" mode for this
// environment, which will simply write out e-mail to the logs instead of
// sending it over the web, which is useful for local dev environments.
$pretend = $app['config']->get('mail.pretend', false);
$mailer->pretend($pretend);
return $mailer;
});
}
}
In your config/app.php, you'll find a line which looks like
'Illuminate\Mail\MailServiceProvider',
You'll need to comment it out and add a line as below
'ExtendedMailServiceProvider',
What this does is replace the mailer which Laravel knows about with the one you've just created. The one you've just created is the same as the default one as it merely extends it, and adds functionality to the logMessage function.

Disable Laravel email during db seed

I use Mandrill mail driver for tests. I have a remote staging, that I seed after deploy. And during seeding I try to disable email sends, that are linked to certain events.
Placing this in seeder:
Config::set('mail.driver', 'log');
Config::set('mail.pretend', true);
Has no effect. I don't understand why. I place this in root DatabaseSeeder#run or/and in child seeders — the same. Calls to Mandrill are still performed.
Is there a solution for this problem?
The reason your
Config::set('mail.driver', 'log');
Config::set('mail.pretend', true);
aren't working is because the mail object doesn't check these values before sending mail. Whaaaaaaaa?. If you take a look at the sendSwiftMessage method in the mailer class
#File: vendor/laravel/framework/src/Illuminate/Mail/Mailer.php
protected function sendSwiftMessage($message)
{
if ($this->events)
{
$this->events->fire('mailer.sending', array($message));
}
if ( ! $this->pretending)
{
$this->swift->send($message, $this->failedRecipients);
}
elseif (isset($this->logger))
{
$this->logMessage($message);
}
}
You can see the class checks $this->pretending, and not the configuration, before deciding if it should send the mail or not. So what sets pretending? That's in the MailServiceProvider class's register method.
public function register()
{
//...
$pretend = $app['config']->get('mail.pretend', false);
$mailer->pretend($pretend);
//...
}
When Laravel boots up and registers each service provider, it eventually registers the mail service provider and that's when it reads the configuration, and then tells the mailer if it should "pretend" or not. By the time you're calling this in your seeder, the mailer's already loaded it's configuration value.
Fortunately, there's a pretty easy solution. The mailer object is a singleton/shared service, and has public methods available to control if it should pretend or not. Just call the pretend method yourself instead of setting configuration values
Mail::pretend(true); //using the `Mail` facade to access the mailer object.
you should be able to turn the mailer off programatically.
This is an answer for Laravel 5.7, because pretend doesn't exists:
If you want to disable mail while seeding the database, you could simply 'abuse'
Mail::fake()
I think in two possibilities, you can try:
You can set the command to enable the mail pretend on-the-fly:
Mail::pretend();
The db seed are running with more than one request:
As is write here:
Configuration values that are set at run-time are only set for the
current request, and will not be carried over to subsequent requests.
So you can try set this config over requests, like a session, than finish in the end of the seeding.

Categories