Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

To Inline Style Email Bundle Laravel Package

robertotru/to-inline-style-email-bundle

Symfony bundle that inlines CSS into HTML emails, converting stylesheet rules into style attributes so messages render correctly in Gmail, Outlook and other clients with limited CSS support. A small, dependency-light helper for reliable email templates.

View on GitHub
Deep Wiki
Context7

Getting Started

The bundle is a thin Symfony wrapper around tijsverkoyen/css-to-inline-styles. Its job: take an HTML document plus a CSS stylesheet and return you the same HTML with every matching rule pushed into a style="…" attribute — which is what most email clients actually render. Nothing about it is specific to email; it just happens to solve the one CSS problem every HTML mailer hits.

Install and register:

composer require robertotru/to-inline-style-email-bundle

Symfony ≤3.4 — register in app/AppKernel.php:

$bundles = [
    // ...
    new ToInlineStyleEmailBundle\ToInlineStyleEmailBundle(),
];

Symfony 4+ — register in config/bundles.php:

return [
    ToInlineStyleEmailBundle\ToInlineStyleEmailBundle::class => ['all' => true],
];

First use case: the Twig filter. The bundle exposes a filter (check the Twig extension class in the vendor tree for the exact name — it is typically inline_css, but forks have renamed it). The simplest call is on rendered HTML:

{{ include('emails/welcome.html.twig')|inline_css('assets/css/email.css') }}

or, passing the CSS as a string:

{{ html|inline_css(cssString) }}

Where to look first:

  1. The tijsverkoyen/css-to-inline-styles README — the bundle is only an integration, so all behavior around specificity and existing style attributes is defined upstream.
  2. The bundle's Resources/config/services.xml (or .yml) to confirm the exact service id and the Twig filter name for the version you installed. Don't trust blog posts; ids have shifted between fork and upstream.

Implementation Patterns

Twig filter in a mail-templating workflow. Keep the HTML and CSS separate: templates/emails/welcome.html.twig for markup, assets/css/email.css for styles. Render the template normally, then inline at the "I'm about to hand HTML to the mailer" stage.

public function sendWelcome(User $user)
{
    $html = $this->renderView('emails/welcome.html.twig', ['user' => $user]);
    $css  = file_get_contents($this->getParameter('kernel.project_dir').'/assets/css/email.css');

    $translator = $this->get('to_inline_style_email');   // verify the id
    $inlined    = $translator->convert($html, $css);

    $message = (new \Swift_Message('Welcome'))
        ->setFrom('[email protected]')
        ->setTo($user->getEmail())
        ->setBody($inlined, 'text/html');

    $this->get('mailer')->send($message);
}

When using the Twig filter, wrap the output — autoescape will escape the style="…" attributes and you'll end up serving literal <span>:

{% autoescape false %}
    {{ renderedEmail|inline_css(css) }}
{% endautoescape %}

Wrap it in a Mailer service. The pattern that scales: a single service with a sendTemplatedHtml($template, $vars, $to) method that owns the render → inline → Swift_Message sequence. It keeps inlining out of controllers and makes the step testable in isolation. Add a MailRenderer decorator if you want to swap in a different inliner later.

Precompile and cache CSS. Emails don't fetch external stylesheets, so every message must carry its CSS inline. Read each CSS file once (at deploy, or with Symfony's cache.app pool) and reuse the string. Doing file_get_contents() on every send is the classic accidental performance hit.

Testing. A functional test that renders the mail template through the inliner and asserts on behavior — <style> blocks removed, style="…" present on a known selector — catches upgrade regressions. Avoid golden-file snapshots: the underlying library reorders and reformats attributes between minor versions.

Mix with a layout. Most teams put a shared header/footer partial around the email body, then inline the whole rendered document once. Inlining per-partial is wasted work and makes specificity reasoning harder.

Gotchas and Tips

The age problem is the big one. Last release was June 2017 — Symfony 2.8/3.x era. Bundle registration on 4+ works via config/bundles.php, but the services.xml layout and Twig extension registration may not play nicely with Flex auto-wiring. If debug:container won't show the filter's service, the bundle's DI config isn't being loaded.

Twig 2 vs Twig 3. The bundle predates Twig 3, so Twig_Extension base classes and getName() are deprecated or removed. If you're on a current Symfony, expect to either pin Twig to ^2 or patch the filter registration. Check composer.json of the installed tag before upgrading.

Autoescape, again. The {{ … }} output of an inliner is HTML. {% autoescape false %} around the filter call is correct; |raw everywhere is how XSS gets in via templated variables that themselves weren't escaped upstream. Escape the variables, disable autoescape only around the inliner.

Path resolution for the CSS argument. If the filter accepts a file path, it resolves relative to the process/console working directory, not the template — and a missing file typically yields an empty CSS string (no styles, no error). Pass the CSS string yourself from a known absolute path, or wire the path into a parameter.

Specificity merges. Existing style="…" attributes on the input HTML are not automatically overridden; the library merges by specificity, so an inline style= in your template beats a stylesheet rule unless the rule is !important or more specific. If a color "doesn't apply," look at the source tag before blaming the inliner.

<style> cleanup is not guaranteed. Verify in a test that <style> blocks are actually removed in your version, and decide whether you want @media blocks preserved (many clients honor them) or stripped. Behavior here changed between css-to-inline-styles releases.

No email-layout magic. The bundle does not give you table-based layouts, Outlook conditional comments, or mso- fallbacks. It only inlines CSS. Plan for a separate base template.

Service id drift. Forks rename things. bin/console debug:container to_inline_style (Symfony ≤3) or debug:container --show-private will tell you the real id on your version faster than any tutorial.

Drop the wrapper if it fights you. The bundle is a few hundred lines of glue. If Symfony integration blocks you, use tijsverkoyen/css-to-inline-styles directly in a small service — same output, one less compatibility surface to reason about.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
boomerangui/chat