pelago/emogrifier
Emogrifier converts CSS from blocks and stylesheets into inline style attributes in HTML. Ideal for HTML email rendering in clients with poor CSS support (e.g., Outlook, Gmail), ensuring consistent styling across email and mobile readers.
Installation:
composer require pelago/emogrifier
Add to composer.json under require if preferred.
Basic Inlining: For HTML emails or mobile-optimized content, inline CSS directly:
use Pelago\Emogrifier\CssInliner;
$html = '<html><body><p class="email-text">Hello</p></body></html>';
$css = '.email-text { color: red; }';
$inlinedHtml = CssInliner::fromHtml($html)->inlineCss($css)->render();
Output: <p style="color: red;">Hello</p> (with proper HTML structure preserved).
No External CSS?
Skip the $css parameter to parse <style> blocks in the HTML:
$inlinedHtml = CssInliner::fromHtml($html)->inlineCss()->render();
First Use Case:
Preprocessing:
Normalize HTML structure (add missing <head>, <body>, fix nesting):
use Pelago\Emogrifier\HtmlProcessor\HtmlNormalizer;
$normalizedHtml = HtmlNormalizer::fromHtml($rawHtml)->render();
Inlining CSS:
inlineCss($css).<style> blocks.disableStyleBlocksParsing() or addExcludedSelector().Post-Processing:
display: none elements or redundant classes:
$prunedHtml = HtmlPruner::fromDomDocument($dom)
->removeElementsWithDisplayNone()
->removeRedundantClassesAfterCssInlined($cssInliner)
->render();
width: 100px → width="100"):
$visualHtml = CssToAttributeConverter::fromDomDocument($dom)
->convertCssToVisualAttributes()
->render();
CSS Variables:
Resolve var(--variable) in inline styles:
$evaluatedHtml = CssVariableEvaluator::fromDomDocument($dom)
->evaluateVariables()
->render();
Laravel Blade:
Create a helper function in app/Helpers/EmailHelper.php:
use Pelago\Emogrifier\CssInliner;
if (!function_exists('emogrify')) {
function emogrify($html, $css = null) {
return CssInliner::fromHtml($html)->inlineCss($css)->render();
}
}
Use in Blade:
{!! emogrify($emailTemplate, $styles) !!}
Service Provider: Register a service to handle email templating:
// app/Providers/EmailServiceProvider.php
public function register() {
$this->app->singleton('emogrifier', function () {
return new CssInliner();
});
}
Queue Jobs: For async processing (e.g., sending emails):
// app/Jobs/ProcessEmail.php
public function handle() {
$html = $this->emogrifier->fromHtml($this->template)->inlineCss($this->styles)->render();
Mail::send([...], $html);
}
Testing:
Use renderBodyContent() to isolate body content for unit tests:
$body = CssInliner::fromHtml($html)->inlineCss()->renderBodyContent();
$this->assertStringContainsString('style="color: red;"', $body);
Selector Specificity:
addExcludedCssSelector('.example') won’t exclude p .example. Use universal selectors (e.g., .example, * .example) for broader exclusion.:hover, :active, etc., are not supported. Avoid dynamic styles.Media Queries:
all, screen, print. Add others with addAllowedMediaType().@media entirely. Exclude critical styles with addExcludedCssSelector().HTML Structure:
HtmlNormalizer adds <head>/<body> if absent, but may alter structure. Test edge cases (e.g., <div><p></div>).<style> blocks are removed by default. Use disableStyleBlocksParsing() to preserve them.CSS Variables:
:root or an ancestor element to be resolved.var(--undefined)) remain as-is.Performance:
div.container > ul li:nth-child(2)) slow processing. Optimize selectors or use addExcludedSelector() for non-critical elements.DOMDocument instances:
$dom = new DOMDocument();
$dom->loadHTML($html);
$cssInliner = CssInliner::fromDomDocument($dom)->inlineCss($css);
Inspect Output:
Use render() to see intermediate HTML. For debugging, log the DOM:
$dom = $cssInliner->getDomDocument();
file_put_contents('debug.html', $dom->saveHTML());
Selector Testing: Test selectors in isolation:
$html = '<div class="test">Hello</div>';
$css = '.test { color: red; }';
$result = CssInliner::fromHtml($html)->inlineCss($css)->render();
Common Issues:
HtmlNormalizer to fix malformed HTML.Custom Processors:
Extend AbstractHtmlProcessor to add steps (e.g., sanitization):
class CustomProcessor extends AbstractHtmlProcessor {
public function addCustomAttribute() {
$this->dom->documentElement->setAttribute('data-processed', 'true');
return $this;
}
}
Selector Extensions:
Override getSupportedSelectors() in a custom CssInliner class to support unsupported selectors (e.g., :nth-child).
Post-Inlining Hooks:
Use getDomDocument() to manipulate the DOM after inlining:
$dom = $cssInliner->getDomDocument();
$xpath = new DOMXPath($dom);
foreach ($xpath->query('//img') as $img) {
$img->setAttribute('alt', 'Fallback');
}
Configuration:
Centralize options in a config file (e.g., config/emogrifier.php):
return [
'allowed_media' => ['all', 'screen', 'print', 'handheld'],
'excluded_selectors' => ['.ad-banner', '.ad-banner *'],
];
Load in a service provider:
$cssInliner->addAllowedMediaType(config('emogrifier.allowed_media'));
Email-Specific Optimizations:
<table> for layout (better email client support). Inline border-collapse and width attributes.!important to critical styles (e.g., color: red !important;).Caching: Cache inlined HTML for static emails:
$cacheKey = md5($html . $css);
$inlinedHtml = Cache::remember($cacheKey, now()->addHours(1), function() use ($html, $css) {
return CssInliner::fromHtml($html)->inlineCss($css)->render();
});
Testing Framework: Create a trait for Laravel tests:
trait EmogrifierTest {
protected function assertInlinedStyles($expectedStyles, $html) {
$inlined = CssInliner::fromHtml($html
How can I help you explore Laravel packages today?