Ink: Automatic CSS Email Framework

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Our email template is HTML... very traditional HTML. What I mean is, this is the type of HTML and CSS you would see on a normal website. And, at least inside Mailtrap... it looks good! But a big lesson of sending emails is that the HTML is often not rendered like a normal browser would render it. Some email clients don't support float or flexbox... so if you're using those to establish an email layout then... oof, it's going to look bad for some people... like people using gmail.

If you want to write an email that's going to look consistently good in every email client, the best practice is actually to use tables for your layout. If you have no idea what a table layout is... oh, you are so, so lucky. Back in the dark ages of the Internet, back before CSS float and flexbox existed, every webpage's layout consisted of tables, rows and cells. It was tables, inside of tables, inside of tables, inside of tables. It was... a nightmare.

So... um... am I saying that the nightmare of needing to write table-based layouts is still a reality when you create emails? Yes... and no. Mailer has another trick up its sleeve.

Hello Ink / Foundation for Emails

Google for "Inky Framework" to find something called "Ink" by "Zurb". Let me define... a few things. Zurb is the name of a company, a cool name - it sounds like an alien race: "the Zurb". Anyways, Zurb is the company that created "Foundation": a CSS framework that's probably the second most famous in the world behind Bootstrap. "Ink" is the name of a CSS framework that's designed specifically for emails. And actually, they've renamed "Ink" to just "Foundation for Emails".

So, Ink, or Foundation for Emails is a CSS framework for responsive HTML emails that works on any device. Even Outlook! Click on the docs.

Foundation for emails is basically two parts. First, it's a CSS file that defines useful CSS classes and a grid structure for designing emails. Again... it's just like Bootstrap CSS for emails.

The Inky Templating Language

That CSS file is super handy. But the second part of Foundation for emails is even more interesting. Click the "Inky" link on the left. The second part of this library is centered around a custom templating language called "Inky". It's a simple, but fascinating tool. Click the "Switch to Inky" link.

Here's the idea: we write HTML using some custom Inky HTML tags, like <container>, <row> and <columns>... as well as a few others like <button> and <menu>. Then, Inky will transform this pretty HTML into the crazy, ugly table-based layout required for it to render in an email! Yea, it lets us have table-based emails... without needing to use tables! Yeehaw!

Using the inky_to_html Filter

Now if you downloaded the course code, you should have a tutorial/ directory, which holds the original welcome.html.twig and an inky/ directory with an updated welcome.html.twig. New stuff!

This is basically the same template but written in that special "Inky" markup: containers, rows, columns, etc. Copy the contents... and let's close a few things. Then open up templates/email/welcome.html.twig and completely replace this file with the updated version.

It's really the same email as before: it has the same dynamic URLs and is printing the recipient's name. It's just different markup. Oh, and notice that the inline_css() stuff we added a few minutes ago is gone! Gasp! Don't worry: we'll put that back in a minute. But until then, forget about CSS.

If we sent this email right now, it would literally send with this markup. To transform this into the table-based markup we want, we'll use another special filter on the entire template. On top, add {% apply inky_to_html %}... and all the way at the bottom, put {% endapply %}. I'll indent this to make it look nice.

{% apply inky_to_html %}
<container>
<row class="header">
<columns>
<a href="{{ url('app_homepage') }}">
<img src="{{ email.image('@images/email/logo.png') }}" class="logo" alt="SpaceBar Logo">
</a>
</columns>
</row>
<row class="welcome">
<columns>
<spacer size="35"></spacer>
<h1>
<center>
Nice to meet you {{ email.toName }}!
</center>
</h1>
<spacer size="10"></spacer>
</columns>
</row>
<spacer size="30"></spacer>
<row>
<columns>
<p>
Welcome to <strong>the Space Bar</strong>, we can't wait to read what you have to write.
Get started on your first article and connect with the space bar community.
</p>
</columns>
</row>
<row>
<columns>
<center>
<button href="{{ url('admin_article_new') }}">Get writing!</button>
</center>
</columns>
</row>
<row>
<columns>
<p>
Check out our existing articles and share your thoughts in the comments!
</p>
</columns>
</row>
<row>
<columns>
<center>
<button href="{{ url('app_homepage') }}">Get reading!</button>
</center>
</columns>
</row>
<row>
<columns>
<p>
We're so excited that you've decided to join us in our corner of the universe,
it's a friendly one with other creative and insightful writers just like you!
Need help from a friend? We're always just a message away.
</p>
</columns>
</row>
<row class="footer">
<columns>
<p>Cheers,</p>
<p>Your friendly <em>Space Bar Team</em></p>
</columns>
</row>
<row class="bottom">
<columns>
<center>
<spacer size="20"></spacer>
<div>
Sent with ❤️ from the friendly folks at The Space Bar
</div>
</center>
</columns>
</row>
</container>
{% endapply %}

Let's try it! Find your browser and make sure you're on the registration page. Let's register as thetruthisoutthere11@example.com, any password, check the terms, register and... error!

Ah, but we know this error! Well, not this exact error, but almost! This is Twig telling us that we're trying to use a filter that requires an extra library. Cool! Copy the composer require line, move back over to your terminal, and paste:

composer require twig/inky-extra

Tip

Make sure you have XSL extension installed for your PHP to be able to use Inky. To check it - you can run php -m | grep xsl in your console and check the output has "xsl".

When that finishes... move back to your browser, go back to the registration form, tweak that email and... deep breath... register! I think it worked! Let's go check it out.

There's the new email! Oof, it looks terrible... but that's only because it doesn't any CSS yet. Check out the HTML source. So cool: it transformed our clean markup into table elements! We just took a huge step towards making our emails look good in every email client... without needing to write bad markup.

Inlining the foundation-emails CSS

To get this to look good, we need to include some CSS from Foundation for Emails. Go back to the documentation, click on the "CSS Version" link and click download. When you unzip this, you'll find a foundation-emails.css file inside. Copy that... and paste it into, how about, the assets/css directory.

How do we include this in our email template? We already know how: the inline_css filter. But instead of adding another apply tag around the entire template, we can piggyback off of inky! Add |inline_css and pass this source() and the path to the CSS file: @styles/foundation-emails.css.

Remember: if you look in config/packages/twig.yaml, we set up a path that allows us to say @styles to refer to the assets/css directory. That's how this path works.

And... I still do want to include my custom email.css code. Copy the source() stuff, add a second argument to inline_css - you can pass this as many arguments of CSS as you want - and point this at email.css.

{% apply inky_to_html|inline_css(source('@styles/foundation-emails.css'), source('@styles/email.css')) %}
... lines 2 - 76
{% endapply %}

That should do it! Oh, but before we try this, back in tutorial/, that inky/ directory also holds an email.css file. Now that we're using a CSS framework, some of the code in our original email.css... just isn't needed anymore! This new email.css is basically the same as the original one... but with some extra stuff removed. Copy the code from the file, and paste it over the one in assets/css.

body {
margin: 0;
padding: 0;
background-color: #f3f3f3;
font-family: Helvetica, Arial, sans-serif;
}
h1 {
background-color: #264459;
color: #ffffff;
/*padding: 30px 0 50px 0;*/
font-weight: normal;
}
hr {
border: none;
border-top: 3px solid #264459;
margin: 20px;
}
.welcome {
background-color: #264459;
}
.bottom {
background-color: #efefee;
}
.logo {
width: 100%;
}
.text-center {
text-align: center;
}
table.button a {
background-color: #264459;
}
table.button table td {
background-color: #264459;
border: 2px solid #264459;
}

Ok, time to see the final product! Go back to the registration page, update the email, add a password, enter and... go check out Mailtrap. There it is and... it looks awesome. Well, it looks exactly like it did before, but in the HTML source, now that we have a table-based layout, we know this will display more consistently across all email clients. I won't say perfect... because you'll need to do some testing - but it's now much more likely to look good.

So that's "Foundation for Emails". It's, one, a CSS framework for emails... a lot like Bootstrap for emails... and two, a tool to transform the pretty markup known as Inky into the ugly table-based HTML that the CSS framework styles and that email clients require.

Watch your Email Sizes

Before we keep going, one thing to watch out for regardless of how you're styling your emails, is email size. It's far from a science, but gmail tends to truncate emails once their size is greater than about 100kb: it hides the rest of the email with a link to see more. Keep that in mind, but more than anything, test your emails to make sure they look good in the real world!

Next, let's bootstrap a console command that will send some emails! It turns out that sending emails in a console command requires an extra trick.

Leave a comment!

  • 2020-04-17 Bagar Davidyan

    👍🏻

  • 2020-04-16 Victor Bocharsky

    Hey Bagar,

    Hm, interesting. OK, thanks for additional clarification on it, I think note would help here.

    Cheers!

  • 2020-04-15 Bagar Davidyan

    Hy Victor!

    I not sure that the XSL-ext is required

    https://github.com/twigphp/...

    I installed the package without any err but then I tried to get a result on the page I got an unobvious error message. Then I've been looking for it in SO and understood I need to install XSL

  • 2020-04-15 Victor Bocharsky

    Hey Bagar,

    Thanks for reporting it! Hm, wasn't error message clear enough? Did you get that error on executing?

    $ composer require twig/inky-extra

    Because in package dependencies it clearly shows that you need to have XSL extension, otherwise I suppose this package won't be even installed. Just want to clarify things here.

    Cheers!

  • 2020-04-10 Diego Aguiar

    Hey Bagar Davidyan

    Thanks for letting us know about it. We'll add a note to the tutorial

    Cheers!

  • 2020-04-10 Diego Aguiar

    Hey Dennis de Best

    Thanks for sharing it with others. Cheers!

  • 2020-04-10 Dennis de Best

    It took some time to get this to work with my php7.4 running on linux alpine. Turns out the xsl extension needed to be installed.

    I made a docker image if anyone has the same problem codebuds/php:prod-1.0.1

  • 2020-04-05 Bagar Davidyan

    wassup, guys!
    pls add a note of requirement to install (activate) XSL extension in local env to make inky work
    I've spent a half-hour to understand what's wrong when got an err

  • 2020-03-09 weaverryan

    Hey nfleming65!

    Ah, that's true! A package downstream requires it - so you would get a Composer error if you're missing it. Is that what you saw? Or did it (somehow) allow you to download the package and you got an error later?

    Cheers!

  • 2020-03-07 nfleming65

    You should also note that you may need to enable the .xsl extension in PHP .ini when you are running the composer require inky command

  • 2019-12-05 donchev

    Thanks for the reply!
    I will implement messenger into my project regardless of the email template - because it's nice ferature. But probably will keep the email templates as a separate project and use only the HTML files in Symfony.
    :)

  • 2019-12-04 weaverryan

    Hey donchev!

    Ah, yes I understand! We also have used the separate "foundation for emails" project - so I understand what you mean by doing all the transforming and inlining at "compile" time. The short answer is that: if you use the Twig functions, then yes - you will be going through the inlining & inky transformation for *every* email. The benefit of course is simplicity in your code. But there is that performance cost - and I haven't profiled it to know exactly how high it is. Some ability to cache the inlining or inky transformation could be added to the Twig extensions - but it doesn't exist currently. So, my best advice is to: (A) profile to see how big of an issue this is and (if it is) (B) either stick with your current setup or (most performant) send your emails async anyways with Messenger. If you're really worried about performance... the last option is the "best" because even if you aren't doing this processing for every email, sending emails still involves a network request. If you can make it async, obviously, that will have the best page speed result.

    Anyways, great question! Let me know if it makes sense.

    Cheers!

  • 2019-12-04 donchev

    Thank You for the reply!

    But what exactly do you mean about "to use production exports of the email templates instead"

    Currently i am developing my email templates as a separate "foundation for emails" project.
    With npm run build I get the production ready files (with css inlined), then I move these file to my Symfony project.

    On my current project I send roughly 50-60k emails per month (transactional + newsletters). If I move the inky files to symfony, this will mean that the inky template will be converted and css inlined by twig 50-60k times per month, right?

  • 2019-12-04 Victor Bocharsky

    Hey Donchev,

    Hm, good question! But what exactly do you mean about "to use production exports of the email templates instead"? Yes, you can always make it faster by doing some kind of optimization (sometimes probably crazy complex optimization?), but does it worth it? And probably it depends! It depends on *your* project and on how many and how often you send your emails. If you really send a lot, probably it would be better for you. But if you send rare, or send not so many emails at once - probably that level of complexity would be worse for you, because you would need to maintain that complexity in the future. Anyway, you can always profile and measure things, and see if you need some optimization in this spot or you just don't care.

    As always, using simple "if-else" router is always faster than using Symfony Router, dispute the fact that Symfony Router is probably the fastest router written in PHP. But does it mean that you need to rewrite all your routes with simple "if-else" router?.. ;)

    I hope this helps!

    Cheers!

  • 2019-12-04 donchev

    Hi team,
    Isn't going to be resource consuming to transform inky to html and inline css on every email send?
    What are the benefits except for keeping the inky files within the same version control?
    I mean, wouldn't it be better to use production exports of the email templates instead?

    Greets!

  • 2019-11-12 weaverryan

    Hey zpine!

    Ah, cool idea! Honestly, it never occurred to me. But yes, I think that's a perfectly appropriate idea - I like it!

    Cheers!

  • 2019-11-11 zpine

    Hi

    Excellent course! I have learned lots of new things!

    Isn't it better to use '*.inky.twig' instead of '*.html.twig'? Personally I think it's pretty straight forward to understand it's not the regular standard html. Anyway not an issue though!

    Thank you!