Keplars

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

The email-templates package supports both Pug and EJS:

npm install email-templates pug ejs

Standalone Installation

# For Pug
npm install pug

# For EJS
npm install ejs

Pug Syntax

Basic Structure

doctype html
html
  head
    title Email Template
  body
    h1 Welcome #{userName}!
    p Thanks for signing up.
    a(href=dashboardUrl) Go to Dashboard

Variables

//- 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}`)= supportEmail

EJS 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

FeaturePugEJS
SyntaxIndentation-based, no closing tagsHTML-like with embedded JS
Learning CurveModerate (new syntax to learn)Easy (familiar HTML)
VerbosityMinimal, cleanMore verbose (like HTML)
JavaScript SupportLimited (mostly in expressions)Full JavaScript in templates
Template InheritanceBuilt-in with extends/blockRequires includes
Best ForClean, minimal codeFamiliar 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:

EJS:

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

On this page