Laravel mail driver
Make Mail::send(), notifications and queued mail in Laravel go through SendSend, with one small class and no package to install.
Laravel sends mail through a “transport”. Add a SendSend transport once and everything that already sends mail in your app uses it: Mail::to(), mailables, notifications, password resets and queued mail. For a one-off send from a controller instead, see Send email from Laravel. This guide needs Laravel 9 or newer.
1. Add your key and From address
Create a live API key in the dashboard with the emails:send scope, and use a From address on a domain you have verified. In .env:
MAIL_MAILER=sendsend
SENDSEND_API_KEY=ss_live_...
MAIL_FROM_ADDRESS="hello@yourdomain.com"
MAIL_FROM_NAME="Your App"2. Add the mailer
In config/mail.php, inside 'mailers':
'sendsend' => [
'transport' => 'sendsend',
'key' => env('SENDSEND_API_KEY'),
],3. Add the transport
<?php
namespace AppMail;
use IlluminateSupportFacadesHttp;
use SymfonyComponentMailerExceptionTransportException;
use SymfonyComponentMailerSentMessage;
use SymfonyComponentMailerTransportAbstractTransport;
use SymfonyComponentMimeAddress;
use SymfonyComponentMimeMessageConverter;
class SendSendTransport extends AbstractTransport
{
public function __construct(private string $key)
{
parent::__construct();
}
protected function doSend(SentMessage $message): void
{
$email = MessageConverter::toEmail($message->getOriginalMessage());
$addresses = fn (array $list) => array_values(array_map(fn (Address $a) => $a->getAddress(), $list));
$from = $email->getFrom()[0];
$name = preg_replace('/["<>\r\n]/', '', $from->getName());
$payload = array_filter([
'from' => $name ? "{$name} <{$from->getAddress()}>" : $from->getAddress(),
'to' => $addresses($email->getTo()),
'cc' => $addresses($email->getCc()) ?: null,
'bcc' => $addresses($email->getBcc()) ?: null,
'reply_to' => $addresses($email->getReplyTo()) ?: null,
'subject' => $email->getSubject(),
'html' => $email->getHtmlBody(),
'text' => $email->getTextBody(),
], fn ($value) => $value !== null && $value !== '');
foreach ($email->getAttachments() as $part) {
$headers = $part->getPreparedHeaders();
$payload['attachments'][] = array_filter([
'filename' => $headers->getHeaderParameter('Content-Disposition', 'filename') ?? 'attachment',
'content' => base64_encode($part->getBody()),
'content_type' => $part->getMediaType() . '/' . $part->getMediaSubtype(),
]);
}
$response = Http::withToken($this->key)
->withHeaders(['Idempotency-Key' => sha1($message->getMessageId())]) // a retry can never send twice
->acceptJson()
->post('https://api.sendsend.email/v1/emails', $payload);
if ($response->failed()) {
throw new TransportException('SendSend: ' . ($response->json('error.message') ?? $response->body()));
}
}
public function __toString(): string
{
return 'sendsend';
}
}4. Register it
In app/Providers/AppServiceProvider.php:
use App\Mail\SendSendTransport;
use Illuminate\Support\Facades\Mail;
public function boot(): void
{
Mail::extend('sendsend', fn (array $config) => new SendSendTransport($config['key']));
}5. Try it
Mail::raw('It works.', fn ($m) => $m->to('you@example.com')->subject('Test from Laravel'));Open the message in your dashboard to see it move from queued to delivered. After that, nothing else in your app changes:
Mail::to($user)->send(new WelcomeMail($user));
$user->notify(new InvoicePaid($invoice));
Mail::to($user)->queue(new ReceiptMail($order)); // queued mail is retried safelyGood to know
- Attachments are sent too, up to 10 files and 10 MB in total.
- Each message carries an idempotency key made from its Laravel message id, so a queue retry can never deliver the same email twice.
- For delivery, bounce and complaint events in your app, add a webhook and verify its signature.
- Test your setup safely with a
ss_test_key first. It simulates delivery and never sends.
Troubleshooting
| Message | Cause |
|---|---|
| domain_not_verified | The From address’s domain is not verified in the project that owns the key. |
| account_not_approved | Live sending is waiting for account approval. A test key works meanwhile but never delivers. |
| unauthorized | The key is wrong or revoked. Clear cached config with php artisan config:clear after changing .env. |
| Mailer [sendsend] is not defined | The mailers entry in config/mail.php is missing, or the config is cached. |
| Unsupported mail transport | Mail::extend() was not reached. Check that it is in AppServiceProvider::boot(). |