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

Zurb Ink Bundle Laravel Package

hampe/zurb-ink-bundle

Symfony bundle that integrates the ZURB Ink responsive email framework, helping you build mobile-friendly HTML emails with Twig templates and Symfony’s templating tools. Ideal for transactional emails and newsletters.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require hampe/zurb-ink-bundle

Register the bundle. There is no Flex recipe, so on Symfony 4+ you add it by hand:

// config/bundles.php
Hampe\ZurbInkBundle\ZurbInkBundle::class => ['all' => true],

On the Symfony 2.8/3.x this bundle was built for, it goes into app/AppKernel.php instead. Check the bundle's README and DependencyInjection/Configuration.php for the exact namespace and config tree for your version — the last tagged release is from early 2018, so expect the docs to describe an older Symfony.

Where to look first, in order:

  1. DependencyInjection/Configuration.php — the authoritative list of options (CSS paths, template locations, inlining toggles). Ignore blog posts; this file is the truth.
  2. Resources/views/ (or templates/) — the base email layout the bundle ships. Extend it rather than writing <html><body> boilerplate by hand.
  3. The Twig extension class (Twig/…Extension.php) — this exposes the filter/function that runs the Inky compiler, which converts Ink tags (<container>, <row>, <columns>, <button>, <callout>) into the nested-table HTML email clients actually understand.

First use case. Build one transactional email end-to-end: a welcome mail.

{# templates/email/welcome.html.twig #}
{% extends '@ZurbInk/layout.html.twig' %}

{% block content %}
  <container>
    <row>
      <columns small="12">
        <h1>Welcome, {{ user.firstName }}</h1>
        <p>Confirm your address to get started.</p>
        <button href="{{ url('confirm', {token: token}) }}">Confirm email</button>
      </columns>
    </row>
  </container>
{% endblock %}

Then render it, run the Inky transform, inline the CSS, and hand the resulting string to your mailer. Get that one email landing correctly in Gmail, Outlook and Apple Mail before you build a second one — the pipeline is the hard part, not the markup.

Implementation Patterns

Wrap the render pipeline in one service. Do not repeat "render → Inky → inline CSS" in every controller. A single EmailRenderer with render(string $template, array $context): string is the pattern that keeps you sane:

$html = $this->twig->render($template, $context);
$html = $this->inky->process($html);          // Ink tags -> tables
$html = $this->inliner->convert($html, $css);  // CSS -> style="" attributes

Ordering is not negotiable: Twig first, Inky second, CSS inlining last. Inlining before Inky means your selectors never match the elements Inky generates.

Layout inheritance over composition. Have one base layout per brand (transactional vs. marketing), extend it everywhere, and override {% block content %}. Partials (_button.html.twig, _footer.html.twig) are fine via include, but run Inky on the fully assembled document — a partial containing <button> rendered on its own will not be transformed.

Keep CSS in files, not in the template. Put assets/email/ink.css and assets/email/brand.css on disk, reference them from bundle config, and let the inliner read them. This gives you one place to change a link colour across every email, and it diffs cleanly in code review.

Preview route, dev-only. A controller guarded by %kernel.debug% that renders a given email template straight to the browser is the single highest-value thing you can add. Designers can iterate without sending mail, and you can eyeball the generated table soup with view-source.

Test the output, not the template. Assert on the rendered string: that no <columns/<row> tags survive, that the confirmation URL is absolute, that the unsubscribe link is present. These catch the regressions that actually reach users' inboxes.

Pass absolute URLs everywhere. url() and absolute_url(asset(...)). Email clients have no base href; relative paths silently 404.

Gotchas and Tips

This package is effectively unmaintained. Last release February 2018, and Zurb Ink itself was superseded by Foundation for Emails and then by MJML. It works fine if you are pinned to an older Symfony, but on Symfony 5+ expect friction: bundle registration, deprecated Twig APIs, and the removal of SwiftMailer all sit downstream of this bundle. Budget a day for a spike before committing to it on a new project; if it fights you, MJML via a small renderer service is the lower-risk path for new work.

CSS inlining destroys media queries. Standard inliners move <style> rules into style="" attributes — which email clients ignore for @media. Make sure your inliner is configured to preserve the <style> block for the media queries while inlining the rest. If your responsive breakpoints suddenly stop working, this is why.

Twig escaping. The Inky output is HTML, so whatever filter or function exposes it must be marked safe, and you must not |raw untrusted user content into the template to compensate. Keep autoescaping on for variables.

Cache warmup hides CSS edits. The bundle resolves CSS file paths at container-build time in some configurations; changing a stylesheet may require bin/console cache:clear before the change appears. If an edit "does nothing", clear the cache before you start debugging your selectors.

Override, don't fork. Bundle templates can be shadowed at templates/bundles/ZurbInkBundle/…. The Inky/Inliner services can be decorated (#[AsDecorator]) or re-aliased in services.yaml if you need custom post-processing — e.g. rewriting URLs to absolute, or appending a tracking pixel.

Check the generated HTML by hand at least once. Inky emits <table role="presentation"> nesting that is verbose and occasionally surprising with <columns small="12" large="6"> combinations. Save the final string to var/email-preview.html, open it, and compare against what you expected. It is much faster than debugging via screenshots from a colleague's Outlook.

Watch line length. Long base64 images or unwrapped HTML can trip quoted-printable encoding and produce =-broken links in some clients. Keep image URLs as asset() references rather than data URIs, and let your mailer's encoder handle wrapping.

Extension point worth knowing: if you need behaviour the bundle doesn't expose — say, injecting a preheader span into every email — write a thin Twig extension or a service decorator around the renderer rather than editing vendor templates. Vendor edits vanish on the next composer update, and this package will not be getting upstream fixes.

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.
alphabalex/messaging-made-easy
rawphp/warp
shootkiran/merobug
narakode/fineauth
numencode/wn-syncops-plugin
ibnnajjaar/mib-global-pay
ably/pubsub-server
mstfkhazaal/defender
mfonte/base62x
karnoweb/metapilot
tobya/bcsapiwrapper
swalker2/cpanel
vish4395/laravel-translation-manager
morningtrain/economic
teksite/systeminfo-laravel
atomcms/voting
run6/weather
siokas/laravelembeddirectives
adamlien/pkuuids
gilsonsouza/lara-doc-generator