Installation:
composer require creonit/mailing-bundle
Enable the bundle in config/bundles.php:
return [
// ...
Creonit\MailingBundle\CreonitMailingBundle::class => ['all' => true],
];
Configuration:
Add the bundle config to config/packages/creonit_mailing.yaml:
creonit_mailing:
from: 'noreply@example.com'
base_template: 'mail/base.html.twig'
templates_path: '%kernel.project_dir%/templates/mailing'
First Use Case:
Create a template file at templates/mailing/welcome.yaml:
welcome:
title: 'Welcome Email'
subject: 'Welcome to Our Platform'
template: '<p>Hello {{ name }}, welcome!</p>'
Send the email in a controller:
use Creonit\MailingBundle\MailingService;
public function sendWelcome(MailingService $mailingService)
{
$mailingService->send('welcome', ['name' => 'John Doe']);
}
YAML Templates:
Store reusable email templates in YAML files under templates_path (e.g., welcome.yaml, invoice.yaml).
Use Twig syntax for dynamic content:
invoice:
subject: 'Your Invoice #{{ invoice_number }}'
template: 'Invoice details: {{ items|join(", ") }}'
Globals:
Define reusable variables in config/packages/creonit_mailing.yaml under globals:
creonit_mailing:
globals:
app_name: 'MyApp'
support_email: 'support@example.com'
Access them in templates via {{ globals.app_name }}.
Dynamic Context: Pass context data when sending emails:
$mailingService->send('welcome', [
'name' => 'Alice',
'user_id' => 123,
]);
Service Integration:
Bind the MailingService to Laravel’s container in config/services.php:
'mailing' => Creonit\MailingBundle\MailingService::class,
Inject it into controllers/services:
public function __construct(private MailingService $mailingService) {}
Event-Driven Emails: Trigger emails from events (e.g., user registration):
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
public function register(UserRegisteredEvent $event, EventDispatcherInterface $dispatcher)
{
$dispatcher->dispatch(new SendWelcomeEmailEvent($event->getUser()));
}
Listen for the event in a subscriber:
public function onSendWelcomeEmail(SendWelcomeEmailEvent $event, MailingService $mailingService)
{
$mailingService->send('welcome', ['name' => $event->getUser()->name]);
}
Queueing Emails: Use Laravel’s queue system to defer email sending:
$mailingService->sendLater('welcome', ['name' => 'Bob'], now()->addMinutes(5));
Template Loaders:
Extend AbstractTemplateLoader to load templates from custom sources (e.g., database, API):
class DatabaseTemplateLoader extends AbstractTemplateLoader
{
public function load(TemplateCollection $collection)
{
$templates = $this->fetchFromDatabase();
foreach ($templates as $template) {
$mailingTemplate = new MailingTemplate($template['name']);
$mailingTemplate->setSubject($template['subject']);
$mailingTemplate->setTemplate($template['body']);
$collection->add($mailingTemplate);
}
}
}
Register the loader in config/packages/creonit_mailing.yaml:
creonit_mailing:
template_loaders:
- Creonit\MailingBundle\Templating\Loader\DatabaseTemplateLoader
Message Builders: Customize email construction (e.g., add attachments, set reply-to):
class AttachmentMessageBuilder implements MessageBuilderInterface
{
public function build(MailingTemplate $template, array $context): MailingMessage
{
$message = new MailingMessage();
$message->setSubject($template->getSubject());
$message->setTemplate($template->getTemplate());
$message->addAttachment($context['attachment_path']);
return $message;
}
}
Register the builder in config/packages/creonit_mailing.yaml:
creonit_mailing:
message_builder: Creonit\MailingBundle\Message\AttachmentMessageBuilder
Template Paths:
Ensure templates_path in the config points to an existing, writable directory. Defaults to config/mailing_templates, but Laravel’s templates directory is more conventional.
Fix: Update templates_path to %kernel.project_dir%/templates/mailing.
Caching Templates: The bundle caches templates by default. Clear the cache after adding new templates:
php bin/console cache:clear
Tip: Disable caching in development:
creonit_mailing:
cache_templates: false
Twig Extensions: If using custom Twig functions/filters in templates, ensure they’re registered with Twig:
$twig->addExtension(new MyCustomTwigExtension());
Tip: Extend the bundle’s Twig environment in a compiler pass.
Context Overrides:
Template variables can be overridden by context data. If {{ name }} is missing in the context but exists in globals, it will use the global value.
Tip: Explicitly pass required context data to avoid runtime errors.
Template Errors: Enable Twig’s strict variables to catch undefined variables:
twig:
strict_variables: true
Tip: Use {{ dump(context) }} in templates to inspect passed data.
Logging: Enable debug mode to log template loading and email sending:
creonit_mailing:
debug: true
Check logs at var/log/dev.log.
Missing Templates: If a template isn’t found, verify:
templates_path.welcome in welcome.yaml).Custom Mailers: Replace the default Swiftmailer integration by binding a custom mailer service:
$container->bind(MailingService::class)->to(MyCustomMailingService::class);
Template Validation:
Add validation for template fields (e.g., required subject or template):
class ValidatingTemplateLoader extends AbstractTemplateLoader
{
public function load(TemplateCollection $collection)
{
$templates = $this->loadTemplatesFromSource();
foreach ($templates as $template) {
if (empty($template['subject'])) {
throw new \InvalidArgumentException('Subject is required.');
}
// ...
}
}
}
Internationalization:
Support multi-language emails by dynamically setting subject and template based on locale:
# templates/mailing/welcome.en.yaml
welcome:
subject: 'Welcome (English)'
template: 'Hello {{ name }}!'
# templates/mailing/welcome.fr.yaml
welcome:
subject: 'Bienvenue (French)'
template: 'Bonjour {{ name }}!'
Tip: Use a custom template loader to switch templates based on the current locale.
Testing:
Mock the MailingService in PHPUnit tests:
$mailingService = $this->createMock(MailingService::class);
$mailingService->expects($this->once())
->method('send')
->with('welcome', ['name' => 'Test User']);
$this->app->instance(MailingService::class, $mailingService);
How can I help you explore Laravel packages today?