Laravel Mail / Swift / How to globally configure "sender" address - php

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

Related

Laravel - WebSockets how to trigger even from client to server via websocket connection

I am using Laravel and I have installed https://beyondco.de/docs/laravel-websockets/getting-started/introduction.
So far so good. I have created my first event:
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class NewMessage implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $message;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct($message)
{
$this->message = $message;
}
/**
* Get the channels the event should broadcast on.
*
* #return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new Channel('home');
}
}
When on server I trigger the event like this:
event(new App\Events\NewMessage("Hello World"))
On the client side I use Laravel Echo to listen for the event and print the result in the console like this:
<script>
Echo.channel('home')
.listen('NewMessage', (e)=> {
console.log(e.message);
})
function SendTestMessage(){
console.log("Test");
Echo.channel('home').trigger('NewMessage',{ message: 'TEST Alarm!' });
}
</script>
This works all fine. When I trigger on the server the even I see immediately on the console in the client the message.
However I would like to make a trigger of even from the client to the server WITHOUT rest POST api request. I would like to trigger the even via the websocket connection.
So I tried to make the following function in JS:
function SendTestMessage(){
console.log("Test");
Echo.channel('home').trigger('NewMessage',{ message: 'TEST Alarm!' });
}
However this thing does not trigger an event to the server. Why is that and how can I trigger even via the websocket communication?
You need to enable in config/websockets.php
'enable_client_messages' => true,
refer to this doc https://pusher.com/docs/channels/using_channels/events/#triggering-client-events
then from client side
var triggered = channel.trigger(eventName, data);
Checking the internals of Laravel Websocket with path name specifically vendor\beyondcode\laravel-websockets\src\HttpApi\Controllers\TriggerEventController.php
which is responsible for accepting events, it is not connected to accept websocket request, only http request, so you can't trigger an event using websocket protocol in laravel websocket, if that was your question.
To confirm this look into the vendor\beyondcode\laravel-websockets\src\Server\Router.php
public function echo()
{
$this->get('/app/{appKey}', WebSocketHandler::class);
$this->post('/apps/{appId}/events', TriggerEventController::class);
$this->get('/apps/{appId}/channels', FetchChannelsController::class);
$this->get('/apps/{appId}/channels/{channelName}', FetchChannelController::class);
$this->get('/apps/{appId}/channels/{channelName}/users', FetchUsersController::class);
}
You will notice that aside from /app/{appkey} route which handles all Socket request, all other route container only has the __invoke() method, which means they don't expect socket request, they only expect REST request and when it comes, the request must have valid signature header which can only be provided by the backend service. it will be a security flaw to have this token as part of your frontend codes. So backend stands as the gateway to trigger this event.
Simply send the appropriate RESTful request to your typical laravel backend controller to trigger your desired event internally and it will trigger it. which is what one of the answer is suggesting and which is advisable. I think there is an internal implementation for it, although can only be triggered by logged in users through private channels or presence channel which is advisable too and it involves RESTful process
Here is the valid signatures you will be needing if you were to send a RESTful request to laravel Websocket package directly without going through laravel backend. looking closely, it uses hmac to verify signature. Signature can only be made or verified using the a security app key only known to backend and laravel websocket.
protected function ensureValidSignature(Request $request)
{
/*
* The `auth_signature` & `body_md5` parameters are not included when calculating the `auth_signature` value.
*
* The `appId`, `appKey` & `channelName` parameters are actually route paramaters and are never supplied by the client.
*/
$params = Arr::except($request->query(), ['auth_signature', 'body_md5', 'appId', 'appKey', 'channelName']);
if ($request->getContent() !== '') {
$params['body_md5'] = md5($request->getContent());
}
ksort($params);
$signature = "{$request->getMethod()}\n/{$request->path()}\n".Pusher::array_implode('=', '&', $params);
$authSignature = hash_hmac('sha256', $signature, App::findById($request->get('appId'))->secret);
if ($authSignature !== $request->get('auth_signature')) {
throw new HttpException(401, 'Invalid auth signature provided.');
}
return $this;
}
this can be found in vendor\beyondcode\laravel-websockets\src\HttpApi\Controllers\Controller.php which is the abstract class extended by all non-websocket endpoint.
I hope this helps

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.

Laravel mail is sending email twice when added bcc

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.

Updating config on runtime for a user

I am using Laravel 5.1
I created a function to get smtp info from db for a user $mail_config=STMPDetails::where('user_id',36)->first() and then I can just call config helper function and pass the array to set config value config($mail_config). and then I call Mail::queue function.
but before it reaches createSmtpDriver#vendor/laravel/framework/src/Illuminate/Mail/TransportManager.php where it reads the configuration again to send the mail, mail config are changed to the one specified in .env file.
Another thing to note is Mail sending function is in a Listener
I am not able to figure out where can I call the function such that configuration changes are retained before mail is sent.
Thanks,
K
This should work:
// Set your new config
Config::set('mail.driver', $driver);
Config::set('mail.from', ['address' => $address, 'name' => $name]);
// Re execute the MailServiceProvider that should use your new config
(new Illuminate\Mail\MailServiceProvider(app()))->register();
Since the default MailServiceProvider is a deferred provider, you should be able to change config details before the service is actually created.
Could you show the contents of $mail_config? I'm guessing that's the problem. It should be something like
config(['mail.port' => 587]);
Update - tested in 5.1 app:
Mail::queue('emails.hello', $data, function ($mail) use ($address) {
$mail->to($address);
});
->> Sent normally to recipient.
config(['mail.driver' => 'log']);
Mail::queue('emails.hello', $data, function ($mail) use ($address) {
$mail->to($address);
});
->> Not sent; message logged.

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.

Categories