Styling Emails with Encore & Sass Part 1

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 app uses Webpack Encore to manage its frontend assets. It's not something we talked much about because, if you downloaded the course code from this page, it already included the final build/ directory. I did this so we didn't need to worry about setting up Encore just to get the site working.

But if you are using Encore, we can make a few improvements to how we're styling our emails. Specifically, we took two shortcuts. First, the assets/css/foundation-emails.css file is something we downloaded from the Foundation website. That's not how we would normally do things with Encore. If we need to use a third-party library, we typically install it with yarn instead of committing it directly.

The other shortcut was with this emails.css file. I'd rather use Sass... but to do that, I need to process it through Encore.

Installing Foundation Emails via Yarn

Let's get to work! Over in the terminal, start by installing all the current Encore dependencies with:

yarn install

When that finishes, install Foundation for Emails with:

yarn add foundation-emails --dev

The end result is that we now have a giant node_modules/ directory and... somewhere way down in this giant directory... we'll find a foundation-emails directory with a foundation-emails.css file inside. They also have a Sass file if you want to import that and control things further... but the CSS file is good enough for us.

Before we make any real changes, make sure Encore can build by running:

yarn dev --watch

And... excellent! Everything is working.

Using Sass & Importing Foundation Emails

Now that we've installed Foundation for Emails properly, let's delete the committed file: I'll right click and go to "Refactor -> Delete". Next, because I want to use Sass for our custom email styling, right click on email.css, go to "Refactor -> Rename" and call it email.scss.

Because this file will be processed through Encore, we can import the foundation-email.css file from right here with @import, a ~ - that tells Webpack to look in the node_modules/ directory - then foundation-emails/dist/foundation-emails.css.

@import "~foundation-emails/dist/foundation-emails.css";
... lines 3 - 39

This feels good! I'll close up node_modules/... cause it's giant.

Creating the Email Entry

Now open up the email layout file: templates/email/emailBase.html.twig. When we used inline_css(), we pointed it at the foundation-emails.css file and the email.css file. But now... we only really need to point it at email.scss... because, in theory, that will include the styles from both files.

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

The problem is that this is now a Sass file... and inline_css only works with CSS files: we can't point it at a Sass file and expect it transform the Sass into CSS. And even if it were a CSS file, the @import won't work unless we process this through Encore.

So here's the plan: we're going to pretend that email.scss is just an ordinary CSS file that we want to include on some page on our site. Open up webpack.config.js. Whenever we have some page-specific CSS or JS, we add a new entry for it. In this case, because we don't need any JavaScript, we can add a "style" entry. Say .addStyleEntry() - call the entry, how about, email, and point it at the file: ./assets/css/email.scss.

... lines 1 - 2
Encore
... lines 4 - 24
.addStyleEntry('email', './assets/css/email.scss')
//.addEntry('page1', './assets/js/page1.js')
... lines 27 - 77
;
... lines 79 - 80

To get Webpack to see the updated config, in the terminal, press Ctrl+C to stop Encore and restart it:

yarn dev --watch

And... it builds! Interesting: the email entrypoint dumped two CSS files. Let's look at the public/build directory. Yep: email.css and also this vendors~email.css.

This is thanks to an optimization that Wepback Encore makes when you use splitEntryChunks()... which you can learn all about in our Encore tutorial. But the basic point is that if we want all of the CSS from the built email.scss file, we need to include both email.css and vendor~email.css.

Ok, easy, right? In the template, we could load the source of vendor~email.css and email.css. The problem is that Webpack splits the files in a very dynamic fashion: if it finds a more efficient way to split the files tomorrow - maybe into three files - it will! Plus, when we do our production build, the files will include a dynamic hash in their filename - like email.123abc.css.

So... we need to do a bit more work to reliably load this stuff through inline_css(). Let's do that next with a custom Twig function.

Leave a comment!

This tutorial is built on Symfony 4.3, but will work well with Symfony 4.4 or 5.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-iconv": "*",
        "aws/aws-sdk-php": "^3.87", // 3.110.11
        "knplabs/knp-markdown-bundle": "^1.7", // 1.7.1
        "knplabs/knp-paginator-bundle": "^2.7", // v2.8.0
        "knplabs/knp-snappy-bundle": "^1.6", // v1.6.0
        "knplabs/knp-time-bundle": "^1.8", // v1.9.1
        "league/flysystem-aws-s3-v3": "^1.0", // 1.0.23
        "league/flysystem-cached-adapter": "^1.0", // 1.0.9
        "league/html-to-markdown": "^4.8", // 4.8.2
        "liip/imagine-bundle": "^2.1", // 2.1.0
        "nexylan/slack-bundle": "^2.1,<2.2.0", // v2.1.0
        "oneup/flysystem-bundle": "^3.0", // 3.1.0
        "php-http/guzzle6-adapter": "^1.1", // v1.1.1
        "sensio/framework-extra-bundle": "^5.1", // v5.4.1
        "stof/doctrine-extensions-bundle": "^1.3", // v1.3.0
        "symfony/asset": "^4.0", // v4.3.4
        "symfony/console": "^4.0", // v4.3.4
        "symfony/flex": "^1.0", // v1.6.2
        "symfony/form": "^4.0", // v4.3.4
        "symfony/framework-bundle": "^4.0", // v4.3.4
        "symfony/mailer": "4.3.*", // v4.3.4
        "symfony/messenger": "4.3.*", // v4.3.4
        "symfony/orm-pack": "^1.0", // v1.0.6
        "symfony/security-bundle": "^4.0", // v4.3.4
        "symfony/sendgrid-mailer": "4.3.*", // v4.3.4
        "symfony/serializer-pack": "^1.0", // v1.0.2
        "symfony/twig-bundle": "^4.0", // v4.3.4
        "symfony/twig-pack": "^1.0", // v1.0.0
        "symfony/validator": "^4.0", // v4.3.4
        "symfony/web-server-bundle": "^4.0", // v4.3.4
        "symfony/webpack-encore-bundle": "^1.4", // v1.6.2
        "symfony/yaml": "^4.0", // v4.3.4
        "twig/cssinliner-extra": "^2.12", // v2.12.0
        "twig/extensions": "^1.5", // v1.5.4
        "twig/inky-extra": "^2.12" // v2.12.0
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.0", // 3.2.2
        "easycorp/easy-log-handler": "^1.0.2", // v1.0.7
        "fzaninotto/faker": "^1.7", // v1.8.0
        "symfony/browser-kit": "4.3.*", // v4.3.5
        "symfony/debug-bundle": "^3.3|^4.0", // v4.3.4
        "symfony/dotenv": "^4.0", // v4.3.4
        "symfony/maker-bundle": "^1.0", // v1.13.0
        "symfony/monolog-bundle": "^3.0", // v3.4.0
        "symfony/phpunit-bridge": "^3.3|^4.0", // v4.3.4
        "symfony/profiler-pack": "^1.0", // v1.0.4
        "symfony/var-dumper": "^3.3|^4.0" // v4.3.4
    }
}