Embedded Images

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

Look book at the HTML source. When we added the logo earlier, we added it as a normal img tag. The only thing special was that we needed to use the absolute_url function in Twig to make sure the URL contained our domain.

Linking versus Embedding Images

It turns out that there are two ways to put an image into an email. The first is this one: a normal, boring img tag that links to your site. The other option is to embed the image inside the email itself.

There are pros and cons to both. For example, if you link directly to an image on your site... and you delete that image... if the user opens up the email, that image will be broken. But... the fact that you're linking to an image on your site... means that you could change the image... and it would change on all the emails.

We'll talk more about when you should link to an image versus embed an image in a few minutes. But first, let's see how we can embed this logo.

Remember, the source logo image is located at assets/images/email/logo.png. This is the physical file we want to embed.

Adding a Twig Path to Images

How do we do that? We're going to do it entirely from inside of Twig with a special function that points to that image.

But to do this, we need a way to refer to the image file from inside of Twig. We're going to do that by adding a new twig path. Open up config/packages/twig.yaml... and I'll close a few files.

One of the config keys you can put under twig is called paths... and it's super cool. Add one new "path" below this: assets/images - I'm literally referring to the assets/images directory - set to the word... how about... images. That part could be anything.

twig:
... line 2
paths:
'assets/images': images
... lines 5 - 9

Ok... so what did this just do? Forget about emails entirely for a minute. Out-of-the-box, when you render a template with Twig, it knows to look for that file in the templates/ directory... and only in the templates/ directory. If you have template files that live somewhere else, that is where "paths" are handy. For example, pretend that, for some crazy reason, we decided to put a template inside the assets/images/ directory called dark-energy.html.twig. Thanks to the item we added under paths, we could render that template by using a special path @images/dark-energy.html.twig.

This feature is referred to as "namespaced Twig paths". You configure any directory, set it to a string "namespace" - like images - then refer to that directory from twig by using @ then the namespace.

Embedding an Image

In our case, we're not planning to put a template inside the assets/images/ directory and render it. But we can leverage the Twig path to refer to the logo file.

Back in the template, remove all the asset stuff that was pointing to the logo. Replace it with {{ email.image() }}. Remember, the email variable is an instance of this WrappedTemplatedEmail class. We're literally calling this image() method: we pass it the physical path to an image file, and it takes care of embedding it.

What's the path to the logo file? It's @images/email/logo.png.

<!doctype html>
<html lang="en">
... lines 3 - 55
<body>
<div class="body">
<div class="container">
<div class="header text-center">
<a href="{{ url('app_homepage') }}">
<img src="{{ email.image('@images/email/logo.png') }}" class="logo" alt="SpaceBar Logo">
</a>
</div>
... lines 64 - 93
</div>
</div>
</body>
</html>

Yep, thanks to our config, @images points to assets/images, and then we put the path after that - email/logo.png.

The "cid" and how Images are Embedded

So... what difference does this make in the final email? Let's find out! Go back to the site and do our normal thing to re-submit the registration form. Over in Mailtrap... ok cool - the email looks exactly the same. The difference is hiding in the HTML source. Woh! Instead of the image src being a URL that points to our site... it's some weird cid: then a long string.

This is great email nerdery. Check out the "Raw" tab. We already know that the content of the email has multiple parts: here's the text version, below is the text/html version and... below that, there is now a third part of the email content: the logo image! It has a Content-ID header - this long cfdf933 string - and then the image contents below.

The Content-Id is the key. Inside the message itself, that is what the cid is referring to. This tells the mail client to go find that "part" of the original message and display it here.

So it's kind of like an email attachment, except that it's displayed within the email. We'll talk about true email attachments later.

Linking Versus Embedding

So, which method should we use to add images to an email: linking or embedding? Oof, that's a tough question. Embedding an image makes it more robust: if the source image is deleted or your server isn't available, it still shows up. It also makes the email "heavier". This can be a problem: if the total size of an email gets too big - even 100kb - it could start to affect deliverability: a bigger size sometimes counts against your email's SPAM score. Deliverability is an art, but this is something to be aware of.

Some email clients will also make a user click a "Show images from sender" link before displaying linked images... but they will display embedded images immediately. But I've also seen some inconsistent handling of embedded images in gmail.

So... the general rule of thumb... if there is one, is this: if you need to include the same image for everyone - like a logo or anything that's part of the email's layout - link to the image. But if what you're displaying is specific to that email - like the email is showing you a photo that was just shared with your account on the site - theni you can embed the image, if it's small. When you embed, the image doesn't need to be hosted publicly anywhere because it's literally contained inside the email.

Next, I already mentioned that the style tag doesn't work in gmail... which means that our email will be completely unstyled for anyone using gmail. That's... a huge problem. To fix this, every style you need must be attached directly to the element that needs it via a style attribute... which is insane! But no worries - Mailer can help, with something called CSS inlining.

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
    }
}