druidvav/email-bundle
Symfony 4 bundle for transactional emails with Twig subject/text/HTML templates, Emogrifier CSS inlining and optional caching, embedded images, lifecycle events, locale switching, and multiple sender profiles via Symfony Mailer.
Bottom line: this is a Symfony 4.4 bundle, not a Laravel package. It cannot be installed into a Laravel application as-is, and it should not be treated as a dependency. It is, however, a compact and readable feature specification for a transactional-email pipeline — and two of its three core mechanisms are already present in modern Laravel. Recommendation: harvest the design, do not adopt the artifact.
Architecture fit
Bundle subclass that depends on FrameworkBundle, TwigBundle, EventDispatcher, Translation, ServiceLocator, and optionally Doctrine/Gedmo Translatable. None of that container wiring, bundle registration (config/bundles.php), or dv_email config tree maps onto Laravel's service providers, config/mail.php, or its event system. There is no compatibility shim worth writing.cid: image embedding, four lifecycle events (BeforeRenderHtmlEvent, AfterRenderHtmlEvent, BeforeSendEvent, AfterSendEvent), per-render locale switching with state restore, multiple sender profiles mapped to named MailerInterface services, and an alias-based message locator.MailComposer-style service plus a config array. Laravel already ships symfony/mailer (9+) and tijsverkoyen/css-to-inline-styles (used by Markdown mail), and exposes MessageSending/MessageSent events that correspond directly to the bundle's before/after-send hooks. $message->embed() covers cid: images. App::setLocale() covers the locale listener. The bundle's marginal value over native Laravel is roughly the config schema and the event naming convention.Integration feasibility
composer require: not viable. Symfony ^4.4 component constraints (4.4 reached end of life in November 2023) will collide with any Laravel 10/11 install requiring Symfony 6/7 components. The failure mode is dependency-resolution failure, not runtime failure — which at least fails fast.Technical risk
symfony/mailer (SwiftMailer migration documented in MIGRATION.md). Any evaluation relying on that note is stale.Key questions
Stack fit (feature-by-feature mapping)
| Bundle capability | Laravel-native equivalent | Verdict |
|---|---|---|
| Twig subject/text/HTML render | Blade Mailable::build() / Markdown mail |
Reimplement in Blade |
| Emogrifier CSS inlining + cache | tijsverkoyen/css-to-inline-styles (already a framework dependency) |
Free |
cid: image embedding |
$message->embed() / embedData() |
Free |
| Before/AfterRenderHtml events | Pre-build hook or view composer | Trivial |
| Before/AfterSend events | MessageSending / MessageSent listeners |
Free |
| Locale switch + restore | App::setLocale() / Mail::locale() |
Free — Laravel has this built in |
| Multiple sender profiles | Named mailers in config/mail.php + Mail::mailer('x') |
Free |
| Message locator by alias | Container binding / config map | Trivial |
| SMTP failover | Mailer DSN failover(...) |
Free |
The honest read of this table: eight of nine capabilities are already in the framework. The port is a thin configuration-driven facade, not a migration project.
Migration path
composer require druidvav/email-bundle in a throwaway Laravel 11 app and record the exact constraint conflict. Deliverable: a one-paragraph "why not" that closes the door permanently and prevents re-litigation.dv_email config schema and event contract as the spec for an internal acme/mail-kit package. Implement render → inline → embed → dispatch → send with Blade views and the framework's existing inliner.Compatibility
composer why-not against the target stack first. Budget for a security-patch cadence the upstream project does not provide.MessageSending/MessageSent event payloads are stable public API; Blade and the CSS inliner are framework-level and versioned with Laravel itself.Sequencing Legal/license clearance → spike to prove incompatibility → spec extraction from README and behavior → internal package → flagged rollout → flag removal. Do the licensing check before step 2; it is the only step that can invalidate the plan.
Maintenance
Support
Scaling
kernel.cache_dir, which is a per-host cache and not safe to rely on across a horizontally scaled fleet without thinking about invalidation.Failure modes to design against
RequestContext and translator locale, then restores. In a long-lived queue Worker, an exception mid-render leaves the process in the wrong locale for the next job. Port must use try/finally or, better, pass locale explicitly per message.cid: embedding is skipped and remote URLs are used behind authentication.kernel.cache_dir may be read-only.Ramp-up
How can I help you explore Laravel packages today?