Laravel Mail Queue: change transport on fly - php

I'm trying to use different SMTP configuration for each user of my application. So, using Swift_SmtpTransport set a new transport instance, assign it to Swift_Mailer and then assign it to Laravel Mailer.
Below the full snippet:
$transport = Swift_SmtpTransport::newInstance($mailConfig['smtp_host'], $mailConfig['smtp_port'], 'ssl');
$transport->setUsername($mailConfig['smtp_user']);
$transport->setPassword($mailConfig['smtp_pass']);
$smtp = new Swift_Mailer($transport);
Mail::setSwiftMailer($smtp);
Mail::queue(....);
Messages are added to the queue but never dispatched. I guess that since the "real" send is asyncronous it uses default SMTP configuration, and not the transport set before Mail::queue().
So, the question is: how to change mail transport when using Mail::queue()?

Instead of using Mail::queue, try creating a queue job class that handles sending the email. That way the transport switching code will be executed when the job is processed.
The Job Class Structure Documentation actually uses a mailing scenario as an example, which receives a Mailer instance that you can manipulate. Just use your code in the class's handle method:
public function handle(Mailer $mailer)
{
$transport = Swift_SmtpTransport::newInstance($mailConfig['smtp_host'], $mailConfig['smtp_port'], 'ssl');
$transport->setUsername($mailConfig['smtp_user']);
$transport->setPassword($mailConfig['smtp_pass']);
$smtp = new Swift_Mailer($transport);
$mailer->setSwiftMailer($smtp);
$mailer->send('viewname', ['data'], function ($m) {
//
});
}

Best way from notifications since Laravel 7 : https://laravel.com/docs/9.x/notifications#customizing-the-mailer
public function toMail($notifiable)
{
return (new MailMessage)
->mailer('postmark')
->line('...');
}

I am working on a test where I need 300 different mailers (to fill Mailtrap Enterprise) so having 300 different configs was not good for me.
I am posting the simple solution I found looking at Illuminate\Mail\Mailer class
$transport = Transport::fromDsn('smtp://user:pass#host:port');
Mail::setSymfonyTransport($transport);
Mail::to('to#email.com')
->send((new LaravelMail())
->subject('Subject')
);
Having this you can do some string manipulation to switch between transport configurations

Related

Use the symfony/mailer component without the symfony framework

I am working on a project that doesn't use any framework and I would like to use Symfony Mailer component to handle sending emails.
The installation part (composer require) was well handled and everything is included in my code without any error. However, I still have a problem : the documentation of the component seems to be written only for using it with the symfony framework.
Indeed, it is refering to autoloaded config files that o
bviously don't exist in my app.
This implementation seems to be very tricky and I was wondering if any of you guys already faced the same problem and what solution you came up with?
Your question made me wonder too how it is easy to send mail only with the mailer component.
So I created a new project from scratch and tried the simplest possible version following the mailer component documentation.
use Symfony\Component\Mailer\Mailer;
use Symfony\Component\Mailer\Transport;
use Symfony\Component\Mime\Email;
class MyMailer
{
// googleDns format is gmail+smtp://USERNAME:PASSWORD#default
public function __construct(private string $googleDsn)
{
}
public function send()
{
$template = file_get_contents('https://raw.githubusercontent.com/leemunroe/responsive-html-email-template/master/email.html');
$transport = Transport::fromDsn($this->googleDsn);
$mailer = new Mailer($transport);
$email = (new Email())
->from('mygmail#address.com')
->to('thedelivery#gmail.com')
->subject('Time for Symfony Mailer!')
->html($template);
$mailer->send($email);
}
}
And I successfully received my mail. I send my mail with gmail, for your information. Transport class should do the sending job for you, but if not you can have a look to inside vendor/symfony/mailer/Transport folder

Laravel - changing email settings on the fly is not working

All of my email settings for my app are stored in the database. The user has the option to change those settings, and it all works great. But I am trying to setup a "Send Test Email" function to allow users to test their settings before saving them. When they submit the form for to send the test email, the email is sent via the original settings rather than the new settings.
The form is submitted to SettingsController.php
// Send a test email
public function sendTestEmail(Request $request)
{
Log::info(config('mail.host'));
// Just to check the current email host - shows the proper host
// from the database - i.e. smtp.mailtrap.io
// Make sure that all of the information properly validates
$request->validate([
'host' => 'required',
'port' => 'required|numeric',
'encryption' => 'required',
'username' => 'required'
]);
// Temporarily set the email settings
config([
'mail.host' => $request->host,
'mail.port' => $request->port,
'mail.encryption' => $request->encryption,
'mail.username' => $request->username,
]);
// Only update the password if it has been changed
if(!empty($request->password))
{
config(['mail.password' => $request->password]);
}
// Try and send the test email
try
{
Log::info(config('mail.host'));
// Just to check the new setting - this also shows the correct
// email host - which is the newly assigned one via the form
// i.e. smtp.google.com
Mail::to(Auth::user()->email)->send(new TestEmail());
return response()->json([
'success' => true,
'sentTo' => Auth::user()->email
]);
}
catch(Exception $e)
{
Log::notice('Test Email Failed. Message: '.$e);
$msg = '['.$e->getCode().'] "'.$e->getMessage().'" on line '.
$e->getTrace()[0]['line'].' of file '.$e->getTrace()[0]['file'];
return response()->json(['message' => $msg]);
}
}
In my TestEmail class, I have brought it down to the basics
namespace App\Mail;
//use Illuminate\Bus\Queueable; // Commented out to be sure it is not queuing
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
//use Illuminate\Contracts\Queue\ShouldQueue; // Commented out to be sure it is not queuing
class TestEmail extends Mailable
{
// use Queueable, SerializesModels; // Commented out to be sure it is not queuing
/**
* Create a new message instance.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
return $this->subject('Test Email From '.config('app.name'))->markdown('email.testEmail');
}
}
Even though the logs are showing the updated smtp host for the config setting, the message is still being sent out via the original setting - i.e. smtp.mailtrap.io.
TL;DR
An immediate answer to your question, please refer to this following code tested on Laravel 5.8:
$transport = app('swift.transport');
$smtp = $transport->driver('smpt');
$smpt->setHost('PUT_YOUR_HOST_HERE');
$smpt->setPort('THE_PORT_HERE');
$smpt->setUsername('YOUR_USERNAME_HERE');
$smpt->setPassword('YOUR_PASSWORD_HERE');
$smpt->setEncryption('YOUR_ENCRYPTION_HERE');
Why setting up config on the fly does not work?
In laravel architecture, it is first had all the service providers registered and that includes the MailServiceProvider. See your config/app.php
// inside config/app.php
...
Illuminate\Mail\MailServiceProvider::class,
...
And the config will be loaded before it reaches your route, see Illuminate\Mail\TransportManager
/**
* Create an instance of the SMTP Swift Transport driver.
*
* #return \Swift_SmtpTransport
*/
protected function createSmtpDriver()
{
$config = $this->app->make('config')->get('mail');
// The Swift SMTP transport instance will allow us to use any SMTP backend
// for delivering mail such as Sendgrid, Amazon SES, or a custom server
// a developer has available. We will just pass this configured host.
$transport = new SmtpTransport($config['host'], $config['port']);
if (isset($config['encryption'])) {
$transport->setEncryption($config['encryption']);
}
//... rest of the code
}
so the way I deal with this using TransportManager's drivers method to pick up desired driver and set the config I want, since above code we can see lots of usage of its api.
Hope this helps
I am facing the same issue I just use
config(['mail.driver' => 'smtp']);
when I Debug
dd(config('mail.driver'));
Configuration is all fine
I just review a comment here by kfirba
I'm pretty sure that the issue is caused because when laravel registers the mailer key in the IoC Container it uses the original config. Changing that won't cause laravel to re-define the mailer key.
If you take a look at MailerServiceProvider you will see that it is deferred which means that once you call it, it will instantiate the object and it uses a singleton. I believe that you've used the Mail::send() method elsewhere in the application which led to registering the mailer key in the application. Since it's a singleton it won't read your configuration files again when you re-use it.
Solution:
config(['mail.driver' => 'smtp']);
(new Illuminate\Mail\MailServiceProvider(app()))->register();
Works on Laravel 8
You can set/change any configuration on the fly using Config::set:
Config::set('key', 'value');
So, to set/change the port in mail.php you may try this:
Config::set('mail.port', 587); // default
Note: Configuration values that are set at run-time are only set for the current request, and will not be carried over to subsequent requests
When your app instance sends its first email the Laravel MailManager resolves a new mailer, using the current config values, and caches it by adding it to an internal mailers array. On each subsequent mail that you send, the same cached mailer will be used.
The MailManager has a method to clear its mailer cache:
Mail::forgetMailers();
Now when you send another mail the MailManager must resolve a new mailer, and it will re-read your config while doing so.

Using mandrill as a mail transport layer in Zend 1

Currently, I am trying to integrate Mandrill functionality into a Zend 1 legacy application, and I'm not too familiar with the way they handle mail transports. Currently, I'm including the mandrill/mandrill composer package and attempting to follow the pattern of the other transport layers included in Zend 1:
class MandrillTransport extends Zend_Mail_Transport_Abstract
{
private $message = array();
protected function _formatMandrillArray()
{
//grab all the relavant data from the pre-existing mail object and put it into a Mandrill-friendly array
}
protected function _getAttachments() {
//loop through and format attachments that get added to array
}
protected function _sendMail()
{
//send Mandrill message with included data
$mandrill = new Mandrill(Zend::registry('config')->mandrill->APIKey);
$mandrill->messages->send($this->message);
}
public function send() {
//Format all the stuff and send it
}
}
And then I'm assuming I can use this in the "send()" function, which appears to take the transport as a parameter:
//TODO: Put this in a better spot
require_once __DIR__ . "/ZendFramework/Zend/Mail/Transport/MandrillTransport.php";
$tr = new MandrillTransport();
$email->send($tr);
Am I on the right track here? The reason I'd like to do this as a transport layer rather than just ripping out the existing code and replacing it with Mandrill is because there are a lot of different business processes sending mail, and we'd like to be able to pick and choose which is using the Sendmail transport and which are using Mandrill.
Thanks in advance,
-Chris

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.

use PHP Swiftmailer in Symfony 1.4's Cron Jobs?

I am using Symfony 1.4 with Propel as ORM. i have configured web server schedulers to trigger a mailing function at every 1Hour. To send mails i am using PHP Swift mailer class, and not the symfony's inbuild Swiftmailer(default in 1.3,1.4). But while using it is giving me error..
as "Catchable fatal error: Argument 1 passed to Swift_Transport_EsmtpTransport::__construct() must implement interface Swift_Transport_IoBuffer, none given in /home/msconslt/sfprojects/test/lib/mailClasses/classes/Swift/Transport/EsmtpTransport.php on line 64".
The code that i am using...
require_once(dirname(__FILE__).'/../../config/ProjectConfiguration.class.php');
$configuration =ProjectConfiguration::getApplicationConfiguration('frontend', 'dev', true);
// Remove the following lines if you don't use the database layer
$databaseManager = new sfDatabaseManager($configuration);
//Create the Transport
$transport = Swift_SmtpTransport::newInstance('smtp.gmail.com', 465, "ssl")
->setUsername('myid#gmail.com')
->setPassword('mypassword')
;
//Create the Mailer using your created Transport
$mailer = Swift_Mailer::newInstance($transport);
//Create a message
$message = Swift_Message::newInstance("Test Mail")
->setFrom(array('myid#gmail.com' => 'Harry'))
->setTo(array('someid#gmail.com'))
->setBody('<html>'.
'<head></head>'.
'<body> <span>Dear, </span><br/> Hi there..</body>'.
'</html>',
'text/html'
);
$mailer->send($message);
is there any other way to send mail through Cron jobs??
Yes. See the relevant part of the 1.4 book: Sending an email from a task.
This problem is because you need add this reference in your taks :
requiere_once dirname(__FILE__) . '/../vendor/symfony/lib/vendor/swiftmailer/swift_required.php

Categories