Dónal
Dónal

Reputation: 187419

Including an <img> in a Thymeleaf-generated HTML document

In my Spring Boot app, I'm generating HTML emails with Thymeleaf. I want to include an <img> in these emails. The image is stored at /src/main/resources/static/img/logo.png.

I've confirmed that the image can be resolved by starting the app locally and requesting http://localhost:8080/img/logo.svg in a browser.

To include this image in the HTML, I've tried all of the following

  1. <img th:src="@{/img/logo.png}" />
  2. <img th:src="@{img/logo.png}" />
  3. <img th:src="@{~/img/logo.png}" />
  4. Base64 encoded image <img src="data:image/png;base64,iVBORw0KGgoAA..." />

The outcome of each of these is:

  1. Throws an exception: org.thymeleaf.exceptions.TemplateProcessingException: Link base "/img/logo.svg" cannot be context relative (/...) unless the context used for executing the engine implements the org.thymeleaf.context.IWebContext interface
  2. Renders <img src="img/logo.png" /> which appears in the email as a broken image
  3. Renders <img src="/img/logo.png" /> which appears in the email as a broken image
  4. The image is rendered in most email clients I tested, but it's blocked by GMail, and there's no way to unblock it via the settings.

I guess that in order for the image to be rendered correctly within an email I need to provide an absolute URL, but I'm not sure how to achieve that.

Part of the problem is that it's not obvious whether an email is not being displayed because the URL is incorrect, or because the email client is blocking images.

Update

I thought this would be obvious, but evidently not: I can't use any solution which hard-codes the hostname to localhost:8080 because this is just the URL I use when running locally, and I also need this to work in other environments, e.g. prod

Upvotes: 5

Views: 6878

Answers (2)

xerx593
xerx593

Reputation: 13299

Advanced

You introduce a property declaring the "public url" (e.g. in application.properties):

public_domain=http://somwhere.com

To use it like:

<img th:src="@{|${public_domain}/img/logo.svg|}" />

like here.


Totally Dynamic

<img th:scr="${#httpServletRequest.scheme}+'://'+${#httpServletRequest.serverName}+':'+${#httpServletRequest.serverPort}+@{img/logo.svg}" />

super cool!! (this will only work in presence of an http (servlet) request, which seems not relevant here.)


Going Deeper

You never know who "watches" your emails with whatever client(, which trusts whatever server..and loads images from it) !!? ...

So embedding image in html email is a "quite popular" question here at [so].

And applied to thymeleaf: They have an extra article for that !! (also showing img attachments .. works in html AND text(without images;()!!!;)

To summarize(, once mailing and templating are configured):

template:

 <img src="sample.png" th:src="|cid:${imageResourceName}|" />

The img element has a hardcoded src value —nice for prototyping—, which will be substituted at runtime by something like cid:image.jpg matching the attached image filename.

service:

String imageResourceName = ...
byte[] imageBytes = ...
String imageContentType = ...

// Prepare the evaluation context
final Context ctx = new Context(locale);
...
ctx.setVariable("imageResourceName", imageResourceName); // so that we can reference it from HTML

// Prepare message using a Spring helper
final MimeMessage mimeMessage = this.mailSender.createMimeMessage();
final MimeMessageHelper message = ...
message.set...

// Create the HTML body using Thymeleaf
final String htmlContent = ...

// Add the inline image, referenced from the HTML code as "cid:${imageResourceName}" !!!
final InputStreamSource imageSource = new ByteArrayResource(imageBytes);
message.addInline(imageResourceName, imageSource, imageContentType);

// Send mail ...

Upvotes: 2

xerx593
xerx593

Reputation: 13299

So "strategically" we have two options:

  • A: to serve the image (from a public server with a static url).
  • B: to send the image "with the email body".

(pros, cons,...)

A

We should do it as an "Absolute URL" as proposed by Thymeleaf:

Absolute URLs allow you to create links to other servers. They start by specifying a protocol name (http:// or https://) ...

They are not modified at all (unless you have an URL Rewriting filter configured at your server and performing modifications at the HttpServletResponse.encodeUrl(...) method)...

<img th:src="@{http://localhost:8080/img/logo.svg}" />

This will be rendered to:

<img src="http://localhost:8080/img/logo.svg" />

(whatever the mail client will do with it)

We can advance this approach by introducing & using some placeholder (for different environments e.g.):

(e.g.) application.properties:

public_domain=http://somewhere.com

To use it like:

<img th:src="@{|${public_domain}/img/logo.svg|}" />

like here.

B

Here we have (technically) the options to:

The outline of the Thymeleaf article, once templating and mailing are configured, is:

template html:

 <img src="/path/when/not/thymeleaf-generated/logo.svg" th:src="|cid:${imageResourceName}|" />

The img element has a hardcoded src value — nice for prototyping —, which will be substituted at runtime by something like cid:image.jpg matching the attached image filename.

service java:

String imageResourceName = ...
byte[] imageBytes = ...
String imageContentType = ... // availabe!

// Prepare the evaluation context
final Context ctx = new Context(locale);
...
ctx.setVariable("imageResourceName", imageResourceName); // so that we can reference it from HTML

// See Article...

// Add the inline image, referenced from the HTML code as "cid:${imageResourceName}" !!!
final InputStreamSource imageSource = new ByteArrayResource(imageBytes);
message.addInline(imageResourceName, imageSource, imageContentType);

// Send mail ...

Upvotes: 0

Related Questions