Learn how to create beautiful, responsive email templates using various template engines supported by NestJS Mailable.
NestJS Mailable supports multiple template engines:
import { MailModule } from 'nestjs-mailable';
@Module({
imports: [
MailModule.forRoot({
config: {
// ... other config
templates: {
engine: 'handlebars',
directory: './templates',
options: {
partials: './templates/partials',
helpers: {
// Custom helpers
uppercase: (str: string) => str.toUpperCase(),
formatDate: (date: Date) => date.toLocaleDateString()
}
}
}
}
})
],
})
export class AppModule {}
templates/welcome.hbs
await mailService.send({
to: { address: user.email, name: user.name },
subject: 'Welcome to Our Platform!',
template: 'welcome',
context: {
userName: user.name,
appName: 'My App',
verificationUrl: 'https://myapp.com/verify?token=abc123'
}
});
templates/partials/header.hbs
templates/partials/footer.hbs
templates/newsletter.hbs
{
templates: {
engine: 'handlebars',
directory: './templates',
options: {
helpers: {
// Format currency
currency: (amount: number, currency = 'USD') => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: currency
}).format(amount);
},
// Format date
formatDate: (date: Date, format = 'short') => {
return new Intl.DateTimeFormat('en-US', {
dateStyle: format as any
}).format(new Date(date));
},
// Conditional helper
ifEquals: function(arg1: any, arg2: any, options: any) {
return arg1 === arg2 ? options.fn(this) : options.inverse(this);
},
// Markdown to HTML
markdown: (text: string) => {
// Use your preferred markdown parser
return marked(text);
}
}
}
}
}
templates/order-confirmation.ejs
<!DOCTYPE html>
<html>
<head>
<title>Order Confirmation</title>
<style>
/* Your CSS styles */
</style>
</head>
<body>
<h1>Order Confirmation #<%= orderNumber %></h1>
<p>Dear <%= customerName %>,</p>
<p>Thank you for your order! Here are the details:</p>
<table>
<thead>
<tr>
<th>Item</th>
<th>Quantity</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<% items.forEach(item => { %>
<tr>
<td><%= item.name %></td>
<td><%= item.quantity %></td>
<td>$<%= item.price.toFixed(2) %></td>
</tr>
<% }); %>
</tbody>
<tfoot>
<tr>
<td colspan="2"><strong>Total:</strong></td>
<td><strong>$<%= totalAmount.toFixed(2) %></strong></td>
</tr>
</tfoot>
</table>
<% if (trackingNumber) { %>
<p>Your tracking number is: <strong><%= trackingNumber %></strong></p>
<% } %>
<p>Estimated delivery: <%= deliveryDate.toDateString() %></p>
</body>
</html>
templates/password-reset.pug
doctype html
html
head
title Password Reset
style.
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; }
.container { max-width: 600px; margin: 0 auto; }
.alert { padding: 15px; background: #f8d7da; border: 1px solid #f5c6cb; }
.btn {
display: inline-block;
padding: 10px 20px;
background: #dc3545;
color: white;
text-decoration: none;
}
body
.container
h1 Password Reset Request
p Hello #{userName},
p We received a request to reset your password. If you didn't make this request, please ignore this email.
if resetUrl
.alert
p To reset your password, click the button below:
p
a.btn(href=resetUrl) Reset Password
p This link will expire in #{expirationTime}.
p If you're having trouble clicking the button, copy and paste the URL below into your web browser:
p= resetUrl
p Best regards,
p The Security Team
MJML creates responsive emails that work across all email clients.
templates/promotional.mjml
<mjml>
<mj-head>
<mj-title>Special Offer Just for You!</mj-title>
<mj-preview>Don't miss out on this exclusive deal</mj-preview>
<mj-attributes>
<mj-all font-family="Arial, sans-serif" />
<mj-button background-color="#007bff" color="white" />
</mj-attributes>
</mj-head>
<mj-body background-color="#f4f4f4">
<mj-section background-color="white" padding="0">
<mj-column>
<mj-image
src=""
alt=""
align="center"
width="200px"
padding="20px" />
</mj-column>
</mj-section>
<mj-section background-color="white">
<mj-column>
<mj-text font-size="24px" font-weight="bold" align="center">
Hello !
</mj-text>
<mj-text font-size="16px" line-height="1.6">
We have an exclusive offer just for you. Get % off your next purchase!
</mj-text>
<mj-button href="" background-color="#28a745">
Claim Your Discount
</mj-button>
<mj-text font-size="12px" color="#666">
Offer expires on
</mj-text>
</mj-column>
</mj-section>
<mj-section background-color="#f8f9fa">
<mj-column>
<mj-text align="center" font-size="12px" color="#666">
© . All rights reserved.
</mj-text>
<mj-text align="center" font-size="12px">
<a href="">Unsubscribe</a>
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
Perfect for simple, text-focused emails.
templates/notification.md
# New Notification
Hello ****,
You have a new notification from :
##
---
### Action Required
[Take Action]()
**Deadline:**
---
Best regards,
The Team
---
*This is an automated message. Please do not reply to this email.*
[Unsubscribe]() | [Privacy Policy]()
templates/
├── layouts/
│ ├── base.hbs
│ └── minimal.hbs
├── partials/
│ ├── header.hbs
│ ├── footer.hbs
│ └── social-links.hbs
├── emails/
│ ├── auth/
│ │ ├── welcome.hbs
│ │ ├── verify-email.hbs
│ │ └── password-reset.hbs
│ ├── orders/
│ │ ├── confirmation.hbs
│ │ ├── shipped.hbs
│ │ └── delivered.hbs
│ └── marketing/
│ ├── newsletter.hbs
│ ├── promotion.hbs
│ └── survey.hbs
└── styles/
├── base.css
└── responsive.css
templates/layouts/base.hbs
export class NotificationMail extends Mailable {
constructor(
private notification: Notification,
private user: User
) {
super();
}
protected build() {
const template = this.selectTemplate();
this.subject(this.notification.title)
.view(template, {
userName: this.user.name,
notificationTitle: this.notification.title,
notificationContent: this.notification.content,
priority: this.notification.priority
})
.tag('notification')
.tag(this.notification.type);
return this.content;
}
private selectTemplate(): string {
switch (this.notification.type) {
case 'urgent':
return 'emails/notifications/urgent';
case 'reminder':
return 'emails/notifications/reminder';
case 'update':
return 'emails/notifications/update';
default:
return 'emails/notifications/default';
}
}
}
export class WelcomeMail extends Mailable {
constructor(
private user: User,
private locale: string = 'en'
) {
super();
}
protected build() {
const template = `emails/welcome/${this.locale}`;
this.subject(this.getLocalizedSubject())
.view(template, {
userName: this.user.name,
appName: this.getLocalizedAppName()
});
return this.content;
}
private getLocalizedSubject(): string {
const subjects = {
en: `Welcome ${this.user.name}!`,
es: `¡Bienvenido ${this.user.name}!`,
fr: `Bienvenue ${this.user.name}!`,
de: `Willkommen ${this.user.name}!`
};
return subjects[this.locale] || subjects.en;
}
}
Always include mobile-responsive CSS and test across different email clients.
Structure your emails with proper HTML semantics for better accessibility.
Email clients have limited CSS support, so inline critical styles.
Test your templates in major email clients (Gmail, Outlook, Apple Mail, etc.).
Always provide a plain text alternative for better deliverability:
await mailService.send({
to: { address: user.email },
subject: 'Welcome!',
template: 'welcome',
context: { userName: user.name },
// Automatically generate plain text from HTML
generateTextFromHtml: true
});