# How to create a custom HTTP mail driver for Laravel 7+
In this post we're going to write a mail driver for Laravel to send HTTP POST
requests to an email API.
The example API accepts JSON requests with the following format:
{
"to": [
{
"name": "User name",
"email": "[email protected]"
},
...
],
"cc": [
{
"name": "User name",
"email": "[email protected]"
},
...
],
"bcc": [
{
"name": "User name",
"email": "[email protected]"
},
...
],
"message": "<body>Message</body>",
"subject": "Test subject"
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Create a custom mail transport class
We will use Laravel's MailgunTransport (opens new window) class as a starting point.
Create a custom mail transport class at
app/CustomMailDriver/CustomTransport.php
:
<?php
namespace App\CustomMailDriver;
use GuzzleHttp\ClientInterface;
use Illuminate\Mail\Transport\Transport;
use Swift_Mime_SimpleMessage;
class CustomTransport extends Transport
{
/**
* Guzzle client instance.
*
* @var \GuzzleHttp\ClientInterface
*/
protected $client;
/**
* API key.
*
* @var string
*/
protected $key;
/**
* The API URL to which to POST emails.
*
* @var string
*/
protected $url;
/**
* Create a new Custom transport instance.
*
* @param \GuzzleHttp\ClientInterface $client
* @param string|null $url
* @param string $key
* @return void
*/
public function __construct(ClientInterface $client, $url, $key)
{
$this->key = $key;
$this->client = $client;
$this->url = $url;
}
/**
* {@inheritdoc}
*/
public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null)
{
$this->beforeSendPerformed($message);
$payload = $this->getPayload($message);
$this->client->request('POST', $this->url, $payload);
$this->sendPerformed($message);
return $this->numberOfRecipients($message);
}
/**
* Get the HTTP payload for sending the message.
*
* @param \Swift_Mime_SimpleMessage $message
* @return array
*/
protected function getPayload(Swift_Mime_SimpleMessage $message)
{
// Change this to the format your API accepts
return [
'headers' => [
'Authorization' => 'Bearer ' . $this->key,
'Accept' => 'application/json',
],
'json' => [
'to' => $this->mapContactsToNameEmail($message->getTo()),
'cc' => $this->mapContactsToNameEmail($message->getCc()),
'bcc' => $this->mapContactsToNameEmail($message->getBcc()),
'message' => $message->getBody(),
'subject' => $message->getSubject(),
],
];
}
protected function mapContactsToNameEmail($contacts)
{
$formatted = [];
if (empty($contacts)) {
return [];
}
foreach ($contacts as $address => $display) {
$formatted[] = [
'name' => $display,
'email' => $address,
];
}
return $formatted;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
Modify the getPayload
function to send whatever payload you want to the API. Refer to
Guzzle requests (opens new window) to learn how to format Guzzle requests.
# Extend Laravel's MailManager
Extend Laravel's MailManager
class (opens new window)
to create a method for your custom transport class.
We're going to create a createCustomTransport
method similar to the
createMailgunTransport
method (opens new window). It will be used by the createTransport
method (opens new window)
to create an instance of our CustomTransport
class.
In app/CustomMailDriver/CustomMailManager.php
:
<?php
namespace App\CustomMailDriver;
use Illuminate\Mail\MailManager;
use App\CustomMailDriver\CustomTransport;
class CustomMailManager extends MailManager
{
protected function createCustomTransport()
{
$config = $this->app['config']->get('services.custom_mail', []);
return new CustomTransport(
$this->guzzle($config), $config['key'], $config['url']
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Service configuration
Add a new section to config/services.php
for the API's URL and authorization key:
'custom_mail' => [
'url' => env('CUSTOM_MAIL_URL'),
'key' => env('CUSTOM_MAIL_API_KEY')
],
2
3
4
# Replace Laravel's MailManager with our CustomMailManager
We will create a service provider to register our CustomMailManager
instead
of Laravel's MailManager
in the service container.
Create a new service provider:
php artisan make:provider CustomMailServiceProvider
In app/Providers/CustomMailServiceProvider.php
:
<?php
namespace App\Providers;
use Illuminate\Mail\MailServiceProvider;
use App\CustomMailDriver\CustomMailManager;
class CustomMailServiceProvider extends MailServiceProvider
{
/**
* Register the Illuminate mailer instance.
*
* @return void
*/
protected function registerIlluminateMailer()
{
$this->app->singleton('mail.manager', function($app) {
return new CustomMailManager($app);
});
// Copied from Illuminate\Mail\MailServiceProvider
$this->app->bind('mailer', function ($app) {
return $app->make('mail.manager')->mailer();
});
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# Register the service provider
Finally, we need to register our new service provider in our app's config:
In config/app.php
:
'providers' => [
...
// Illuminate\Mail\MailServiceProvider::class,
App\Providers\CustomMailServiceProvider::class,
],
2
3
4
5
Make sure to copy out Laravel's MailServiceProvider
.
# Mailer Config
In config/mail.php
, under mailers
, you need to add a new entry:
'custom' => [
'transport' => 'custom',
],
2
3
The value of transport
must be the lowercase value the word between create
and Transport
in your new method in CustomMailManager
. custom
in this
case because the method was createCustomTransport
.
# Try it out
To try it out, we can create a Postman Mock API (opens new window).
Replace the GET
request to postman-echo.com/get
with a POST
request to
postman-echo.com/post
.
Clicking on the mock in Postman will take you to a page where you can view the requests take have been sent to it.
Now we need to update our .env
file:
MAIL_MAILER=custom
CUSTOM_MAIL_URL="https://bbe3d6a4-90e5-480d-aece-0090afdf4cea.mock.pstmn.io"
CUSTOM_MAIL_API_KEY="secret-key"
2
3
Now you can send an email from your application and view the requests that have been received:
# Tests
We can create a Guzzle mock handler (opens new window) to test our new mail driver with.
Here is an example test:
<?php
namespace Tests\Unit;
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\Psr7\Response;
use App\CustomMailDriver\CustomTransport;
use Swift_Message;
use Tests\TestCase;
use Illuminate\Support\Facades\Log;
class CustomMailDriverTest extends TestCase
{
protected function createEmail()
{
$message = new Swift_Message('Test subject', '<body>Message body</body>');
$message->setTo('[email protected]');
$message->setCc('[email protected]');
$message->setBcc('[email protected]');
$message->setFrom('[email protected]');
return $message;
}
protected function mockGuzzleClient()
{
$this->mock_handler = new MockHandler([
new Response(200, []),
]);
$this->client = new Client(['handler' => $this->mock_handler]);
}
public function testSendMail()
{
$this->mockGuzzleClient();
$message = $this->createEmail();
$test_url = 'https://localhost:8000/email';
$transport = new CustomTransport(
$this->client,
$test_url,
'secret-key'
);
$transport->send($message);
$last_request = $this->mock_handler->getLastRequest();
$body = (string) $last_request->getBody();
$data = json_decode($body, true);
$expected = [
'to' => [
[
'name' => null,
'email' => '[email protected]',
]
],
'cc' => [
[
'name' => NULL,
'email' => '[email protected]',
]
],
'bcc' => [
[
'name' => NULL,
'email' => '[email protected]',
]
],
'message' => '<body>Message body</body>',
'subject' => 'Test subject',
];
// Test that the data was sent to the correct URL
$this->assertEquals($test_url, (string)$last_request->getUri());
// Test the correct data was sent to the API
$this->assertEquals($expected, $data);
// Test that the authorization key was sent in the headers
$this->assertEquals('Bearer secret-key', $last_request->getHeaderLine('Authorization'));
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# Older Laravel versions
The mail driver methods have been changed between Laravel 6 and 7. For older versions of Laravel, I'd suggest the following blog posts:
- Custom Mail Driver for Laravel (opens new window)
- How to add custom Mail Driver to Laravel (opens new window)
- Mail Logging in Laravel 5.3: Extending the Mail Driver (opens new window)
# Repository
The code for this post is available on Github (opens new window). Any questions, comments or issues can be posted as issues on the repository.
Newsletter
If you'd like to subscribe to my blog, please enter your details below. You can unsubscribe at any time.