1. Overview

In our previous article, we saw how to use Spring to write and send text emails.

But it’s also possible to use Spring template engines to write beautiful HTML emails with dynamic content.

In this tutorial, we’re going to learn how to do it using the most famous of them: Thymeleaf and FreeMarker.

2. Spring HTML Emails

Let’s start from the Spring Email tutorial.

First, we’ll add a method to the EmailServiceImpl class to send emails with an HTML body:

private void sendHtmlMessage(String to, String subject, String htmlBody) throws MessagingException {
    MimeMessage message = emailSender.createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
    helper.setTo(to);
    helper.setSubject(subject);
    helper.setText(htmlBody, true);
    emailSender.send(message);
}

We’re using MimeMessageHelper to populate the message. The important part is the true value passed to the setText method: it specifies the HTML content type.

Let’s see now how to build this htmlBody using Thymeleaf and FreeMarker templates.

3. Thymeleaf Configuration

Let’s start with the configuration. We can isolate this in a class called EmailConfiguration.

First, we should provide a template resolver to locate the template files directory.

3.1. Templates as Classpath Resources

Template files can be shipped within the JAR file, which is the simplest way to maintain cohesion between templates and their input data.

To locate templates from the JAR, we use the ClassLoaderTemplateResolver. Our templates are in the main/resources/mail-templates directory, so we set the Prefix attribute relative to the resource directory:

@Bean
public ITemplateResolver thymeleafTemplateResolver() {
    ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();
    templateResolver.setPrefix("mail-templates/");
    templateResolver.setSuffix(".html");
    templateResolver.setTemplateMode("HTML");
    templateResolver.setCharacterEncoding("UTF-8");
    return templateResolver;
}

3.2. Templates From External Directory

In other cases, we may want to modify templates without having to rebuild and deploy. To achieve this, we can put the templates on the filesystem instead.

It might be useful to configure this path in application.properties so that we can modify it for each deployment. This property can be accessed using the @Value annotation:

@Value("${spring.mail.templates.path}")
private String mailTemplatesPath;

We then pass this value to a FileTemplateResolver, in place of the ClassLoaderTemplateResolver in our thymeleafTemplateResolver method:

FileTemplateResolver templateResolver = new FileTemplateResolver();
templateResolver.setPrefix(mailTemplatesPath);

3.3. Configure the Thymeleaf Engine

The final step is to create the factory method for the Thymeleaf engine. We’ll need to tell the engine which TemplateResolver we’ve chosen, which we can inject via a parameter to the bean factory method:

@Bean
public SpringTemplateEngine thymeleafTemplateEngine(ITemplateResolver templateResolver) {
    SpringTemplateEngine templateEngine = new SpringTemplateEngine();
    templateEngine.setTemplateResolver(templateResolver);
    templateEngine.setTemplateEngineMessageSource(emailMessageSource());
    return templateEngine;
}

Here, the resolver we created earlier is injected automatically by Spring into the template engine factory method.

4. FreeMarker Configuration

In the same fashion as Thymeleaf, in the EmailConfiguration class, we’ll configure the template resolver for FreeMarker templates (.ftl):
And this time, the location of the templates will be configured in the FreeMarkerConfigurer bean.

4.1. Templates in the Classpath

Here, we have the same options as for Thymeleaf. Let’s configure templates as classpath resources:

@Bean 
public FreeMarkerConfigurer freemarkerClassLoaderConfig() {
    Configuration configuration = new Configuration(Configuration.VERSION_2_3_27);
    TemplateLoader templateLoader = new ClassTemplateLoader(this.getClass(), "/mail-templates");
    configuration.setTemplateLoader(templateLoader);
    FreeMarkerConfigurer freeMarkerConfigurer = new FreeMarkerConfigurer();
    freeMarkerConfigurer.setConfiguration(configuration);
    return freeMarkerConfigurer; 
}

4.2. Templates on the File System

To configure templates from another path in the filesystem, we’ll need to replace the TemplateLoader instance:

TemplateLoader templateLoader = new FileTemplateLoader(new File(mailTemplatesPath));

5. Localization with Thymeleaf and FreeMarker

In order to manage translations with Thymeleaf, we can specify a MessageSource instance to the engine:

@Bean
public ResourceBundleMessageSource emailMessageSource() {
    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
    messageSource.setBasename("mailMessages");
    return messageSource;
}
@Bean
public SpringTemplateEngine thymeleafTemplateEngine() {
   ...
   templateEngine.setTemplateEngineMessageSource(emailMessageSource());
   ...
}

Then, we’d create resource bundles for each locale we support:

src/main/resources/mailMessages_xx_YY.properties

As FreeMarker proposes localization by duplicating the templates, we don’t have to configure the message source there.

6. Thymeleaf Templates Content

Next, let’s have a look at the template-thymeleaf.html file:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  </head>
  <body>
    <p th:text="#{greetings(${recipientName})}"></p>
    <p th:text="${text}"></p>
    <p th:text="#{regards}"></p>
    <p>
      <em th:text="#{signature(${senderName})}"></em> <br />
    </p>
  </body>
</html>

As can be seen, we’ve used Thymeleaf notation, that is, ${…} for variables and #{…} for localized strings.

As the template engine is correctly configured, it’s very simple to use it: We’ll just create a Context object that contains template variables (passed as a Map here).

Then, we’ll pass it to the process method along with the template name:

@Autowired
private SpringTemplateEngine thymeleafTemplateEngine;

@Override
public void sendMessageUsingThymeleafTemplate(
    String to, String subject, Map<String, Object> templateModel)
        throws MessagingException {
                
    Context thymeleafContext = new Context();
    thymeleafContext.setVariables(templateModel);
    String htmlBody = thymeleafTemplateEngine.process("template-thymeleaf.html", thymeleafContext);
    
    sendHtmlMessage(to, subject, htmlBody);
}

Now, let’s see how to do the same thing with FreeMarker.

7. FreeMarker Templates Content

As can be seen, FreeMarker’s syntax is more simple, but again it does not manage localized strings. So, here’s the English version:

<!DOCTYPE html>
<html>
    <head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
      <p>Hi ${recipientName}</p>
      <p>${text}</p>
      <p>Regards,</p>
      <p>
        <em>${senderName} at Baeldung</em> <br />
      </p>
    </body>
</html>

Then, we should use the FreeMarkerConfigurer* class to get the template file, and finally, FreeMarkerTemplateUtils to inject data from our *Map:

@Autowired
private FreeMarkerConfigurer freemarkerConfigurer;

@Override
public void sendMessageUsingFreemarkerTemplate(
    String to, String subject, Map<String, Object> templateModel)
        throws IOException, TemplateException, MessagingException {
        
    Template freemarkerTemplate = freemarkerConfigurer.getConfiguration()
      .getTemplate("template-freemarker.ftl");
    String htmlBody = FreeMarkerTemplateUtils.processTemplateIntoString(freemarkerTemplate, templateModel);

    sendHtmlMessage(to, subject, htmlBody);
}

To go further, we’ll see how to add a logo to our email signature.

8. Emails With Embedded Images

Since it’s very common to include images in an HTML email, we’ll see how to do this using a CID attachment.

The first change concerns the sendHtmlMessage method. We have to set MimeMessageHelper as multi-part by passing true to the second argument of the constructor:

MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");

Then, we have to get the image file as a resource. We can use the @Value annotation for this:

@Value("classpath:/mail-logo.png")
Resource resourceFile;

Notice that the mail-logo.png file is in the src/main/resources directory.

Back to the sendHtmlMessage method, we’ll add resourceFile as an inline attachment, to be able to reference it with CID:

helper.addInline("attachment.png", resourceFile);

Finally, the image has to be referenced from both Thymeleaf and FreeMarker emails using CID notation:

<img src="cid:attachment.png" />

9. Conclusion

In this article, we’ve seen how to send Thymeleaf and FreeMarker emails, including rich HTML content.

To conclude, most of the work is related to Spring; therefore, the use of one or the other is quite similar for a simple need such as sending emails.

As always, the full source code of the examples can be found over on GitHub.