Mailer Bundle
The Mailer Bundle adds a small mail-oriented layer on top of Symfony Mailer.
Its current focus is not transport management. It focuses on:
- registering mail templates from different bundles;
- previewing and testing example emails from an admin UI;
- decorating Symfony's body renderer with the translatable renderer from
softspring/mime-translatable; - exposing an optional Doctrine-backed email history model and admin screens.
A good way to think about this bundle is: it is the shared infrastructure around translatable, previewable emails in the Softspring ecosystem.
Installation
Install the package:
composer require softspring/mailer-bundle:^6.0
If your application does not use Symfony Flex, enable it manually:
<?php
return [
// ...
Softspring\MailerBundle\SfsMailerBundle::class => ['all' => true],
];
The bundle expects Symfony Mailer to be configured in the application.
Main Features
The bundle has three main blocks:
- a template registry built from tagged template loaders;
- an admin UI to list templates, preview them, and send test emails;
- an optional
EmailHistoryInterfacemodel with Doctrine mappings and admin pages.
It also decorates twig.mime_body_renderer so translatable email classes can render correctly through the softspring/mime-translatable component.
Configuration
Configure the bundle under sfs_mailer:
# config/packages/sfs_mailer.yaml
sfs_mailer:
entity_manager: default
templates:
my_template:
template: 'emails/my_template.html.twig'
subject_block: subject
html_block: html
text_block: text
from_email:
sender_name: 'Acme'
address: 'noreply@example.com'
example_context:
companyName: 'Acme'
history:
enabled: true
class: App\Entity\EmailHistory
The three top-level blocks are:
entity_managertemplateshistory
Entity Manager
entity_manager selects the Doctrine entity manager used by target entity resolution for EmailHistoryInterface.
Template Configuration
Each entry inside sfs_mailer.templates can define:
templatesubject_blockhtml_blocktext_blockfrom_email.sender_namefrom_email.addressexample_context
However, the current built-in ParameterTemplateLoader does not yet use most of those fields. In the current code, it only creates template entries keyed by the template id and leaves the rest effectively unused.
That means this block should currently be seen as a template registry seed, not as a full declarative email-definition system.
History Configuration
The history block enables the email history model and admin controller wiring.
Available options:
enabledclass
When enabled, the configured class must implement EmailHistoryInterface.
Template Registry
The core service is Softspring\MailerBundle\Template\TemplateLoader.
It does not load templates from one place only. Instead, it gathers all services tagged with sfs_mailer.template_loader, asks each one for a TemplateCollection, and merges them.
This is the key extension point of the bundle.
Template Object
The Template value object currently stores:
iddescriptionclasspreview
Default values matter:
classdefaults toSoftspring\MailerBundle\Mime\TranslatableEmailpreviewdefaults tofalse
The admin UI uses these fields directly, especially id, class, and preview.
Built-In Loader
The bundle ships one built-in loader: ParameterTemplateLoader.
It reads the sfs_mailer.templates container parameter and creates template entries for each configured key.
In the current implementation, this loader:
- sets the template id;
- does not copy
template, block names, sender data, or example context into theTemplateobject; - does not mark templates as previewable;
- leaves the default
classuntouched.
So, by itself, the parameter loader is not enough to build rich, preview-ready admin templates.
Tagged Loaders From Other Bundles
The real intended model is that feature bundles contribute their own loaders.
For example, softspring/user-bundle provides MailTemplateLoader, which registers templates such as:
sfs_user.reset_passwordsfs_user.register_confirmsfs_user.invite
and explicitly sets:
- the email class;
preview = true
That is why the mailer bundle is best understood as a shared registry and admin shell for template providers implemented in other bundles.
Creating Your Own Loader
To expose emails in the admin UI, create a loader service implementing TemplateLoaderInterface and tag it with sfs_mailer.template_loader.
Example:
<?php
namespace App\Mailer\Loader;
use App\Mime\WelcomeEmail;
use Softspring\MailerBundle\Template\Loader\TemplateLoaderInterface;
use Softspring\MailerBundle\Template\Template;
use Softspring\MailerBundle\Template\TemplateCollection;
class MailTemplateLoader implements TemplateLoaderInterface
{
public function load(): TemplateCollection
{
$collection = new TemplateCollection();
$template = new Template();
$template->setId('app.welcome');
$template->setClass(WelcomeEmail::class);
$template->setPreview(true);
$collection->addTemplate($template);
return $collection;
}
}
services:
App\Mailer\Loader\MailTemplateLoader:
tags: ['sfs_mailer.template_loader']
This is the most reliable way to integrate application emails with the current bundle.
Admin Template UI
The bundle ships an admin controller to inspect registered templates.
Import the routes when you want that UI:
_sfs_mailer_templates:
resource: '@SfsMailerBundle/config/routing/mailer_templates.yaml'
prefix: /admin/mailer/templates
The available routes are:
sfs_mailer_templates_searchsfs_mailer_templates_previewsfs_mailer_templates_test
Search Page
MailerTemplateController::search() loads the TemplateCollection and renders all registered templates.
The search page:
- translates
<template id>.namefor the displayed name; - uses the template description if available;
- falls back to
<template id>.descriptiontranslation when no explicit description exists; - only shows preview links when
template.previewistrue.
Preview Page
MailerTemplateController::preview() is built for email classes that implement Softspring\Component\MimeTranslatable\ExampleEmailInterface.
The controller:
- loads the template object by id;
- gets the template email class;
- checks that the class implements
ExampleEmailInterface; - calls
::generateExample($translator, $locale); - renders the email through
TranslatableBodyRenderer; - shows the rendered subject and HTML body.
This makes preview support class-driven, not configuration-driven.
In other words, if you want previews, you need an email class with a static example generator.
Test Send Page
MailerTemplateController::test() lets an authenticated admin send a test email.
The form contains:
localetoEmailtoName
The locale choices come from %kernel.enabled_locales%.
When the form is submitted, the controller:
- builds the example email through
ExampleEmailInterface::generateExample(); - sets the target recipient with
to(new Address(...)); - sends the message with
MailerInterface.
If the Twig template is missing, the controller adds a form error instead of crashing the page.
Requirements For Preview And Test
For a template to fully work in the admin UI, you usually need:
- a custom template loader that sets the email class;
preview = true;- an email class implementing
ExampleEmailInterface; - a generated example that can render without application-specific runtime state.
Without that, the template may appear in the list but not be previewable or testable.
Email Classes And Rendering
The bundle exposes three mail-related classes under Softspring\MailerBundle\Mime:
TranslatableEmailExtendedContextEmailTranslatableBodyRenderer
In the current code, all three are deprecated wrappers around classes from softspring/mime-translatable.
For new code, prefer the component classes directly:
Softspring\Component\MimeTranslatable\TranslatableEmailSoftspring\Component\MimeTranslatable\ExtendedContextEmailSoftspring\Component\MimeTranslatable\TranslatableBodyRenderer
Renderer Decoration
The bundle decorates Symfony's twig.mime_body_renderer service with TranslatableBodyRenderer.
That means emails rendered through Symfony Mailer use the translatable body renderer automatically once the bundle is enabled.
This is the main runtime integration point of the bundle outside the admin UI.
Writing Example Emails
The preview and test screens expect ExampleEmailInterface.
A typical email class looks like this:
<?php
namespace App\Mime;
use Softspring\Component\MimeTranslatable\ExampleEmailInterface;
use Softspring\Component\MimeTranslatable\TranslatableEmail;
use Symfony\Contracts\Translation\TranslatorInterface;
class WelcomeEmail extends TranslatableEmail implements ExampleEmailInterface
{
public static function generateExample(TranslatorInterface $translator, ?string $locale = null): TranslatableEmail
{
return new self($translator, $locale);
}
public function __construct(TranslatorInterface $translator, ?string $locale = null)
{
parent::__construct($translator, $locale);
$this->htmlTemplate('emails/welcome.html.twig');
$this->subject('welcome.subject', 'messages');
$this->setContextParam('name', 'Jane');
}
}
The key point is that generateExample() must return a fully renderable email without needing a database lookup or request state.
Email History
The bundle includes an optional email history model.
This part contains:
EmailHistoryInterfaceModel\EmailHistoryEntity\EmailHistory- Doctrine mappings for the model and entity
- admin routes and templates to browse stored history
Enabling History
Enable it in configuration:
sfs_mailer:
history:
enabled: true
class: App\Entity\EmailHistory
When history is enabled, the bundle:
- validates that the configured class implements
EmailHistoryInterface; - enables the default entity mapping for
Softspring\MailerBundle\Entity\EmailHistory; - resolves
EmailHistoryInterfaceto your configured class; - registers the mail history admin controller.
Implementing The Entity
The bundle ships Softspring\MailerBundle\Model\EmailHistory as a mapped superclass and Softspring\MailerBundle\Entity\EmailHistory as a ready entity.
If you want an application entity, the usual approach is:
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Softspring\MailerBundle\Entity\EmailHistory as BaseEmailHistory;
use Softspring\MailerBundle\Model\EmailHistoryInterface;
#[ORM\Entity]
class EmailHistory extends BaseEmailHistory implements EmailHistoryInterface
{
}
The mapped model stores:
idstatustemplateId- serialized
message createdAtlastStatusAt
What The History Model Exposes
Besides the raw serialized message, the model provides helper accessors such as:
getMessageSubject()getMessageFrom()getMessageTo()getMessageReplyTo()getMessageDate()getMessageBodyHtml()getMessageBodyText()
This is what the admin history templates use to show message details.
History Status Values
EmailHistoryInterface defines four statuses:
STATUS_PENDINGSTATUS_IN_PROGRESSSTATUS_SENTSTATUS_FAILED
These values are intended for your own history-writing logic.
Admin History UI
Import the history routes when you want the backoffice screens:
_sfs_mailer_history:
resource: '@SfsMailerBundle/config/routing/mailer_history.yaml'
prefix: /admin/mailer/history
The available routes are:
sfs_mailer_history_searchsfs_mailer_history_details
Search Screen
MailerHistoryController::search() loads all history rows ordered by createdAt DESC.
The page shows:
- message date
- template id
- recipients
- subject
- senders
- status
Details Screen
MailerHistoryController::details() loads one history record by id and renders the stored HTML body and message metadata.
This UI is read-only. The bundle does not provide history filters, retries, or resend actions.
Important Limitations
These limitations come directly from the current code and are worth knowing before you design around the bundle.
Template Configuration Is Only Partially Implemented
The templates configuration block accepts fields such as template, block names, sender info, and example context, but the built-in ParameterTemplateLoader currently does not transfer those values into the Template object.
So configuration alone does not yet describe a full email template runtime model.
ParameterTemplateLoader Reuses One Template Instance
The current loader creates one Template object outside the loop and reuses it for every configured key.
That means parameter-based template registration should be treated carefully, especially if you expect multiple distinct template objects in the final collection.
History Is A Model And UI, Not A Full Logging Pipeline
The bundle includes history entities, mappings, an event wrapper, and admin screens, but it does not currently register a built-in listener that captures every sent email and persists history automatically.
If you enable history, you still need your own application logic to create and update EmailHistoryInterface records.
Event Surface Is Minimal
The repository contains EmailHistoryEvent, but SfsMailerEvents is currently empty and the bundle does not expose a richer built-in event workflow around sending, storing, or status changes.
Admin Template Redirect On Missing Template
When the template id is not found, the template controller redirects to the mail history search route, not to the template list route.
This is part of the current implementation and can feel odd in a project where history is disabled.
Practical Recommendations
For the current 6.0 codebase, the most robust way to use the bundle is:
- create email classes based on
softspring/mime-translatable; - implement
ExampleEmailInterfacefor the emails you want to preview; - register them through your own tagged template loader;
- use the admin UI for preview and test sending;
- if you need history, implement and persist
EmailHistoryInterfaceyourself.
That matches the real shape of the package better than trying to express the whole mail system through sfs_mailer.templates configuration alone.