I am trying to send email with Bcc, but I have noticed that SwiftMailer is sending emails twice (one with Bcc and the other without it), and I removed bcc it's working fine without duplicated mails.
mailController.php
class mailController extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct()
{
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
return $this->from('test#gmail.com', 'test')
->view('portal.confirmation')
->subject('test Email')
->bcc('email#gmail.com','wahdan');
}
}
.env
MAIL_DRIVER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
Update
This issue happen only in local environment ,but in production it's working perfect without any duplicated emails.
If you BCC an email and send it through mailtrap.io, you will receive two copies of the email in your mailbox. If there are two BCCs, you will receive three copies, etc. The emails will look identical (including the "To:").
This behavior is specific to mailtrap.io, not Laravel (i.e., it is not on the sending side).
If the number of duplicate emails is the same as the number of BCCs plus the original, I think you can be confident that is the reason.
This answer assumes that you are using mailtrap.io as your SMTP server locally, but not in production.
Related
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.
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.
Laravel offers the config mail.from to allow specifying a global/default From address. This calls setFrom on the Swift-Message internally and sets the "Header From" that shows in the recipients email client.
However, the message is then also sent with Return-Path/Envelope-From of this value, because of getReversePath, that derives this From value as no other option (Sender/Return-Path) has been set.
The site is running on a multi-project host with Exim4 running locally, so there are no restrictions to set these addresses like if I was using something like Gmail SMTP.
Laravel is configured to use sendmail.
Let's assume the machines hostname is webX.hosts.our-company-intern.com and the project that runs on it belongs to a subdomain of customer-brand.com. E-Mails should be sent from the "main-domain" (without the subdomain part), though, like noreply#customer-brand.com. customer-brand.com. does not resolve to the machine the subdomain project is hosted.
I would like to send the mail with an Envelope address of my actual hostname (better: preserve the default Envelope-From/Return-Path Exim/Sendmail would use), like appname#webX.hosts.our-company-intern.com and only have From: noreply#customer-brand.com.
Reason for that is, I'd like to have a valid Return-Path indicating from which host it actually came from.
SPF is also a reason, but not the main one; we control the customer-brand.com domain and could just add our hosts address, I'd still like to avoid it if possible and use our domain that already have all our hosts in their SPF record.
When I put the following line in the Laravel vendor class-method Mailer::send:
$message->sender('appname#webX.hosts.our-company-intern.com');
This yields my desired result, but of course I cannot just edit it in vendor code.
Where can I configure this properly (maybe via a callback that executes for every message)? Or isn't there any such option and I should write an issue in the Laravel/Mail package?
I also tried putting -f in the sendmail command:
/usr/sbin/sendmail -bs -f"appname#webX.hosts.our-company-intern.com"
- however this already fails at the _establishProcessConnection(). Called in CLI the error is:
exim: incompatible command-line options or arguments
Versions:
Laravel 5.4.36
Swiftmailer 5.4.9
Exim 4.89-2+deb9u2
Config mail.php:
'from' => [
'address' => 'noreply#customer-brand.com',
'name' => 'Customer Brand',
],
Tinker-Shell test code:
Mail::raw('Text to e-mail', function($message) {
$message->to('my-personal-email-for-testing#domain.com');
})
Current mail headers:
Received: from webX.hosts.our-company-intern.com (xxx.xxx.xxx.xxx) by ...
Received: from appname (helo=[127.0.0.1])
by webX.hosts.our-company-intern.com with local-smtp (Exim 4.89)
(envelope-from <noreply#customer-brand.com>) // this should change
...
From: Customer Brand <noreply#customer-brand.com>
Return-Path: noreply#customer-brand.com // this should change
On the top of my head: you could hook in the Illuminate\Mail\Events\MessageSending event and add the sender there.
Given the corporate environment I'll assume you know how to listen to events (if not, let me know). In that case, here's the listener you'll need:
class MessageSendingListener
{
/**
* Handle the event.
*
* #param \Illuminate\Mail\Events\MessageSending $event
* #return void
*/
public function handle(MessageSending $event)
{
// $event->message is of type \Swift_Message
// So you'll need the setSender() method here.
$event->message->setSender('appname#webX.hosts.our-company-intern.com');
}
}
As written by Sajal Soni
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
class DemoEmail extends Mailable
{
use Queueable, SerializesModels;
/**
* The demo object instance.
*
* #var Demo
*/
public $demo;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct($demo)
{
$this->demo = $demo;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
return $this->from('sender#example.com')
->view('mails.demo')
->text('mails.demo_plain')
->with(
[
'testVarOne' => '1',
'testVarTwo' => '2',
])
->attach(public_path('/images').'/demo.jpg', [
'as' => 'demo.jpg',
'mime' => 'image/jpeg',
]);
}
}
I hope this will help you
Mention MAIL_FROM_ADDRESS=dev#example.com in .env file
Then run:
php artisan config:cache
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=xx-xxxxxxxx-xxxx
MAIL_PASSWORD=xx-xxxxxxxx-xxxx
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=dev#example.com
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([]);
}
}
}
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.