Categories: spring

Spring 4 Email using Velocity,Freemarker Template library

Spring provides template libraries which can be used to prepare email templates. Although we can inject the html directly into our email-message, as we saw in previous tutorial, it’s better to keep business logic apart from presentation. Email templates are also the preferred way in Enterprise applications mail sending. Templates can be created separately from the application that will use them.

In this post, we will see two commonly used template libraries : FreeMarker and Velocity. As of Spring 4.3.x, Velocity templates are deprecated, and Freemarker is the preferred choice to go for. Just for demonstration, we have included both, but you should be using only one of them, preferably Freemarker. In this post, we are keeping our templates into src/main/resources and accessing them using corresponding template library. Let’s get going.


Project structure

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>

 <groupId>com.websystique.spring</groupId>
 <artifactId>SpringEmailWtihTemplatesExample</artifactId>
 <version>1.0.0</version>
 <packaging>jar</packaging>

 <name>SpringEmailWtihTemplatesExample</name>

 <properties>
  <springframework.version>4.3.0.RELEASE</springframework.version>
 </properties>

 <dependencies>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-core</artifactId>
   <version>${springframework.version}</version>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context</artifactId>
   <version>${springframework.version}</version>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context-support</artifactId>
   <version>${springframework.version}</version>
  </dependency>
  <dependency>
   <groupId>javax.mail</groupId>
   <artifactId>javax.mail-api</artifactId>
   <version>1.5.5</version>
  </dependency>
  <dependency>
   <groupId>javax.mail</groupId>
   <artifactId>mail</artifactId>
   <version>1.4.7</version>
  </dependency>
  <dependency>
   <groupId>org.apache.velocity</groupId>
   <artifactId>velocity</artifactId>
   <version>1.7</version>
  </dependency>
  <dependency>
   <groupId>org.freemarker</groupId>
   <artifactId>freemarker</artifactId>
   <version>2.3.23</version>
  </dependency>
 </dependencies>

 <build>
  <plugins>
   <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.2</version>
    <configuration>
     <source>1.7</source>
     <target>1.7</target>
    </configuration>
   </plugin>
  </plugins>
 </build>
</project>

Configuration class

We will configure a JavaMailSender , using host settings. Additionally, we will configure Bean used for setting up Velocity and Freemarker templating.

package com.websystique.spring.configuration;

import java.io.IOException;
import java.util.Properties;

import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.exception.VelocityException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.ui.freemarker.FreeMarkerConfigurationFactoryBean;
import org.springframework.ui.velocity.VelocityEngineFactory;

@SuppressWarnings("deprecation")
@Configuration
@ComponentScan(basePackages = "com.websystique.spring")
public class AppConfig {

 // Put Other Application configuration here.

 @Bean
 public JavaMailSender getMailSender() {
  JavaMailSenderImpl mailSender = new JavaMailSenderImpl();

  // Using gmail.
  mailSender.setHost("smtp.gmail.com");
  mailSender.setPort(587);
  mailSender.setUsername("Your-gmail-id");
  mailSender.setPassword("Your-gmail-password");

  Properties javaMailProperties = new Properties();
  javaMailProperties.put("mail.smtp.starttls.enable", "true");
  javaMailProperties.put("mail.smtp.auth", "true");
  javaMailProperties.put("mail.transport.protocol", "smtp");
  javaMailProperties.put("mail.debug", "true");

  mailSender.setJavaMailProperties(javaMailProperties);
  return mailSender;
 }

 /*
  * FreeMarker configuration.
  */ @Bean
 public FreeMarkerConfigurationFactoryBean getFreeMarkerConfiguration() {
  FreeMarkerConfigurationFactoryBean bean = new FreeMarkerConfigurationFactoryBean();
  bean.setTemplateLoaderPath("/fmtemplates/");
  return bean;
 }

 /*
  * Velocity configuration.
  */ @Bean
 public VelocityEngine getVelocityEngine() throws VelocityException, IOException {
  VelocityEngineFactory factory = new VelocityEngineFactory();
  Properties props = new Properties();
  props.put("resource.loader", "class");
  props.put("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");

  factory.setVelocityProperties(props);
  return factory.createVelocityEngine();
 }

}

Please do not forget to update above configuration with your [email] credentials in order to run this example.

Mail Sender service

Service which prepares the MimeMessage using MimeMessagePreparator and MimiMessageHelper & eventually sends the email using JavaMailSender.

package com.websystique.spring.service;

import java.util.HashMap;
import java.util.Map;

import javax.mail.internet.MimeMessage;

import org.apache.velocity.app.VelocityEngine;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.mail.MailException;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.mail.javamail.MimeMessagePreparator;
import org.springframework.stereotype.Service;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.ui.velocity.VelocityEngineUtils;

import com.websystique.spring.model.ProductOrder;

import freemarker.template.Configuration;

@SuppressWarnings("deprecation")
@Service("mailService")
public class MailServiceImpl implements MailService{

 @Autowired
 JavaMailSender mailSender;
 
 @Autowired
 VelocityEngine velocityEngine;
 
 @Autowired
 Configuration freemarkerConfiguration;
 

 @Override
 public void sendEmail(Object object) {

  ProductOrder order = (ProductOrder)object;
  
  MimeMessagePreparator preparator = getMessagePreparator(order);
  
  try {
            mailSender.send(preparator);
            System.out.println("Message has been sent.............................");
        }
        catch (MailException ex) {
            System.err.println(ex.getMessage());
        }
 }

 private MimeMessagePreparator getMessagePreparator(final ProductOrder order){
  
  MimeMessagePreparator preparator = new MimeMessagePreparator() {

   public void prepare(MimeMessage mimeMessage) throws Exception {
             MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
 
                helper.setSubject("Your order on Demoapp with Templates");
                helper.setFrom("customerserivces@yourshop.com");
                helper.setTo(order.getCustomerInfo().getEmail());
     
                Map<String, Object> model = new HashMap<String, Object>();
                model.put("order", order);
                
             String text = geFreeMarkerTemplateContent(model);//Use Freemarker or Velocity
                System.out.println("Template content : "+text);

                // use the true flag to indicate you need a multipart message
             helper.setText(text, true);

             //Additionally, let's add a resource as an attachment as well.
             helper.addAttachment("cutie.png", new ClassPathResource("linux-icon.png"));

            }
        };
        return preparator;
 }
 
 
 public String geVelocityTemplateContent(Map<String, Object> model){
  StringBuffer content = new StringBuffer();
  try{
   content.append(VelocityEngineUtils.mergeTemplateIntoString(velocityEngine, "/vmtemplates/velocity_mailTemplate.vm", model));
   return content.toString();
  }catch(Exception e){
   System.out.println("Exception occured while processing velocity template:"+e.getMessage());
  }
       return "";
 }


 public String geFreeMarkerTemplateContent(Map<String, Object> model){
  StringBuffer content = new StringBuffer();
  try{
         content.append(FreeMarkerTemplateUtils.processTemplateIntoString( 
           freemarkerConfiguration.getTemplate("fm_mailTemplate.txt"),model));
         return content.toString();
  }catch(Exception e){
   System.out.println("Exception occured while processing fmtemplate:"+e.getMessage());
  }
       return "";
 }
}

During mail preparation, we called the template libraries, providing the model data, and received the evaluated HTML which can be uses as message body.

Main class to run this example

package com.websystique.spring.configuration;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;

import com.websystique.spring.model.CustomerInfo;
import com.websystique.spring.model.ProductOrder;
import com.websystique.spring.service.OrderService;

public class SampleEmailApplication {

 public static void main(String[] args) {
  AbstractApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

  OrderService orderService = (OrderService) context.getBean("orderService");
  orderService.sendOrderConfirmation(getDummyOrder());
  ((AbstractApplicationContext) context).close();
 }
 
 public static ProductOrder getDummyOrder(){
  ProductOrder order = new ProductOrder();
  order.setOrderId("1111");
  order.setProductName("Thinkpad T510");
  order.setStatus("confirmed");
  
  CustomerInfo customerInfo = new CustomerInfo();
  customerInfo.setName("Websystique Admin");
  customerInfo.setAddress("WallStreet");
  customerInfo.setEmail("websystique@gmail.com");
  order.setCustomerInfo(customerInfo);
  return order;
 }

}

filler classes

package com.websystique.spring.service;

public interface MailService {

 public void sendEmail(final Object object);
}
package com.websystique.spring.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.websystique.spring.model.ProductOrder;

@Service("orderService")
public class OrderServiceImpl implements OrderService{

 @Autowired
 MailService mailService;

 @Override
 public void sendOrderConfirmation(ProductOrder productOrder) {
  mailService.sendEmail(productOrder);
 }
 
}
package com.websystique.spring.service;

import com.websystique.spring.model.ProductOrder;

public interface OrderService {

 public void sendOrderConfirmation(ProductOrder productOrder);
 
}
package com.websystique.spring.model;


public class ProductOrder {
 
 private String orderId;
 
 private String productName;

 private String status;
 
 private CustomerInfo customerInfo;
 
 public String getOrderId() {
  return orderId;
 }

 public void setOrderId(String orderId) {
  this.orderId = orderId;
 }

 public String getProductName() {
  return productName;
 }

 public void setProductName(String productName) {
  this.productName = productName;
 }

 public String getStatus() {
  return status;
 }

 public void setStatus(String status) {
  this.status = status;
 }

 public CustomerInfo getCustomerInfo() {
  return customerInfo;
 }

 public void setCustomerInfo(CustomerInfo customerInfo) {
  this.customerInfo = customerInfo;
 }

 @Override
 public String toString() {
  return "ProductOrder [orderId=" + orderId + ", productName=" + productName + ", status=" + status
    + ", customerInfo=" + customerInfo + "]";
 }

 
}
package com.websystique.spring.model;

public class CustomerInfo {

 private String name;
 
 private String address;
 
 private String email;

 public String getName() {
  return name;
 }

 public void setName(String name) {
  this.name = name;
 }

 public String getAddress() {
  return address;
 }

 public void setAddress(String address) {
  this.address = address;
 }

 public String getEmail() {
  return email;
 }

 public void setEmail(String email) {
  this.email = email;
 }

 @Override
 public String toString() {
  return "CustomerInfo [name=" + name + ", address=" + address + ", email=" + email + "]";
 }
 
}

Freemarker Template

<html>

    <body>
        <h3>Dear ${order.customerInfo.name}, thank you for placing order.</h3>

        <div>
            Your order id is : ${order.orderId}.
        </div>
        <span>Sent using FreeMarker Template</span>
    </body>
</html>

Velocity Template

<html>
    <body>
        <h3>Dear ${order.customerInfo.name}, thank you for placing order.</h3>

        <div>
            Your order id is : ${order.orderId}.
        </div>
        <span>Sent using Velocity Template</span>
    </body>
</html>

Build & Execute

Run the mail program, and check your [customer] mailbox. You should see a new message.

Open it.


As you can see , whole message is pure HTML, along with an attachment. Finally, check the logs:

Important: In case you are getting problems while connecting to Gmail, check first if you have two step security enabled in your gmail. Additionally, check if you have received an email from provider[gmail] saying “Sign-in attempt prevented”. If yes, there will be instruction in that email on how to allow/disallow less secured apps to access your mail provider [Turn on/off access to less secured apps].
Jul 06, 2016 1:22:40 AM org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@73c94b51: startup date [Wed Jul 06 01:22:40 CEST 2016]; root of context hierarchy
DEBUG: JavaMail version 1.5.5
DEBUG: successfully loaded resource: /META-INF/javamail.default.providers
DEBUG: Tables of loaded providers
DEBUG: Providers Listed By Class Name: {com.sun.mail.smtp.SMTPSSLTransport=javax.mail.Provider[TRANSPORT,smtps,com.sun.mail.smtp.SMTPSSLTransport,Oracle], com.sun.mail.smtp.SMTPTransport=javax.mail.Provider[TRANSPORT,smtp,com.sun.mail.smtp.SMTPTransport,Oracle], com.sun.mail.imap.IMAPSSLStore=javax.mail.Provider[STORE,imaps,com.sun.mail.imap.IMAPSSLStore,Oracle], com.sun.mail.pop3.POP3SSLStore=javax.mail.Provider[STORE,pop3s,com.sun.mail.pop3.POP3SSLStore,Oracle], com.sun.mail.imap.IMAPStore=javax.mail.Provider[STORE,imap,com.sun.mail.imap.IMAPStore,Oracle], com.sun.mail.pop3.POP3Store=javax.mail.Provider[STORE,pop3,com.sun.mail.pop3.POP3Store,Oracle]}
DEBUG: Providers Listed By Protocol: {imaps=javax.mail.Provider[STORE,imaps,com.sun.mail.imap.IMAPSSLStore,Oracle], imap=javax.mail.Provider[STORE,imap,com.sun.mail.imap.IMAPStore,Oracle], smtps=javax.mail.Provider[TRANSPORT,smtps,com.sun.mail.smtp.SMTPSSLTransport,Oracle], pop3=javax.mail.Provider[STORE,pop3,com.sun.mail.pop3.POP3Store,Oracle], pop3s=javax.mail.Provider[STORE,pop3s,com.sun.mail.pop3.POP3SSLStore,Oracle], smtp=javax.mail.Provider[TRANSPORT,smtp,com.sun.mail.smtp.SMTPTransport,Oracle]}
DEBUG: successfully loaded resource: /META-INF/javamail.default.address.map
Template content : <html>

    <body>
        <h3>Dear Websystique Admin, thank you for placing order.</h3>

        <div>
            Your order id is : 1111.
        </div>
        <span>Sent using FreeMarker Template</span>
    </body>
</html>
DEBUG: getProvider() returning javax.mail.Provider[TRANSPORT,smtp,com.sun.mail.smtp.SMTPTransport,Oracle]
DEBUG SMTP: useEhlo true, useAuth true
DEBUG SMTP: trying to connect to host "smtp.gmail.com", port 587, isSSL false
220 smtp.gmail.com ESMTP qg5sm119276wjc.13 - gsmtp
DEBUG SMTP: connected to host "smtp.gmail.com", port: 587

EHLO 192.168.1.2
250-smtp.gmail.com at your service, [81.243.87.175]
250-SIZE 35882577
250-8BITMIME
250-STARTTLS
250-ENHANCEDSTATUSCODES
250-PIPELINING
250 SMTPUTF8
DEBUG SMTP: Found extension "SIZE", arg "35882577"
DEBUG SMTP: Found extension "8BITMIME", arg ""
DEBUG SMTP: Found extension "STARTTLS", arg ""
DEBUG SMTP: Found extension "ENHANCEDSTATUSCODES", arg ""
DEBUG SMTP: Found extension "PIPELINING", arg ""
DEBUG SMTP: Found extension "SMTPUTF8", arg ""
STARTTLS
220 2.0.0 Ready to start TLS
EHLO 192.168.1.2
250-smtp.gmail.com at your service, [81.243.87.175]
250-SIZE 35882577
250-8BITMIME
250-AUTH LOGIN PLAIN XOAUTH2 PLAIN-CLIENTTOKEN OAUTHBEARER XOAUTH
250-ENHANCEDSTATUSCODES
250-PIPELINING
250 SMTPUTF8
DEBUG SMTP: Found extension "SIZE", arg "35882577"
DEBUG SMTP: Found extension "8BITMIME", arg ""
DEBUG SMTP: Found extension "AUTH", arg "LOGIN PLAIN XOAUTH2 PLAIN-CLIENTTOKEN OAUTHBEARER XOAUTH"
DEBUG SMTP: Found extension "ENHANCEDSTATUSCODES", arg ""
DEBUG SMTP: Found extension "PIPELINING", arg ""
DEBUG SMTP: Found extension "SMTPUTF8", arg ""
DEBUG SMTP: Attempt to authenticate using mechanisms: LOGIN PLAIN DIGEST-MD5 NTLM 
DEBUG SMTP: AUTH LOGIN command trace suppressed
DEBUG SMTP: AUTH LOGIN succeeded
DEBUG SMTP: use8bit false
MAIL FROM:<customerserivces@yourshop.com>
250 2.1.0 OK qg5sm119276wjc.13 - gsmtp
RCPT TO:<websystique@gmail.com>
250 2.1.5 OK qg5sm119276wjc.13 - gsmtp
DEBUG SMTP: Verified Addresses
DEBUG SMTP:   websystique@gmail.com
DATA
354  Go ahead qg5sm119276wjc.13 - gsmtp
Date: Wed, 6 Jul 2016 01:22:45 +0200 (CEST)
From: customerserivces@yourshop.com
To: websystique@gmail.com
Message-ID: <401198518.2.1467760965478@dragon>
Subject: Your order on Demoapp with Templates
MIME-Version: 1.0
Content-Type: multipart/mixed; 
 boundary="----=_Part_0_1960353164.1467760961698"

------=_Part_0_1960353164.1467760961698
Content-Type: multipart/related; 
 boundary="----=_Part_1_1010865894.1467760961715"

------=_Part_1_1010865894.1467760961715
Content-Type: text/html; charset=us-ascii
Content-Transfer-Encoding: 7bit

<html>

    <body>
        <h3>Dear Websystique Admin, thank you for placing order.</h3>

        <div>
            Your order id is : 1111.
        </div>
        <span>Sent using FreeMarker Template</span>
    </body>
</html>
------=_Part_1_1010865894.1467760961715--

------=_Part_0_1960353164.1467760961698
Content-Type: image/x-png; name=cutie.png
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=cutie.png

WXMAAABIAAAASABGyWs+AAAACXZwQWcAAAB4AAAAcADdnEtTAAAp60lEQVR42u19eXiV1bnv75v2
......
6DBuUAGcFdAUgrZUgEq8C5Qme8
------=_Part_0_1960353164.1467760961698--
.
250 2.0.0 OK 1467760966 qg5sm119276wjc.13 - gsmtp
QUIT
221 2.0.0 closing connection qg5sm119276wjc.13 - gsmtp
Message has been sent.............................
Jul 06, 2016 1:22:46 AM org.springframework.context.annotation.AnnotationConfigApplicationContext doClose
INFO: Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@73c94b51: startup date [Wed Jul 06 01:22:40 CEST 2016]; root of context hierarchy

That’s it. As we saw, it’s rather easy to use Spring’s email support in your own application. Feel free to comment, and suggest improvements.

Download Source Code


References

View Comments

  • I had to specify my file path like this to make this work:
    fmcf.setTemplateLoaderPath("classpath:/emailTemplates/");
    If your FreeMaker can not read template file, try this

    • Thanks Patryk.

      Including the classpath fix the error from not able to read the freemaker template.

      bean.setTemplateLoaderPath("classpath:/fmtemplates/");

Share
Published by

Recent Posts

Spring Boot + AngularJS + Spring Data + JPA CRUD App Example

In this post we will be developing a full-blown CRUD application using Spring Boot, AngularJS, Spring Data, JPA/Hibernate and MySQL,…

7 years ago

Spring Boot Rest API Example

Spring Boot complements Spring REST support by providing default dependencies/converters out of the box. Writing RESTful services in Spring Boot…

7 years ago

Spring Boot WAR deployment example

Being able to start the application as standalone jar is great, but sometimes it might not be possible to run…

7 years ago

Spring Boot Introduction + hello world example

Spring framework has taken the software development industry by storm. Dependency Injection, rock solid MVC framework, Transaction management, messaging support,…

7 years ago

Secure Spring REST API using OAuth2

Let's secure our Spring REST API using OAuth2 this time, a simple guide showing what is required to secure a…

8 years ago

AngularJS+Spring Security using Basic Authentication

This post shows how an AngularJS application can consume a REST API which is secured with Basic authentication using Spring…

8 years ago