Creating Dynamic HTML Email Newsletters With Thymeleaf
A well-designed newsletter can turn a routine product update into a useful customer touchpoint. For Java applications, Thymeleaf provides a familiar way to generate personalised HTML email using server-side templates, Spring Boot, and reusable fragments.
The same template can display a subscriber’s name, recent articles, account details, promotions, or event information. A plain-text alternative can be generated alongside the HTML version, helping messages work across Gmail, Outlook, Apple Mail, and mobile clients.
For Australian businesses, timing and compliance matter as much as presentation. A newsletter sent at 9:00 am in Sydney may reach Perth customers at 7:00 am, while commercial messages should support the requirements of the Spam Act 2003, including consent, sender identification, and a working unsubscribe method.
Set Up The Email Template
Create an HTML file such as newsletter.html in src/main/resources/templates/email. Thymeleaf processes this file on the server before the result is handed to an email library such as Spring’s JavaMailSender.
A simple template can use normal HTML during browser testing while adding Thymeleaf attributes for dynamic values:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title th:text="${newsletter.subject}">Monthly Newsletter</title>
</head>
<body>
<h1 th:text="${newsletter.heading}">Latest Updates</h1>
<p>Hello <span th:text="${subscriber.firstName}">Customer</span>,</p>
<p th:text="${newsletter.introduction}">
Here are this month's updates.
</p>
</body>
</html>
Email clients have limited CSS support, so use table-based layouts, inline styles, absolute image URLs, and a maximum content width of about 600 pixels. Avoid relying on JavaScript, external fonts, or complex responsive behaviour.
Model The Newsletter Data
A dedicated model keeps email rendering separate from controller logic. It can contain the subject, heading, introduction, publication date, article collection, brand details, and unsubscribe URL.
public record NewsletterModel(
String subject,
String heading,
String introduction,
List<NewsletterItem> items,
String unsubscribeUrl
) {}
public record NewsletterItem(
String title,
String summary,
String url,
String imageUrl
) {}
The subscriber should be represented by a separate object, particularly when the newsletter includes account information. This avoids exposing internal domain entities directly to the template and makes it easier to remove sensitive fields.
For an Australian audience, date and currency values should be formatted deliberately. A campaign aimed at Sydney, Melbourne, and Brisbane may use Australia/Sydney, while a Perth campaign needs Australia/Perth. Prices can be rendered with an AUD label rather than leaving recipients to infer the currency.
Render Dynamic Content With Thymeleaf
Inject SpringTemplateEngine into an email service and populate a Context with the newsletter model and subscriber details:
@Service
public class NewsletterRenderer {
private final SpringTemplateEngine templateEngine;
public NewsletterRenderer(SpringTemplateEngine templateEngine) {
this.templateEngine = templateEngine;
}
public String render(NewsletterModel newsletter, Subscriber subscriber) {
Context context = new Context(Locale.ENGLISH);
context.setVariable("newsletter", newsletter);
context.setVariable("subscriber", subscriber);
return templateEngine.process("email/newsletter", context);
}
}
Use th:each to create a repeated article block and th:href or th:src for safe attribute replacement:
<table role="presentation" width="100%">
<tr th:each="item : ${newsletter.items}">
<td>
<img th:src="${item.imageUrl}"
th:alt="${item.title}"
width="120">
<h2 th:text="${item.title}">Article Title</h2>
<p th:text="${item.summary}">Article summary</p>
<a th:href="${item.url}">Read the article</a>
</td>
</tr>
</table>
Use th:if for optional sections, such as a discount banner or event invitation. th:unless can provide a fallback message when a collection is empty.
Build Reusable Email Fragments
Email layouts often repeat a header, footer, social links, and legal information. Thymeleaf fragments reduce duplication and make brand changes easier to apply across multiple campaigns.
<footer th:fragment="footer(unsubscribeUrl)"
style="font-size:12px; color:#666;">
<p>JavaWhizz, Australia</p>
<p>
<a th:href="${unsubscribeUrl}">Unsubscribe</a>
</p>
</footer>
Include the fragment with th:replace:
<div th:replace="~{email/fragments :: footer(${newsletter.unsubscribeUrl})}">
Footer content
</div>
A footer should identify the organisation, provide a reliable contact or business address, and include a clear unsubscribe link. These details are particularly important for Australian commercial email campaigns and should remain visible even when images are disabled.
Send HTML And Plain-Text Email
A multipart message gives recipients an HTML version and a readable fallback. Spring’s MimeMessageHelper supports both formats:
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setTo(subscriber.email());
helper.setSubject(newsletter.subject());
helper.setText(plainTextBody, htmlBody);
mailSender.send(message);
The plain-text body should contain the key information, article links, sender identity, and unsubscribe URL. Do not generate it by blindly stripping tags if that produces broken links or unreadable spacing; a small dedicated text template is usually clearer.
For scheduled newsletters, queue delivery rather than sending thousands of messages in a single web request. This supports retry handling, rate limits, bounce processing, and campaign reporting.
Test Layouts Across Email Clients
Email rendering varies widely. Test the template in Gmail, Outlook for Windows, Apple Mail, and common Android clients. Check both desktop and narrow mobile widths, especially if customers are likely to read messages while commuting in Sydney or Melbourne.
A practical test checklist includes:
- Subject and preview text display correctly
- Images have meaningful alternative text
- Links use HTTPS and remain readable
- The layout works with images blocked
- Unsubscribe links lead to a functioning page
Send test messages to internal addresses before a campaign reaches customers. Confirm that tracking parameters do not make URLs excessively long and that personalisation has a safe fallback when a first name is missing.
Protect Personalisation And Delivery
Thymeleaf escapes variable text by default when using th:text, which helps prevent unexpected HTML injection. Avoid th:utext unless the supplied content has been sanitised and is intentionally trusted.
Use a dedicated email provider or properly configured SMTP service with SPF, DKIM, and DMARC records. Monitor bounces and complaints, and keep consent records with the relevant source and timestamp. Australian campaigns should also provide an unsubscribe process that is simple to use and honoured promptly.
Personalisation should add value rather than create surveillance concerns. Keep recommendations relevant, avoid exposing another recipient’s data, and use signed or opaque tokens in unsubscribe URLs instead of putting private information in query parameters.
Improve Performance And Maintainability
Keep newsletter content in a service or campaign repository rather than embedding large amounts of business logic in the template. Thymeleaf should format and display prepared data; it should not decide pricing, eligibility, or database relationships.
Cache static fragments where appropriate, compress images, and use a content delivery network for assets. A modular design also helps local teams prepare campaigns for different regions, such as an EOFY promotion using Australian dollars or an event announcement with a Brisbane venue and local time.
Preview templates with realistic data, including long names, missing images, empty article lists, and unusually long headlines. Automated tests can render the template and assert that the subject, unsubscribe URL, article links, and recipient name appear in the generated HTML.