sylius/mailer-bundle
SyliusMailerBundle integrates the Sylius Mailer component with Symfony, providing configurable email sending, templating, and sender management for Sylius eCommerce applications. It simplifies transactional email setup and customization.
Installation
composer require sylius/mailer-bundle:^2.2.1
Add the bundle to config/bundles.php:
return [
// ...
Sylius\Bundle\MailerBundle\SyliusMailerBundle::class => ['all' => true],
];
Version note: v2.2.1 is a drop-in patch over v2.2.0 (no BC breaks) but it fixes the
EmailTwigAdapteragainst Twig 3.29'sTemplateWrapper::unwrap()signature change. If your project resolves Twig^3.29, pinsylius/mailer-bundle:^2.2.1— older patches will fatal when rendering a mail template.
Configuration
Configure your mailer in config/packages/sylius_mailer.yaml:
sylius_mailer:
mailer:
dsn: '%env(MAILER_DSN)%' # e.g., 'smtp://user:[email protected]:port'
transport: 'smtp' # or 'sendmail', 'failover', etc.
templates:
path: '%kernel.project_dir%/templates/mailer'
Sanity-check the wiring before writing any email code
php bin/console debug:container sylius.mailer.sender
php bin/console debug:config sylius_mailer
composer show sylius/mailer-bundle twig/twig
If the container compiles and the Twig version is sane, the adapter is fine.
First Use Case
Create a template file (e.g., welcome.txt.twig in templates/mailer/):
Hello {{ name }},
Welcome to our platform!
Send an email in a controller:
use Sylius\Component\Mailer\Sender\SenderInterface;
public function sendWelcomeEmail(SenderInterface $sender, string $email, string $name): void
{
$sender->send(
new Email(
'Welcome',
$email,
'welcome.txt.twig',
['name' => $name]
)
);
}
Template Management
templates/mailer/ (supports .twig, .html.twig, .txt.twig).{# templates/mailer/order_confirmation.html.twig #}
<h1>Order #{{ order.number }}</h1>
templates/mailer/ directory.Email Sending
$sender->send(new Email('Subject', '[email protected]', 'template.twig', ['var' => 'value']));
# config/packages/messenger.yaml
framework:
messenger:
transports:
async: '%env(MESSENGER_TRANSPORT_DSN)%'
routing:
'Sylius\Component\Mailer\Message\SendEmailMessage': async
Then dispatch:
$bus->dispatch(new SendEmailMessage($email));
Event-Driven Emails
Subscribe to Sylius events (e.g., OrderCreatedEvent) and trigger emails:
use Sylius\Component\Core\Event\OrderCreatedEvent;
public function __invoke(OrderCreatedEvent $event): void
{
$sender->send(new Email(
'Order Confirmation',
$event->getOrder()->getCustomer()->getEmail(),
'order_confirmation.html.twig',
['order' => $event->getOrder()]
));
}
Testing
null transport for tests:
sylius_mailer:
mailer:
transport: 'null://default'
SenderInterface and assert calls on it (more robust than relying on bundle internals).$this->assertNotEmpty($twig->render('@SyliusMailer/.../welcome.html.twig', ['name' => 'x']));
This is exactly the class of bug v2.2.1 fixed, and a render smoke test catches it on the next Twig bump.Keep Twig and the bundle in lockstep: bump them together in CI. A Twig minor can change adapter internals without any deprecation, so treat twig/twig as a coupled dependency.
Custom Mailer Logic:
Extend the Email class or create a decorator for SenderInterface:
class CustomSender implements SenderInterface
{
private $decorated;
public function __construct(SenderInterface $decorated) { $this->decorated = $decorated; }
public function send(Email $email): void
{
// Add custom logic (e.g., logging, analytics)
$this->decorated->send($email);
}
}
Register as a service:
services:
Sylius\Component\Mailer\Sender\SenderInterface: '@app.custom_sender'
A decorator is also the cleanest place to swap in a test double.
Dynamic Templates: Use a service to resolve template paths dynamically:
$templatePath = $this->templateResolver->resolve('mailer', 'dynamic_template.twig', ['locale' => $locale]);
Localization: Combine with Symfony's translation system for multi-language emails:
{# templates/mailer/welcome.html.twig #}
<h1>{{ 'welcome.heading'|trans }}</h1>
Twig 3.29+ and EmailTwigAdapter (fixed in v2.2.1)
TemplateWrapper::unwrap(). Bundles before v2.2.1 call it the old way and blow up at render time — typically a TypeError/ArgumentCountError surfaced the moment a mail template is rendered, not at container compile time.composer require sylius/mailer-bundle:^2.2.1. If your lockfile pins an older patch, composer update sylius/mailer-bundle is enough — it's a patch bump with no API changes.Template Caching:
php bin/console cache:clear
config/packages/twig.yaml for development:
twig:
cache: false
DSN Configuration:
MAILER_DSN is correctly formatted (e.g., smtp://user:pass@host:port).sylius_mailer:
mailer:
dsn: '%env(MAILER_DSN)%'
transport_options:
encryption: ssl
port: 465
Async Transport Quirks:
async transport, ensure the messenger worker is running:
php bin/console messenger:consume async -vv
php bin/console messenger:failed:list
php bin/console messenger:failed:retry <message-id>
Attachment Limits:
if ($attachment->getSize() > 10_000_000) { // 10MB
throw new \RuntimeException('Attachment too large');
}
Log Sent Emails: Add a subscriber to log emails:
public function onEmailSent(EmailSentEvent $event): void
{
$this->logger->info('Email sent', [
'subject' => $event->getEmail()->getSubject(),
'to' => $event->getEmail()->getTo(),
]);
}
Check Transport Errors:
Enable verbose logging in config/packages/monolog.yaml:
monolog:
handlers:
main:
level: debug
Isolate render vs. transport failures: render the template yourself first ($twig->render(...)), then send. Adapter/Twig problems look identical to transport problems in logs otherwise.
Custom Email Events:
Extend the EmailSentEvent or create your own:
class CustomEmailEvent extends EmailSentEvent
{
public function getCustomData(): array { /* ... */ }
}
Template Preprocessors: Add logic before rendering templates via a compiler pass or event listener:
public function onEmailRender(EmailRenderEvent $event): void
{
$event->setTemplateData(array_merge($event->getTemplateData(), ['custom_var' => true]));
}
Transport Factories: Create custom transports (e.g., for AWS SES):
class AWSSesTransportFactory implements TransportFactoryInterface
{
public function createTransport(array $options): TransportInterface
{
return new AWSSesTransport($options);
}
}
Register in config/packages/sylius_mailer.yaml:
sylius_mailer:
mailer:
transport: 'aws_ses'
Bulk Email Optimization: Use batch processing for large email lists:
foreach ($emails as $batch) {
$sender->sendBatch($batch);
}
How can I help you explore Laravel packages today?