Email Templates with Thymeleaf in Java: A Complete Guide

Thymeleaf is a modern server-side Java template engine that works perfectly for creating dynamic email templates. Its natural templating approach and Spring integration make it ideal for generating rich, personalized email content.


Why Thymeleaf for Email Templates?

Advantages:

  • Natural Templates: HTML files that display correctly in browsers and email clients
  • Spring Integration: Seamless integration with Spring Boot
  • Expression Language: Powerful Spring EL integration
  • Internationalization: Built-in i18n support
  • Fragments: Reusable template components

Setup and Dependencies

Maven Dependencies

<dependencies>
<!-- Spring Boot Starter Mail -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- Thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- JavaMail for HTML emails -->
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>1.6.2</version>
</dependency>
</dependencies>

Application Configuration

# application.yml
spring:
mail:
host: smtp.gmail.com
port: 587
username: [email protected]
password: your-app-password
properties:
mail:
smtp:
auth: true
starttls:
enable: true
thymeleaf:
prefix: classpath:/templates/emails/
suffix: .html
mode: HTML
encoding: UTF-8
cache: false # Disable cache for development

Basic Email Service Setup

Example 1: Basic Email Service Configuration

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
import org.thymeleaf.templateresolver.ITemplateResolver;
import java.util.Properties;
@Configuration
public class EmailConfig {
@Bean
public JavaMailSender javaMailSender() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost("smtp.gmail.com");
mailSender.setPort(587);
mailSender.setUsername("[email protected]");
mailSender.setPassword("your-app-password");
Properties props = mailSender.getJavaMailProperties();
props.put("mail.transport.protocol", "smtp");
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.debug", "true"); // Enable for development
return mailSender;
}
@Bean
public TemplateEngine emailTemplateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(htmlTemplateResolver());
return templateEngine;
}
private ITemplateResolver htmlTemplateResolver() {
ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();
templateResolver.setPrefix("/templates/emails/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode(TemplateMode.HTML);
templateResolver.setCharacterEncoding("UTF-8");
templateResolver.setCacheable(false);
return templateResolver;
}
}

Creating Email Templates

Example 2: Basic Welcome Email Template

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Welcome to Our Service</title>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
color: #333;
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
text-align: center;
border-radius: 10px 10px 0 0;
}
.content {
background: #f9f9f9;
padding: 30px;
border-radius: 0 0 10px 10px;
}
.button {
display: inline-block;
background: #667eea;
color: white;
padding: 12px 30px;
text-decoration: none;
border-radius: 5px;
margin: 20px 0;
}
.footer {
text-align: center;
margin-top: 30px;
font-size: 12px;
color: #666;
}
</style>
</head>
<body>
<div class="header">
<h1>Welcome to Our Platform!</h1>
</div>
<div class="content">
<h2>Hello, <span th:text="${user.name}">User</span>!</h2>
<p>Thank you for joining our community. We're excited to have you on board.</p>
<p>Your account has been successfully created with the following details:</p>
<ul>
<li><strong>Username:</strong> <span th:text="${user.username}">username</span></li>
<li><strong>Email:</strong> <span th:text="${user.email}">[email protected]</span></li>
<li><strong>Join Date:</strong> <span th:text="${#dates.format(user.joinDate, 'MMMM dd, yyyy')}">January 01, 2024</span></li>
</ul>
<p>To get started, please verify your email address by clicking the button below:</p>
<div style="text-align: center;">
<a th:href="${verificationLink}" class="button">Verify Email Address</a>
</div>
<p>If the button doesn't work, copy and paste this link in your browser:</p>
<p style="word-break: break-all; font-size: 12px;">
<a th:href="${verificationLink}" th:text="${verificationLink}">Verification Link</a>
</p>
<p>If you have any questions, feel free to reply to this email.</p>
<p>Best regards,<br>The Team</p>
</div>
<div class="footer">
<p>&copy; 2024 Our Company. All rights reserved.</p>
<p>
<a href="#" th:href="${unsubscribeLink}">Unsubscribe</a> | 
<a href="#" th:href="${privacyPolicyLink}">Privacy Policy</a>
</p>
</div>
</body>
</html>

Example 3: Password Reset Template

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Password Reset Request</title>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
color: #333;
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.header {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
color: white;
padding: 20px;
text-align: center;
border-radius: 10px 10px 0 0;
}
.content {
background: #f9f9f9;
padding: 30px;
border-radius: 0 0 10px 10px;
}
.button {
display: inline-block;
background: #f5576c;
color: white;
padding: 12px 30px;
text-decoration: none;
border-radius: 5px;
margin: 20px 0;
}
.code {
background: #fff;
border: 2px dashed #f5576c;
padding: 15px;
text-align: center;
font-size: 24px;
font-weight: bold;
letter-spacing: 5px;
margin: 20px 0;
}
.warning {
background: #fff3cd;
border: 1px solid #ffeaa7;
padding: 15px;
border-radius: 5px;
margin: 20px 0;
}
</style>
</head>
<body>
<div class="header">
<h1>Password Reset Request</h1>
</div>
<div class="content">
<h2>Hello, <span th:text="${user.name}">User</span>!</h2>
<p>We received a request to reset your password for your account.</p>
<div class="warning">
<strong>Important:</strong> This password reset link will expire in 
<span th:text="${expirationHours}">24</span> hours.
</div>
<p>Click the button below to reset your password:</p>
<div style="text-align: center;">
<a th:href="${resetLink}" class="button">Reset Password</a>
</div>
<p>Or use this verification code:</p>
<div class="code" th:text="${resetCode}">123456</div>
<p>If you didn't request a password reset, please ignore this email or 
contact our support team if you have concerns.</p>
<p>For security reasons, we recommend:</p>
<ul>
<li>Choosing a strong, unique password</li>
<li>Not sharing your password with anyone</li>
<li>Enabling two-factor authentication</li>
</ul>
<p>Best regards,<br>Security Team</p>
</div>
<div class="footer">
<p>&copy; 2024 Our Company. All rights reserved.</p>
<p>This is an automated message. Please do not reply to this email.</p>
</div>
</body>
</html>

Email Service Implementation

Example 4: Comprehensive Email Service

import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.util.Locale;
@Service
public class EmailService {
private final JavaMailSender mailSender;
private final TemplateEngine templateEngine;
@Autowired
public EmailService(JavaMailSender mailSender, TemplateEngine templateEngine) {
this.mailSender = mailSender;
this.templateEngine = templateEngine;
}
public void sendWelcomeEmail(User user, String verificationLink) {
Context context = new Context();
context.setVariable("user", user);
context.setVariable("verificationLink", verificationLink);
context.setVariable("unsubscribeLink", "https://example.com/unsubscribe");
context.setVariable("privacyPolicyLink", "https://example.com/privacy");
String htmlContent = templateEngine.process("welcome", context);
sendEmail(
user.getEmail(),
"Welcome to Our Platform - Get Started!",
htmlContent
);
}
public void sendPasswordResetEmail(User user, String resetLink, String resetCode, int expirationHours) {
Context context = new Context();
context.setVariable("user", user);
context.setVariable("resetLink", resetLink);
context.setVariable("resetCode", resetCode);
context.setVariable("expirationHours", expirationHours);
String htmlContent = templateEngine.process("password-reset", context);
sendEmail(
user.getEmail(),
"Password Reset Request - Action Required",
htmlContent
);
}
public void sendOrderConfirmationEmail(Order order, User user) {
Context context = new Context();
context.setVariable("user", user);
context.setVariable("order", order);
context.setVariable("orderItems", order.getItems());
context.setVariable("formattedTotal", String.format("$%.2f", order.getTotalAmount()));
String htmlContent = templateEngine.process("order-confirmation", context);
sendEmail(
user.getEmail(),
"Order Confirmation - #" + order.getOrderNumber(),
htmlContent
);
}
public void sendNewsletterEmail(List<Subscriber> subscribers, Newsletter newsletter) {
for (Subscriber subscriber : subscribers) {
Context context = new Context();
context.setVariable("subscriber", subscriber);
context.setVariable("newsletter", newsletter);
context.setVariable("unsubscribeLink", 
"https://example.com/unsubscribe?email=" + subscriber.getEmail());
String htmlContent = templateEngine.process("newsletter", context);
sendEmail(
subscriber.getEmail(),
newsletter.getTitle(),
htmlContent
);
}
}
public void sendInternationalizedEmail(User user, Locale locale) {
Context context = new Context(locale);
context.setVariable("user", user);
context.setVariable("welcomeMessage", getMessage("welcome.message", locale));
String htmlContent = templateEngine.process("welcome-international", context);
sendEmail(
user.getEmail(),
getMessage("email.welcome.subject", locale),
htmlContent
);
}
private void sendEmail(String to, String subject, String htmlContent) {
try {
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setTo(to);
helper.setSubject(subject);
helper.setText(htmlContent, true); // true = HTML content
helper.setFrom("[email protected]", "Our Company");
mailSender.send(message);
System.out.println("Email sent successfully to: " + to);
} catch (MessagingException e) {
throw new RuntimeException("Failed to send email to: " + to, e);
}
}
// Helper method for internationalization
private String getMessage(String key, Locale locale) {
// Implement your message source lookup
return key; // Simplified for example
}
}
// Supporting Data Classes
class User {
private String name;
private String email;
private String username;
private java.time.LocalDateTime joinDate;
// Constructors, getters, setters
public User(String name, String email, String username, java.time.LocalDateTime joinDate) {
this.name = name;
this.email = email;
this.username = username;
this.joinDate = joinDate;
}
// Getters and setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public java.time.LocalDateTime getJoinDate() { return joinDate; }
public void setJoinDate(java.time.LocalDateTime joinDate) { this.joinDate = joinDate; }
}
class Order {
private String orderNumber;
private List<OrderItem> items;
private Double totalAmount;
private java.time.LocalDateTime orderDate;
// Constructors, getters, setters
public String getOrderNumber() { return orderNumber; }
public List<OrderItem> getItems() { return items; }
public Double getTotalAmount() { return totalAmount; }
public java.time.LocalDateTime getOrderDate() { return orderDate; }
}
class OrderItem {
private String productName;
private Integer quantity;
private Double price;
// Constructors, getters, setters
}

Advanced Template Features

Example 5: Template Fragments and Layouts

base-template.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title layout:title-pattern="$CONTENT_TITLE - Our Company">Our Company</title>
<style>
/* Common styles for all emails */
body {
font-family: Arial, sans-serif;
line-height: 1.6;
color: #333;
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
text-align: center;
border-radius: 10px 10px 0 0;
}
.content {
background: #f9f9f9;
padding: 30px;
border-radius: 0 0 10px 10px;
}
.footer {
text-align: center;
margin-top: 30px;
font-size: 12px;
color: #666;
}
</style>
</head>
<body>
<div class="header" layout:fragment="header">
<h1>Our Company</h1>
</div>
<div class="content" layout:fragment="content">
<!-- Content will be inserted here -->
</div>
<div class="footer" layout:fragment="footer">
<p>&copy; 2024 Our Company. All rights reserved.</p>
<p>
<a href="https://example.com/unsubscribe">Unsubscribe</a> | 
<a href="https://example.com/privacy">Privacy Policy</a>
</p>
</div>
</body>
</html>

notification-email.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{emails/base-template}">
<head>
<title>Notification</title>
</head>
<body>
<div layout:fragment="header">
<h1 th:text="${notificationTitle}">Notification</h1>
</div>
<div layout:fragment="content">
<h2>Hello, <span th:text="${user.name}">User</span>!</h2>
<p th:text="${notificationMessage}">Notification message goes here.</p>
<div th:if="${actionLink}" style="text-align: center; margin: 20px 0;">
<a th:href="${actionLink}" 
style="display: inline-block; background: #667eea; color: white; 
padding: 12px 30px; text-decoration: none; border-radius: 5px;">
<span th:text="${actionText}">Take Action</span>
</a>
</div>
<div th:if="${!actionLink}">
<p>No action is required from your side.</p>
</div>
</div>
</body>
</html>

Testing Email Templates

Example 6: Email Template Testing

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@SpringBootTest
public class EmailTemplateTest {
@Autowired
private TemplateEngine templateEngine;
@Test
public void testWelcomeEmailTemplate() {
Context context = new Context();
context.setVariable("user", new User("John Doe", "[email protected]", "johndoe", 
java.time.LocalDateTime.now()));
context.setVariable("verificationLink", "https://example.com/verify?token=abc123");
String htmlContent = templateEngine.process("welcome", context);
assertNotNull(htmlContent);
assertTrue(htmlContent.contains("John Doe"));
assertTrue(htmlContent.contains("[email protected]"));
assertTrue(htmlContent.contains("https://example.com/verify?token=abc123"));
assertTrue(htmlContent.contains("Verify Email Address"));
}
@Test
public void testPasswordResetTemplate() {
Context context = new Context();
context.setVariable("user", new User("Jane Smith", "[email protected]", "janesmith", 
java.time.LocalDateTime.now()));
context.setVariable("resetLink", "https://example.com/reset?token=xyz789");
context.setVariable("resetCode", "ABC123");
context.setVariable("expirationHours", 24);
String htmlContent = templateEngine.process("password-reset", context);
assertNotNull(htmlContent);
assertTrue(htmlContent.contains("Jane Smith"));
assertTrue(htmlContent.contains("ABC123"));
assertTrue(htmlContent.contains("24"));
assertTrue(htmlContent.contains("Reset Password"));
}
}

Best Practices for Email Templates

  1. Responsive Design: Use media queries for mobile devices
  2. Inline CSS: Many email clients don't support external CSS
  3. Alt Text: Always provide alt text for images
  4. Fallback Colors: Specify background colors for dark mode
  5. Testing: Test across multiple email clients
  6. Accessibility: Use proper contrast ratios and font sizes
  7. Unsubscribe Links: Always include unsubscribe functionality
  8. Plain Text Alternative: Consider sending multipart emails

Conclusion

Thymeleaf provides a powerful and flexible solution for creating dynamic email templates in Java applications. Key benefits include:

  • Natural Templating: HTML files that work in browsers and email clients
  • Spring Integration: Seamless integration with Spring Boot ecosystem
  • Dynamic Content: Powerful expression language for personalization
  • Internationalization: Built-in support for multiple languages
  • Reusability: Template fragments and layouts

By combining Thymeleaf with Spring's email support, you can create professional, personalized email communications that enhance user engagement and provide valuable notifications to your users.


Next Steps: Explore Thymeleaf's layout dialect for reusable template components and consider integrating with email testing services like Litmus or Email on Acid for cross-client compatibility testing.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper