Sending HTML mail with Spring Boot and Thymeleaf

Sending an e-mail from the backend application part is a quite common use case in the world of enterprise applications. Although HTML content isn’t standardized message format, numerous mail clients support at least a subset of the markup language. In this post you will learn how to send an e-mail using standard Spring Boot modules and prepare HTML content for a message using Thymeleaf template engine.

Advertisement

1.  Plain text mail

To warm you up, let’s create and send a simple text message over SMTP.

1.1. Mail starter dependencies

It isn’t surprising that Spring Boot already provides the starter project, which defines all necessary library modules. Simply add the following artifact to your pom.xml.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

1.2. Properties configuration

Spring will automatically register a default mail sender service if required dependencies (included in the starter) are available on the class path and the spring.mail.host property is defined. All we need to finish configuration is to add this property to our application.properties files.

spring.mail.host=localhost

The default mail sender can be customized using an array of configuration properties that can be easily adjusted. You should assign their values appropriate for your SMTP server.

spring.mail.port=25 # SMTP server port
spring.mail.username= # Login used for authentication
spring.mail.password= # Password for the given login
spring.mail.protocol=smtp
spring.mail.defaultEncoding=UTF-8 # Default message encoding

1.3. Mail sending service

To make use of the preconfigured JavaMailSender  implementation, we should inject its instance into our client class, which will  be responsible for creation and shipment of a new mail message.

@Service
public class MailClient {

    private JavaMailSender mailSender;

    @Autowired
    public MailService(JavaMailSender mailSender) {
        this.mailSender = mailSender;
    }

    public void prepareAndSend(String recipient, String message) {
        //TODO implement
    }

}

1.4. Compose and post

The following sample presents a basic implementation of our client. Familiarize yourself with the listing and read the description below if you need a comment.

public void prepareAndSend(String recipient, String message) {
    MimeMessagePreparator messagePreparator = mimeMessage -> {
        MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage);
        messageHelper.setFrom("sample@dolszewski.com");
        messageHelper.setTo(recipient);
        messageHelper.setSubject("Sample mail subject");
        messageHelper.setText(message);
    };
    try {
        mailSender.send(messagePreparator);
    } catch (MailException e) {
        // runtime exception; compiler will not force you to handle it
    }
}

The send() method is overloaded and accepts several types of parameters:

  • SimpleMailMessage – As the name suggests this is a basic model of a mail message so only the most common properties can be assigned. It doesn’t allow modifying message headers and transports only plain text content.
  • MimeMessage – Complex mail message model provided by javax.mail library.
  • MimeMessagePreparator – An interface which provides a builder template method for MimeMessage and alleviates exception handling while creating an instance of the type. The official documentation (but also common sense :)) suggests MimeMessagePreparator as the preferred type for mail message building.

The MimeMessageHelper class is a decorator for MimeMessage that provides more developer friendly interface and adds input validation for many properties of the class. You don’t have to use it, but you definitely won’t regret trying.

Note, that the send() method throws MailException which is a subclass of RuntimeException. In case of failure in message delivery, most likely you would like to repeat the send action or at least handle the unpleasant situation with some more sophisticated solution like … logging the error message with the corresponding stack trace.

2.  Manual testing

If you want to verify the functionality of the client you need a running SMTP server on your local machine that will handle your requests. If you don’t have your favorite yet, you can use one of the following:

  • FakeSMTP – A simple server written in Java. Supported by any operating system with Java 1.6 or newer installed.
  • smtp4dev – A server with a plain and user friendly interface. For Windows only.
  • Papercut – Another simple server designed for Windows.

3.  Mail integration testing

You may also be wondering how to write a fully automated test to verify the client functionality. Just like in case of manual testing, you need to start SMPT server before running the client. In this sample, we’re going to use GreenMail, which is quite fast and integrates well with JUnit.

3.1. Test dependencies

GreenMail is available in the central Maven repository. Just add the dependency to your pom.xml.

<dependency>
    <groupId>com.icegreen</groupId>
    <artifactId>greenmail</artifactId>
    <version>1.5.0</version>
    <scope>test</scope>
</dependency>

3.2. Test template with SMTP server

We can now create our integration test class, which will start Spring application and exercise the mail client. But before we write the actual test, we need to make sure the SMTP server will be started as a part of the test fixture and properly stopped when the test is finished.

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(Application.class)
public class MailClientTest {

    private GreenMail smtpServer;

    @Before
    public void setUp() throws Exception {
        smtpServer = new GreenMail(new ServerSetup(25, null, "smtp"));
        smtpServer.start();
    }

    @After
    public void tearDown() throws Exception {
        smtpServer.stop();
    }
    
}

3.3. Exercising mail client

First, we need to inject the instance of the mail client into the test class. After that, we can finally execute the client and verify if a desired e-mail message is received by the GreenMail server instance.

@Autowired
private MailClient mailClient;

@Test
public void shouldSendMail() throws Exception {
    //given
    String recipient = "name@dolszewski.com";
    String message = "Test message content";
    //when
    mailClient.prepareAndSend(recipient, message);
    //then
    assertReceivedMessageContains(message);
}

private void assertReceivedMessageContains(String expected) throws IOException, MessagingException {
    MimeMessage[] receivedMessages = smtpServer.getReceivedMessages();
    assertEquals(1, receivedMessages.length);
    String content = (String) receivedMessages[0].getContent();
    assertTrue(content.contains(expected));
}

4.  HTML mail content

An HTML message payload can be of course built manually, but it isn’t a very practical approach and has many disadvantages that probably don’t have to be explained. In this part we’re going to focus on separation of view template generation and sending logic.

4.1 Thymeleaf dependency

If your project doesn’t contain the Thymeleaf starter, you should begin with adding the dependency to the pom.xml file. Spring Boot will automatically prepare the engine using its default setup.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

4.2. Mail HTML template

The default configuration of Thymeleaf expects that all HTML files are placed under resources/templates directory and ends with the .html extension. Let’s create a simple file called mailTemplate.html that we’re going to send using the created mail client class.

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head></head>
<body>
    <span th:text="${message}"></span>
</body>
</html>

The template contains almost nothing except for a placeholder for a message that can be passed as an argument during the generation process.

4.3. Template processing

Create another service class which is going to be responsible for preparing mail content by combining the written template and external model, which in our case is a simple text message.

@Service
public class MailContentBuilder {

    private TemplateEngine templateEngine;

    @Autowired
    public MailContentBuilder(TemplateEngine templateEngine) {
        this.templateEngine = templateEngine;
    }

    public String build(String message) {
        Context context = new Context();
        context.setVariable("message", message);
        return templateEngine.process("mailTemplate", context);
    }

}

The instance of the TemplateEngine class is provided by Spring Boot Thymeleaf auto configuration. All we need to do is to call the process() method which expects the name of the template that we want to use and the context object that acts as a container for our model.

4.4. Changing message preparation

Inject newly created MailContentBuilder into the MailService class. We need one small adjustment inside the prepareAndSend() method to make use of the builder and set the generate content as the mime message payload. We also use an overloaded variant of the setText() method to set the Content-Type header as text/html instead of default text/plain.

public void prepareAndSend(String recipient, String message) {
    MimeMessagePreparator messagePreparator = mimeMessage -> {
        // ... 
        String content = mailContentBuilder.build(message);
        messageHelper.setText(content, true);
    };
    // ...
}

4.5. Test update

The last thing that requires update is our test, more precisely, the expected content of the received message. Just make a small change in the verification logic, run the test, and check the results.

@Test
public void shouldSendMail() throws Exception {
    //given
    String recipient = "name@dolszewski.com";
    String message = "Test message content";
    //when
    mailService.prepareAndSend(recipient, message);
    //then
    String content = "<span>" + message + "</span>";
    assertReceivedMessageContains(content);
}

Summary

Once again Spring Boot presents as an easy to use and highly productive framework. The configuration step is almost completely hidden from developers so they can focus on implementing actual use cases rather than common, repeatable setup steps. The whole code of created application can be found in the repository. If you have any questions or something is unclear, I encourage you to post a comment or contact me via e-mail that can be found in the contact section.

Facebooktwittergoogle_plusredditlinkedinmail
Advertisement