laravel maintained by mailsenpai
SMTP Senpai for Laravel
Laravel mail transport for SMTP Senpai by MailSenpai, the EU-hosted SMTP relay and transactional email API.
Built on mailsenpai/symfony-mailer, so it works with every Laravel mail feature
(Mailables, notifications, queues, Mail::raw).
Docs: https://en.mailsenpai.com/smtp-api/ · Product: https://en.mailsenpai.com/smtp-senpai/ · All integrations: https://en.mailsenpai.com/integrations/ Source code and issues: https://gitlab.com/smtp-senpai/laravel
Installation
composer require mailsenpai/laravel
Requires PHP 8.2+ and Laravel 12 or 13. The service provider is auto-discovered.
Configuration
.env:
MAIL_MAILER=smtpsenpai
MAILSENPAI_API_KEY=msp_your_key
MAIL_FROM_ADDRESS=orders@yourdomain.com # on a domain verified in your account
MAIL_FROM_NAME="Your shop"
That's it: the package adds a smtpsenpai mailer to config/mail.php if you have not defined one.
To define it yourself (for example to use several keys):
// config/mail.php
'mailers' => [
'smtpsenpai' => [
'transport' => 'smtpsenpai',
// optional: 'key' => env('MAILSENPAI_API_KEY'),
],
],
The key is read from the mailer options, then config/mailsenpai.php (MAILSENPAI_API_KEY), then
config/services.php ('mailsenpai' => ['key' => ...]). Publish the config file with:
php artisan vendor:publish --tag=mailsenpai-config
SMTP instead of the API
The API (HTTPS) is recommended: it also works where outgoing SMTP ports are blocked. To use the relay over SMTP (relay.mailsenpai.com, port 2525, STARTTLS required):
MAILSENPAI_MODE=smtp
MAILSENPAI_SMTP_USERNAME=r1234@yourdomain.com
MAILSENPAI_SMTP_PASSWORD=...
or per mailer: ['transport' => 'smtpsenpai', 'mode' => 'smtp', 'username' => ..., 'password' => ...].
Sending
Mail::to('customer@example.com')->send(new OrderShipped($order));
Supported: HTML and text, attachments, Reply-To, custom X-... headers (other headers are dropped by the API),
Laravel tags and metadata (sent as X-Tag and X-Metadata-<key>). Add an Idempotency-Key header to a message
to make sure it is never sent twice within 24 hours, even if a queued job is retried:
public function headers(): Headers
{
return new Headers(text: ['Idempotency-Key' => 'order-shipped-'.$this->order->id]);
}
Good to know: every recipient (To, Cc and Bcc) receives an individual copy, so Cc addresses are not visible to the other recipients (use SMTP mode if you need a visible Cc). Up to 50 recipients and 10 attachments (10 MB in total) per email. Recipients on your suppression list are skipped and logged as a warning.
Webhooks
Set the webhook in the customer area (or through the API) and put the whsec_... secret in .env:
MAILSENPAI_WEBHOOK_SECRET=whsec_...
Protect your route with the signature middleware:
use MailSenpai\Laravel\Http\Middleware\VerifyWebhookSignature;
Route::post('/webhooks/mailsenpai', function (Request $request) {
$event = $request->json()->all(); // {id, type, created_at, data: {email, reason, message_id, ...}}
if ($event['type'] === 'bounce') {
// ...
}
return response()->noContent();
})->middleware(VerifyWebhookSignature::class);
Put the route in routes/api.php, or exclude it from CSRF protection in bootstrap/app.php:
$middleware->validateCsrfTokens(except: ['webhooks/mailsenpai']);
Invalid or expired signatures (more than 5 minutes) get a 403. MailSenpai\Laravel\WebhookSignature::isValid()
is available if you prefer to check it yourself.
Tests
composer install
vendor/bin/phpunit
License
MIT, © MailSenpai.