Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

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.

... line 2
'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
<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">
... lines 64 - 93

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!

Login or Register to join the conversation
Abelardo Avatar
Abelardo Avatar Abelardo | posted 3 years ago


I had to put this path:
'public/build/assets/images': images
According to Symfony, (me too), "(project_dir)/assets" doesn't exist.

2 Reply

Hey AbelardoLG,

Don't you have assets/ directory in the root of your project? I suppose you didn't download the code and work in your own project, right? In this screencast we set path to the source images, but you set it to the built images, i.e. Encore build directory "public/build/". I think you need to point to dir where your source images are located. I mean, the images come to public/build/assets/images/ somewhere from another "source" directory that you should link to instead. Because Encore build dir should be in .gitignore


Abelardo Avatar

Hi there,

I am build this app on my pace.

My assets directory is included into public dir which is under the root.

I am going to investigate why it happens. :) Brs.


Hey Abelardo,

Yeah, just point it to your assets directory in public/ folder, but not to the public/build/ directory yet. :) We will point to build directory later, but for now we want to point to the image source directory.



Hi Victor, posting this message here as you raised this point. I have to admit that I have just done a few chapters here and there to learn a few specific things about Mailer - I might have missed out where this is done. However, checking the code in the /finish directory, should the twig.yaml file not have the path to the build directory? Or did I miss out something?

    'public/build/images': images

Hey Martin,

It depends on what you need. If you only want to have an ability to load only images from the Twig templates - you will be enough to point only to your image folder in the public/ dir. Actually, it depends on if you handle all your images via Webpack Encore or no. If you do - then yeah, you would need to point to the build/ directory, but once again you may want to point to a specific image directory in that build/ folder. In this course project we just didn't handle images that we're using in emails via Webpack Encore, that's why our images are in "assets/images" and not in "build/", that's it, but it may be different for your custom project, depends on how you do things in it.

I hope this helps!


Evelyn P. Avatar
Evelyn P. Avatar Evelyn P. | posted 2 years ago

What about if you have a separate front-end application, for instance a Vue SPA using Symfony as an API. Would it be better to embed images or have some path to the front-end app hardcoded in the Twig file?


Hey Evelyn P.

If the API is returning the image file paths, then, it should return them as absolute URLs. But, if you're talking about the images loaded by your frontend app (let's say, in order to load its own design) then having a base path stored somewhere makes sense (in a Twig file, or in the frontend app itself)



For a11y please don't use "logo" word inside alternative text of logo image. It's not that useful for screen readers users.


Hey Tomasz,

Thank you for your feedback! Agree, it's not that useful at all, though probably makes sense to change it to "SymfonyCasts Logo"? Or just "SymfonyCasts"? Or are you about to do not use alt text at all? Because, IMO using some alt text would be good for SEO.



The truth is that img[alt] is not meant for SEO. It's meant for a11y technologies like screen readers and for text browsers (like for people who are in process of installing Arch Linux :) ). If your image is decorative (like borders, separators) it should container empty alt="". Don't skip alt at all — in that case screen reader can read filename. If your image contains logo or some banner with text, alt should container exactly that text. For example Foo company logo with "Foo" word and some icons should have just alt="Foo" attribute. The most complex case is with photos and charts — here you have to describe the whole image. Sometimes it might be good idea to just link to another page with text representation, instead.

1 Reply

Thanks again for the advice on this TomaszGasior :)

Cat in space

"Houston: no signs of life"
Start the conversation!

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
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "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.9", // v1.17.6
        "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