tomazahlin/symfony-mailer-bundle
A Symfony bundle that integrates Symfony Mailer into your application, giving you configurable transports and mailer services for sending emails through a clean, framework-native setup.
This package wraps Laravel's mailer to expose header and envelope controls that core Mail doesn't surface cleanly. Release 2.2.1 is a patch that adds Return-Path header support, so bounces can be routed to a dedicated mailbox instead of your From address.
composer require <vendor>/<package>:^2.2
php artisan vendor:publish --tag=<package>-config
php artisan config:clear
Look first at, in order:
config/<package>.php — the return-path / envelope options added in 2.2.xsmtp, ses, sendmail)README / CHANGELOG for the 2.2.1 entry, which documents the exact key nameFirst use case — route bounces away from your primary sender:
// config/<package>.php
'return_path' => env('MAIL_RETURN_PATH', '[email protected]'),
[email protected]
Send a test mailable and inspect the raw source at the receiving end: Return-Path: <[email protected]> should be present, and your From should be unchanged. That single check validates the whole feature.
Global default, per-message override. Set the config default once, then override for specific mailables — transactional mail usually wants a different bounce mailbox than marketing mail:
public function build()
{
return $this->subject('Your receipt')
->withSymfonyMessage(function (Email $message) {
$message->getHeaders()->addMailboxHeader(
'Return-Path',
'[email protected]'
);
});
}
VERP / plus-addressing for attribution. Combine the header with a per-recipient address so bounces are traceable without parsing the DSN body:
$returnPath = "bounces+{$user->id}@example.com";
Then a single inbound mailbox (Postmark inbound, Mailgun routes, SES receipt rule) can map the local-part back to a user and suppress them automatically.
Pair it with an envelope sender. The header is what the receiving MTA records; the envelope sender (MAIL FROM) is what SPF/DMARC and most MTAs actually act on. If the package exposes both, set both to the same address, or your header will be silently rewritten. For sendmail transports this is the -f flag — check MAIL_SENDMAIL_COMMAND.
Queued mail. Because the header is applied at message build time, it survives serialization for queued mailables. Set it inside build() or via withSymfonyMessage(), not in the controller, so the queued job produces an identical message.
Testing. Assert the header rather than the rendered body:
Mail::fake();
Mail::to($user)->send(new Receipt($user));
Mail::assertSent(Receipt::class, function ($mail) use ($user) {
return $mail->hasTo($user->email);
});
For a real header assertion, drop Mail::fake() and use the array or log transport, then inspect Mail::getSymfonyTransport()->messages() or the log file.
bounces.example.com) with its own SPF record and MX.MAIL_RETURN_PATH changes won't take effect until php artisan config:clear (and a queue restart: php artisan queue:restart — workers hold the old config).log mailer to see exactly what the package emits before your provider touches it:MAIL_MAILER=log php artisan tinker
Then send a mailable and read storage/logs/laravel.log. Diff that against the headers of a mail received at a real inbox to spot provider rewriting.
^2.2.1 and run your mail tests before deploying, since header ordering/duplication bugs in this area are easy to introduce and hard to notice.Return-Path headers in the raw output. Symfony's addMailboxHeader appends; use setHeader() semantics or make the per-message path the only one.How can I help you explore Laravel packages today?