Pug & EJS
Build email templates with Pug and EJS template engines and Keplars Mail Service
Pug and EJS are popular Node.js template engines that work great for email templates. Pug offers clean, indentation-based syntax while EJS provides embedded JavaScript in templates.
Template Engines Overview
Pug (formerly Jade)
Clean, whitespace-sensitive syntax that compiles to HTML. Great for developers who prefer minimal markup.
Advantages:
- Clean, minimal syntax without closing tags
- Indentation-based structure
- Built-in template inheritance and mixins
- Less verbose than HTML
EJS (Embedded JavaScript)
HTML templates with embedded JavaScript. Familiar to developers who know HTML and JavaScript.
Advantages:
- Looks like regular HTML
- Full JavaScript expressions support
- Easy to learn if you know HTML
- Great for existing HTML templates
Installation
Using email-templates Package (Recommended)
The email-templates package supports both Pug and EJS:
npm install email-templates pug ejsStandalone Installation
# For Pug
npm install pug
# For EJS
npm install ejsPug Syntax
Basic Structure
doctype html
html
head
title Email Template
body
h1 Welcome #{userName}!
p Thanks for signing up.
a(href=dashboardUrl) Go to DashboardVariables
//- Escaped output
p Hello #{userName}
//- Unescaped output (use with caution)
p !{htmlContent}
//- Attributes
a(href=url class="button") Click Here
img(src=logoUrl alt="Logo")Conditionals
if isPremium
p You have premium access!
else
p Upgrade to premium for more features.
//- Unless (if not)
unless hasSubscription
p Subscribe now for updates.Loops
ul
each item in items
li #{item.name} - $#{item.price}
//- With index
ul
each item, index in items
li #{index + 1}. #{item.name}Mixins (Reusable Components)
mixin button(text, url)
a(href=url style="background-color: #007bff; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px;")= text
//- Usage
+button('Get Started', 'https://example.com/start')
+button('Learn More', 'https://example.com/learn')EJS Syntax
Basic Structure
<!DOCTYPE html>
<html>
<head>
<title>Email Template</title>
</head>
<body>
<h1>Welcome <%= userName %>!</h1>
<p>Thanks for signing up.</p>
<a href="<%= dashboardUrl %>">Go to Dashboard</a>
</body>
</html>Variables
<!-- Escaped output -->
<p>Hello <%= userName %></p>
<!-- Unescaped output (raw HTML) -->
<p><%- htmlContent %></p>
<!-- JavaScript expression -->
<p>Total: $<%= (price * quantity).toFixed(2) %></p>Conditionals
<% if (isPremium) { %>
<p>You have premium access!</p>
<% } else { %>
<p>Upgrade to premium for more features.</p>
<% } %>Loops
<ul>
<% items.forEach(function(item) { %>
<li><%= item.name %> - $<%= item.price %></li>
<% }); %>
</ul>
<!-- Alternative with for loop -->
<% for (let i = 0; i < items.length; i++) { %>
<p><%= items[i].name %></p>
<% } %>Includes (Partials)
<%- include('partials/header') %>
<div class="content">
<h1><%= title %></h1>
<p><%= message %></p>
</div>
<%- include('partials/footer') %>Using Pug with Node.js
import pug from 'pug';
// Compile from string
const template = pug.compile(`
doctype html
html
body
h1 Hello #{name}!
p Your order ##{orderId} is confirmed.
a(href=trackingUrl) Track Order
`);
// Render with data
const html = template({
name: 'John',
orderId: '12345',
trackingUrl: 'https://example.com/track/12345'
});
// Send with Keplars Mail Service
await fetch('https://api.keplars.com/api/v1/send-email/async', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.KEPLERS_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
to: ['[email protected]'],
subject: 'Order Confirmation',
body: html
})
});Pug with Template Files
import pug from 'pug';
async function sendWelcomeEmail(userData: any) {
// Compile template file
const template = pug.compileFile('./templates/welcome.pug');
// Render
const html = template(userData);
// Send
await fetch('https://api.keplars.com/api/v1/send-email/async', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.KEPLERS_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
to: [userData.email],
subject: 'Welcome!',
body: html
})
});
}Using EJS with Node.js
import ejs from 'ejs';
const templateString = `
<!DOCTYPE html>
<html>
<body>
<h1>Hello <%= name %>!</h1>
<p>Your account balance is $<%= balance.toFixed(2) %>.</p>
<% if (isPremium) { %>
<p>Premium features are now available.</p>
<% } %>
</body>
</html>
`;
// Render template
const html = ejs.render(templateString, {
name: 'John',
balance: 150.50,
isPremium: true
});
// Send with Keplars Mail Service
await fetch('https://api.keplars.com/api/v1/send-email/async', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.KEPLERS_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
to: ['[email protected]'],
subject: 'Account Update',
body: html
})
});EJS with Template Files
import ejs from 'ejs';
async function sendOrderConfirmation(orderData: any) {
// Render template file
const html = await ejs.renderFile('./templates/order-confirmation.ejs', orderData);
// Send
const response = await fetch('https://api.keplars.com/api/v1/send-email/async', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.KEPLERS_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
to: [orderData.email],
subject: `Order #${orderData.orderId} Confirmed`,
body: html
})
});
return response.json();
}Using email-templates Package
The email-templates package provides a unified interface for both Pug and EJS:
import Email from 'email-templates';
const email = new Email({
message: {
from: '[email protected]'
},
views: {
root: './templates',
options: {
extension: 'pug' // or 'ejs'
}
}
});
// Render template
const html = await email.render('welcome', {
name: 'John',
dashboardUrl: 'https://example.com/dashboard'
});
// Send with Keplars
await fetch('https://api.keplars.com/api/v1/send-email/async', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.KEPLERS_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
to: ['[email protected]'],
subject: 'Welcome!',
body: html
})
});Express.js Integration
With Pug
import express from 'express';
import pug from 'pug';
const app = express();
app.use(express.json());
// Set Pug as view engine
app.set('view engine', 'pug');
app.set('views', './templates');
app.post('/send-welcome', async (req, res) => {
const { email, name } = req.body;
// Render template
const template = pug.compileFile('./templates/welcome.pug');
const html = template({ name, dashboardUrl: 'https://example.com/dashboard' });
// Send with Keplars
const response = await fetch('https://api.keplars.com/api/v1/send-email/async', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.KEPLERS_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
to: [email],
subject: 'Welcome!',
body: html
})
});
const result = await response.json();
res.json({ success: true, messageId: result.messageId });
});With EJS
import express from 'express';
import ejs from 'ejs';
const app = express();
app.use(express.json());
app.set('view engine', 'ejs');
app.set('views', './templates');
app.post('/send-invoice', async (req, res) => {
const { email, invoiceData } = req.body;
// Render template
const html = await ejs.renderFile('./templates/invoice.ejs', invoiceData);
// Send with Keplars
const response = await fetch('https://api.keplars.com/api/v1/send-email/async', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.KEPLERS_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
to: [email],
subject: `Invoice #${invoiceData.invoiceNumber}`,
body: html
})
});
const result = await response.json();
res.json({ success: true, messageId: result.messageId });
});Example Templates
Pug Welcome Email
templates/welcome.pug:
doctype html
html
head
style.
body { font-family: Arial, sans-serif; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
.button {
background-color: #007bff;
color: white;
padding: 10px 20px;
text-decoration: none;
border-radius: 5px;
display: inline-block;
}
body
.container
h1 Welcome #{userName}!
p Thanks for signing up on #{signupDate}.
if isPremium
p You have premium access with these features:
ul
each feature in premiumFeatures
li= feature
else
p Consider upgrading to premium for additional features.
a.button(href=dashboardUrl) Go to Dashboard
hr
p.footer
| Questions? Contact us at
a(href=`mailto:${supportEmail}`)= supportEmailEJS Order Confirmation
templates/order-confirmation.ejs:
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; }
table { width: 100%; border-collapse: collapse; margin: 20px 0; }
th, td { padding: 10px; text-align: left; border-bottom: 1px solid #ddd; }
.total { font-weight: bold; font-size: 18px; }
</style>
</head>
<body>
<h1>Order Confirmation</h1>
<p>Hi <%= customerName %>,</p>
<p>Thank you for your order! Order #<%= orderId %> has been confirmed.</p>
<h2>Order Details</h2>
<table>
<thead>
<tr>
<th>Item</th>
<th>Quantity</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<% items.forEach(function(item) { %>
<tr>
<td><%= item.name %></td>
<td><%= item.quantity %></td>
<td>$<%= item.price.toFixed(2) %></td>
</tr>
<% }); %>
</tbody>
</table>
<p class="total">Total: $<%= total.toFixed(2) %></p>
<% if (shippingAddress) { %>
<h3>Shipping Address</h3>
<p>
<%= shippingAddress.street %><br>
<%= shippingAddress.city %>, <%= shippingAddress.state %> <%= shippingAddress.zip %>
</p>
<% } %>
<p>We'll send you a shipping confirmation when your items are on the way.</p>
</body>
</html>Pug Password Reset
templates/password-reset.pug:
doctype html
html
head
style.
body { font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; }
.button { background-color: #007bff; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; display: inline-block; }
.warning { background-color: #fff3cd; padding: 10px; border-left: 4px solid #ffc107; }
body
h1 Reset Your Password
p Hi #{userName},
p We received a request to reset your password. Click the button below to create a new password:
p
a.button(href=resetUrl) Reset Password
p This link will expire in #{expirationHours} hours.
.warning
strong Important:
| If you didn't request a password reset, please ignore this email and your password will remain unchanged.Comparison: Pug vs EJS
| Feature | Pug | EJS |
|---|---|---|
| Syntax | Indentation-based, no closing tags | HTML-like with embedded JS |
| Learning Curve | Moderate (new syntax to learn) | Easy (familiar HTML) |
| Verbosity | Minimal, clean | More verbose (like HTML) |
| JavaScript Support | Limited (mostly in expressions) | Full JavaScript in templates |
| Template Inheritance | Built-in with extends/block | Requires includes |
| Best For | Clean, minimal code | Familiar HTML structure |
Choose Pug if you prefer minimal syntax and don't mind learning new syntax. Choose EJS if you want templates that look like regular HTML with embedded JavaScript.
Best Practices
Cache Compiled Templates: Compile templates once and reuse them. Don't recompile on every email send.
Use Template Inheritance: Both engines support template inheritance. Create base layouts and extend them.
Separate Logic from Templates: Keep business logic in your application code. Templates should focus on presentation.
Test with Real Data: Test templates with realistic data including edge cases.
Email-Safe CSS: Keep CSS simple and inline. Both engines support inline CSS, but consider using email-specific CSS libraries.
Validate Output: Always validate the generated HTML to ensure it works across email clients.
TypeScript Support
Pug with TypeScript
import pug from 'pug';
interface WelcomeEmailData {
userName: string;
dashboardUrl: string;
isPremium: boolean;
}
async function sendWelcomeEmail(data: WelcomeEmailData) {
const template = pug.compileFile('./templates/welcome.pug');
const html = template(data);
await fetch('https://api.keplars.com/api/v1/send-email/async', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.KEPLERS_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
to: [data.email],
subject: 'Welcome!',
body: html
})
});
}EJS with TypeScript
import ejs from 'ejs';
interface OrderData {
customerName: string;
orderId: string;
items: Array<{ name: string; price: number; quantity: number }>;
total: number;
}
async function sendOrderConfirmation(data: OrderData) {
const html = await ejs.renderFile<OrderData>('./templates/order.ejs', data);
await fetch('https://api.keplars.com/api/v1/send-email/async', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.KEPLERS_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
to: [data.email],
subject: `Order #${data.orderId} Confirmed`,
body: html
})
});
}Resources
Pug:
- Official Documentation: https://pugjs.org
- Language Reference: https://pugjs.org/language/attributes.html
EJS:
- Official Documentation: https://ejs.co
- Syntax Reference: https://ejs.co/#docs
email-templates:
- Documentation: https://email-templates.js.org
- GitHub: https://github.com/forwardemail/email-templates
Next Steps
- React Email - Component-based templates with React
- MJML - Responsive email framework
- Handlebars - Simple dynamic templates
- Send Emails - Learn more about sending emails with Keplars