Read more

How to turn images into inline attachments in emails

Klaus Weidinger
February 28, 2023Software engineer at makandra GmbH

Not all email clients support external images in all situations, e.g. an image within a link. In some cases, a viable workaround is to turn your images into inline attachments.

Note

Rails provides a simple mechanism to achieve this:

Illustration money motivation

Opscomplete powered by makandra brand

Save money by migrating from AWS to our fully managed hosting in Germany.

  • Trusted by over 100 customers
  • Ready to use with Ruby, Node.js, PHP
  • Proactive management by operations experts
Read more Show archive.org snapshot

This documentation makes it look like you have to care about these attachments in two places. You have to create the attachment in the mailer action and can later use it in the mailer view. This can be really cumbersome, because your mailer action would then need to know ahead of time which images in which versions will be required later.

Luckily you can still create these inline attachments in the mailer view. Thus, we can wait with inlining until we know for sure which image version we will need and the solution becomes very simple.

Here is a small helper function for your mailer views:

def inline!(version)
  # It is recommended to watch for a feature flag, see below.
  return version unless Rails.config.feature_inline_email_images

  # `attachments` is provided by Rails, see the linked documentation above
  # URLs should be unique! If we already inlined this image, don't do it again.
  if attachments[version.url].nil?
    attachments.inline[version.url] = File.read(version.file.path)
  end
  attachments[version.url]
end

This helper expects a Carrierwave version as in input. It returns an object that represents the attachment and responds to the #url method.

Mailer view:

-# without inlining
= image_tag version.url, alt: 'regular image', ...

-# with inlining
= image_tag inline!(version).url, alt: 'inlined image attachment', ...

Our inline! helper from the first code block has the following effects:

  • Turning your images into inline attachments will turn your regular emails into multipart emails
  • The src attributes of img tags in an email will not contain a URL anymore, but will instead reference another part of that email by a content ID cid:xxxxx@xxxxx.mail.
  • Your tests will not be able to verify easily that you used the correct image version any longer

Because of the last point, it is recommended to use a feature flag and disable inlining in tests by default so you will still be able to easily test that your emails use the correct images. See Using feature flags to stabilize flaky E2E tests for more details on how to implement and use feature flags.

For these cases where you explicitly want to test the inlining itself, keep in mind that you now have to deal with a multipart email. The main content will be in the first part. The following snippet makes your tests work mostly the same way for both regular and multipart emails:

let(:body) do
  if mail.body.parts.any?
    mail.body.parts.first.body.to_s
  else
    mail.body.to_s
  end
end

let(:dom) { Nokogiri::HTML(body) }

The only difference will the src attributes of your image tags. As mentioned above, they will contain content IDs instead of URLs when you turn the inlining on.

Klaus Weidinger
February 28, 2023Software engineer at makandra GmbH
Posted by Klaus Weidinger to makandra dev (2023-02-28 10:29)