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:
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 ofimg
tags in an email will not contain a URL anymore, but will instead reference another part of that email by a content IDcid: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.