Sending emails in non-production environments is a common mistake. This is embarassing!
I've made this mistake before, and so I've developed a list of ways to ensure it doesn't happen again!
Here's my bag-of-tricks for making sure I don't send accidental emails, brought to you by painful experience.
Mail Fakes
Using mail fakes is the primary means of ensuring you don't send real emails when running a Laravel app's test suite.
In this case, you can add Mail::fake()
to each test that might send an email. This is ideal because no mail will be sent out, and you don't need to care about switching mail drivers, etc.
Here's an example Feature test tests/Feature/SomeControllerTest.php
<?php
namespace Tests/Feature;
use App\Mail\SomeEmailWeSend;
use Illuminate\Support\Facades\Mail;
use Tests\TestCase;
class SomeControllerTest extends TestCase
{
public function test_some_action_was_done()
{
// Ensure we don't send emails
Mail::fake();
// Do some work
// Some example assertions you might
// want to make in your tests
Mail::assertNothingSent(); // assertNothingQueued
Mail::assertSent(SomeEmailWeSend::class); // assertQueued
Mail::assertNotSent(SomeEmailWeSend::class); // assertNotQueued
}
}
This is great - we can both stop emails from sending, and test that the application sent (or queued up) some emails to send (or not)!
The downside of this is that if you miss running Mail::fake()
, you could accidentally send an email! So, I use Mail fakes in conjunction with some of the other options here.
Setting Global Addresses
Laravel allows you to set the application to always send to a specific address. You may want to set this for any non-production environment.
This is done via Mail::alwaysTo()
. I always add the following to my app/Providers/AppServiceProvider.php
class:
use Illuminate\Support\Facades\Mail;
// later, within the AppServiceProvider class...
public function boot()
{
// If we're not production or staging, use a fake address
if (! $this->app->environment('production', 'staging')) {
Mail::alwaysTo('testing@example.org');
}
}
It's generally safe to send test emails to the @example.org
domain. It actually exists for this purpose! That being said, I still wouldn't go sending personal information there.
Some neat facts about this method:
- This prevents CC and BCC addresses from being sent as well.
- There's also a
Mail::alwaysFrom()
method
Staging Environments
Whether or not you set your staging environment to send "real" emails is up to you.
If you do something crazy like copying production data into staging, you may not wish to allow staging to send real emails. Who would do such a thing? Well, this article exists because yours-truly did just that!
Setting a No-Op Driver
There's a mail driver you can use that stops Laravel from sending emails! Instead, it will log emails out to your set log channel (the laravel.log
file by default).
This can be done by setting environment variable MAIL_MAILER
to log
.
MAIL_MAILER=log
You can also force this in your AppServiceProvider
if you'd like:
use Illuminate\Support\Facades\Mail;
// later, within the AppServiceProvider class...
public function boot()
{
// If we're not production or staging, use a fake address
if (! $this->app->environment('production', 'staging')) {
Mail::alwaysTo('testing@example.org');
// Also force the "log" mailer
config()->set('mail.default', 'log')
}
}
Using a Service
Finally, if you need more advanced features (inspecting the emails being sent, storing them, etc), there are some handy services you can use!
Where and how you use these ranges from local apps, to cloud-based services, to apps you can run on servers and within CI pipelines.
- HELO works great for local development - it's a local application you can download
-
Mailtrap is an “actual” smtp service you can use. You just set your mail driver to
smtp
and configure Mailtrap's remote host -
Mailhog can be installed locally (or on servers / within CI pipelines) and be used with the
smtp
mail driver