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 book lover

Growing Rails Applications in Practice

Check out our e-book. Learn to structure large Ruby on Rails codebases with the tools you already know and love.

  • Introduce design conventions for controllers and user-facing models
  • Create a system for growth
  • Build applications to last
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)