Sunday, September 3, 2017

Spring Boot - Example Email with Thymeleaf HTML Templates

I have a Spring Boot backend application that needed to send emails.

Java application email libraries include: Java mail native, Spring, and apache email (and others).  Lots of articles say apache email API is more intuitive, but others say if you are already using a Java/Spring application, then to go ahead and use the Spring one.   I decided to try the Spring Boot one first, since we have a Java/Spring application.

The Spring boot library satisfied the three criteria below, and seems to work great on my local environment:
  1. the ability to send simple text email 
  2. the ability to send HTML email (support for HTML emails already had the Thymeleaf HTML templating library provided with Spring Boot)
  3. the ability to attach a file(s) to the email
The application is using the "Starters" spring boot jars (i.e. spring-boot-starter-web, spring-boot-starter-data-jpa, spring-boot-starter-security, etc), so I just needed to make the application aware of the spring-boot-starter-thymeleaf jar file for the application to use it.

Environment:
Windows 7
Oracle Java 1.8.0_66
Spring Boot 1.3.1.RELEASE
Sprint Boot 1.3.1.RELEASE spring-boot-starter-thymeleaf

The example below supports criteria number 2 to send HTML emails using Thymeleaf email templates.  I've also included unescaped HTML, passing multiple variables to HTML template, and inline image in the example below as well.


src/main/resources/templates/logo_cherryshoe.png: cherryshoe logo to put as inline image in HTML email

src/main/resources/application.properties:  add applicable properties
# Email
spring.mail.host=<email smtp host>
spring.mail.port=<email smtp port>
spring.mail.defaultEncoding=UTF-8
spring.mail.properties.mail.smtp.connecttimeout=5000
spring.mail.properties.mail.smtp.timeout=3000
spring.mail.properties.mail.smtp.writetimeout=5000
cherryshoe.mail.defaultFrom=<email>
# must match thyme html template name under templates resource folder
cherryshoe.mail.defaultMailTemplate=email

src/main/resources/templates/email.html: Thyme HTML Email template, used as the default email template
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head><title>CherryShoe Html Email Example</title>
<style>
body {
    background-color: white;
}

/* White space is preserved with \n\n\n\n and css class a, span, tr { white-space: pre; } */
a, span, tr { white-space: pre; }

body {
 color: black;
 font-family: 'Arial', sans-serif;
 font-weight: normal;
 font-style: normal;
 font-size: x-small;
}

table {
    border-collapse: collapse;
    width: 100%;
}

table, th, td {
    border: 1px solid black;
}

.ten {
 width: 10%;
}
.forty {
 width: 40%;
}

.twenty-five {
 width: 25%;
}

th {
    background-color: #C6DEFF;
    color: black;
    vertical-align: middle;
}

th, td {
    padding: 1px;
    text-align: left;
    vertical-align: middle;
}

.cherryshoe-font-bold {
 color: black;
 font-family: 'Arial', sans-serif;
 font-weight: bold;
 font-style: normal;
 font-size: x-small;
}

</style>
</head>
<body>
Hi <span th:text="${name}"></span>, <br></br><br></br>

<!-- Need br formatted this way for SAX Parser to parse with no errors -->
<br></br><br></br>
<span class="cherryshoe-font-bold">Example Table Data generated from backend service</span>
<table>
<tr>
<th class="twenty-five">Column 1</th>
<th class="ten">Column 2</th>
<th class="twenty-five">Column 3</th>
<th class="forty">Column 4</th>
</tr>
<!-- Use th:utext to show unescaped html -->
<span th:utext="${table1}"></span>
</table>

<br></br><br></br>
Thank you
<br></br>
<img src="logo_cherryshoe.png" th:src="'cid:' + ${imageResourceName}" alt="CherryShoe Logo"/>

</body>
</html>


src/main/java/com/cherryshoe.service.email.SendEmailService.java
package com.cherryshoe.service.email;

import java.io.IOException;

public interface SendEmailService {

 /**
  * Send html emails.
  *
  * @throws IOException
  */
 public void sendHtmlEmail() throws IOException;
}


src/main/java/com/cherryshoe.service.email.SendEmailServiceSpringBootImpl
package com.cherryshoe.service.email;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.InputStreamSource;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

@Service
/*
 * White space is preserved in text email with \n. It is also preserved in HTML
 * email with the css in the html template
 */
public class SendEmailServiceSpringBootImpl implements SendEmailService {

 // JavaMailSenderImpl is thread safe after it's constructed
 @Autowired
 private JavaMailSender mailSender;

 // needed for HTML email templating
 @Autowired
 private TemplateEngine templateEngine;

 @Value("${cherryshoe.mail.defaultFrom}")
 private String defaultFrom;

 // The default configuration of Thymeleaf expects that all HTML files are
 // placed under resources/templates directory and ends with the .html
 // extension.
 // Can also externalize the templates/*.html files
 @Value("${cherryshoe.mail.defaultMailTemplate}")
 private String defaultMailTemplate;

 private Logger log = LoggerFactory.getLogger(getClass());

 private final ThreadLocal<DateFormat> threadLocalDf = new ThreadLocal<DateFormat>() {
  @Override
  protected DateFormat initialValue() {
   return new SimpleDateFormat("MM/dd/yyyy");
  }
 };

 @Override
 public void sendHtmlEmail() throws IOException {
  MimeMessage mail = mailSender.createMimeMessage();
  InputStream imageIs = null;
  try {

   // set multiple context variables key/value pairs for email template
   String templateMailBodyNameKey = "name";
   String templateMailBodyNameVal = "[Name]";
   String templateMailBodyTable1Key = "table1";
   StringBuffer templateMailBodyTable1Val = new StringBuffer();
   String templateMailBodyImageKey = "imageResourceName";
   String templateMailBodyImageVal = "logo_cherryshoe.png";

   // table 1 get data
   for (int i = 0; i < 5; i++) {
    templateMailBodyTable1Val.append("<tr><td class=\"twenty-five\">Column 1 Data - ");
    templateMailBodyTable1Val.append(i);
    templateMailBodyTable1Val.append("</td>");
    templateMailBodyTable1Val.append("<td class=\"ten\">Column 2 Data -  ");
    templateMailBodyTable1Val.append(i);
    templateMailBodyTable1Val.append("</td>");
    templateMailBodyTable1Val.append("<td class=\"twenty-five\">Column 3 Data - ");
    templateMailBodyTable1Val.append(i);
    templateMailBodyTable1Val.append("</td>");
    templateMailBodyTable1Val.append("<td class=\"forty\">Column 4 Data - ");
    templateMailBodyTable1Val.append(i);
    templateMailBodyTable1Val.append("</td></tr>");
   }

   Map<String, Object> variables = new HashMap<String, Object>();
   variables.put(templateMailBodyNameKey, templateMailBodyNameVal);
   variables.put(templateMailBodyTable1Key, templateMailBodyTable1Val.toString());
   variables.put(templateMailBodyImageKey, templateMailBodyImageVal);

   Context context = new Context();
   variables.forEach((name, value) -> context.setVariable(name, value));

   String content = templateEngine.process(defaultMailTemplate, context);

   MimeMessageHelper helper = new MimeMessageHelper(mail, true);
   helper.setTo(defaultFrom);
   helper.setReplyTo(defaultFrom);
   helper.setFrom(defaultFrom);
   helper.setSubject("CherryShoe Example Html Email - " + threadLocalDf.get().format(new Date()));
   helper.setText(content, true); // make html email

   // Add the inline image, must go after setText. Referenced from the
   // mail template as "cid:${imageResourceName}"
   imageIs = this.getClass().getClassLoader().getResourceAsStream("templates/" + templateMailBodyImageVal);
   byte[] imageByteArray = IOUtils.toByteArray(imageIs);

   final InputStreamSource imageSource = new ByteArrayResource(imageByteArray);
   helper.addInline(templateMailBodyImageVal, imageSource, "image/png");

   mailSender.send(mail);
  } catch (MessagingException e) {
   log.warn(e.getMessage());
  } finally {
   if (imageIs != null)
    imageIs.close();
  }

 }
}

src/main/test/com/cherryshoe/email/service.SendEmailServiceITTest
package com.cherryshoe.service.email;

import javax.inject.Named;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.transaction.annotation.Transactional;
import com.cherryshoe.CherryShoeAppApplication;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = CherryShoeAppApplication.class)
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class })
@Transactional
@WebAppConfiguration
public class SendEmailServiceITTest {

 @Autowired
 @Named("sendEmailServiceSpringBootImpl")
 private SendEmailService emailServiceSpringBoot;

 @Test
 public void sendHtmlEmailTest() throws Exception {
  try {
   emailServiceSpringBoot.sendHtmlEmail();
  } catch (Exception e) {
   e.printStackTrace();
  }
 }
}


15 comments:

  1. Thanks for sharing! You know, Mozilla Thunderbird is one of the major alternatives to Microsoft’s email applications and PIM tools. Completely free and open source, it is available to users on various platforms, including Windows, Mac OS X and Linux. Transfer emails from Thunderbird to Outlook software url is an efficient and extremely fast data migration solution that will help you transfer all types of matching data from Thunderbird to Outlook in no time.

    ReplyDelete
  2. Thanks for this post, really useful. There's one more tool to find contact information: improver.io. It helps me to find personal emails and phone numbers.find email chrome extension

    ReplyDelete
  3. Some template providers like ‘buytemplates.net’ offer template related services irrespective of where you actually purchased the template from.https://www.teddyway.hu

    ReplyDelete
  4. Thanks for sharing such a useful and amazing blog, Nice to read this.I thank you for this.Explore 24x7ServerSupport to get the most comprehensive cloud computing services.

    ReplyDelete
  5. Awesome and interesting article. Great things you've always shared with us. Thanks. Just continue composing this kind of post. hotmail sign in

    ReplyDelete
  6. So, if anyone misses the email sign up a pop-up then they can easily get back to your offer. So, create some offer banners or call-to-action & email sign up on your homepage, blog.
    Email List for Marketing

    ReplyDelete
  7. The article looks magnificent, but it would be beneficial if you can share more about the suchlike subjects in the future. Keep posting. sign in to Hotmail

    ReplyDelete
  8. Its a great pleasure reading your post.Its full of information I am looking for and I love to post a comment that "The content of your post is awesome" Great work. ActiveCampaign limits

    ReplyDelete
  9. Great Information sharing .. I am very happy to read this article .. thanks for giving us go through info.Fantastic nice. I appreciate this post.
    email hunter

    ReplyDelete
  10. Thank you for some other informative blog. Where else could I get that type of information written in such an ideal means? I have a mission that I’m just now working on, and I have been at the look out for such information. email lookup

    ReplyDelete
  11. It is the kind of information I have been trying to find. Thank you for writing this information. It has proved utmost beneficial for me. visit this site

    ReplyDelete
  12. If you are looking for a professional developers of magneto, then this blog is for you. You can opt for quality Magento Development Services USA for a wide range of information.

    ReplyDelete
  13. Very informative article that you have shared here about the dedicated servers. Your article is very useful for us and I couldn't find any knowledge on this matter prior to. You can opt for quality SEO marketing services Minneapolis for a wide range of information.

    ReplyDelete
  14. Backing up critical data is done quickly and effectively because your company can effortlessly create a replication site. You can buy office 2019 key UK at a great price. Most enterprise virtualization platforms contain software that helps automate the failover during a disaster.

    ReplyDelete

I appreciate your time in leaving a comment!