Skip to main content

Email Templates

Email templating allows you to prepare and reuse email content efficiently. Create templates once, then reference them when sending to ensure consistent branding and messaging across all your emails.

Why Use Templates

Templates provide several benefits:

  • Consistency: Ensure brand consistency across all emails
  • Efficiency: Define content once, reuse many times
  • Maintainability: Update content in one place
  • Personalization: Use Handlebars for dynamic content
  • Separation of concerns: Keep email content separate from application logic

Managing Templates

You can manage templates in two ways:

  1. Templates API: Programmatically create, update, and delete templates
  2. Admin Interface: Visual interface at Email Templates in the side menu

Email Templates List Email templates list in the admin interface

Template Editor Template editor showing Handlebars syntax and fields

Creating Templates

Via API

Create a template using the create template API:

curl -XPOST "https://ee.example.com/v1/templates/template" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"account": "example",
"name": "Welcome Email",
"description": "Welcome new users to the platform",
"content": {
"subject": "Welcome to {{{params.companyName}}}!",
"text": "Hello {{params.firstName}},\n\nWelcome to {{params.companyName}}!",
"html": "<h1>Hello {{params.firstName}}</h1><p>Welcome to <strong>{{params.companyName}}</strong>!</p>"
}
}'

Response:

{
"created": true,
"account": "example",
"id": "AAABgUIbuG0AAAAE"
}

Save the id value to reference this template when sending.

Via Admin Interface

  1. Navigate to Email templates in the EmailEngine UI
  2. Click Create Template
  3. Fill in template details:
    • Name: Template identifier (required)
    • Description: What this template is for (optional)
    • Subject: Email subject with optional Handlebars
    • Text: Plain text version with optional Handlebars
    • HTML: HTML version with optional Handlebars
  4. Click Save

The interface shows a preview of how the template will render.

Using Templates

Basic Usage

When sending emails using the Submission API, set the template property instead of subject, html, or text:

curl -XPOST "https://ee.example.com/v1/account/example/submit" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"to": [
{
"name": "Recipient Name",
"address": "recipient@example.com"
}
],
"template": "AAABgUIbuG0AAAAE",
"render": {
"params": {
"firstName": "Alice",
"companyName": "Acme Corp"
}
}
}'

EmailEngine loads the template and renders it with your provided params.

With Other Properties

You can include any other valid submission properties:

curl -XPOST "https://ee.example.com/v1/account/example/submit" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"to": [{ "address": "recipient@example.com" }],
"template": "AAABgUIbuG0AAAAE",
"render": {
"params": {
"firstName": "Alice",
"companyName": "Acme Corp"
}
},
"replyTo": { "address": "support@example.com" },
"attachments": [
{
"filename": "welcome.pdf",
"content": "base64-encoded-content"
}
]
}'

Template Syntax

Templates use Handlebars for dynamic content. EmailEngine extends Handlebars with additional helpers compatible with SendGrid's dynamic templates, making migration between platforms straightforward.

Variables

Insert variables using double or triple braces:

Hello {{params.firstName}} {{params.lastName}}!

Subject: Welcome {{{params.name}}}
  • Double braces {{...}}: HTML-escape the value
  • Triple braces {{{...}}}: No escaping (use for plain text fields like subject)

Built-in Variables

EmailEngine provides built-in variables:

From: {{account.name}} <{{account.email}}>
Support: {{service.url}}
Custom params: {{params.anyKey}}

Available variables:

  • {{account.email}} - Sender's email address
  • {{account.name}} - Sender's display name
  • {{service.url}} - EmailEngine instance URL
  • {{params.*}} - Any custom parameters you provide

Conditionals

Use if/else for conditional content:

<p>Hello {{params.firstName}},</p>

{{#if params.isPremium}}
<p>Welcome to our premium tier! You have access to all features.</p>
{{else}}
<p>Welcome! Upgrade to premium for additional features.</p>
{{/if}}

Loops

Iterate over arrays with each:

<h2>Your order:</h2>
<ul>
{{#each params.items}}
<li>{{this.name}} - ${{this.price}}</li>
{{/each}}
</ul>

<p>Total: ${{params.total}}</p>

With data:

{
"params": {
"items": [
{ "name": "Product A", "price": "29.99" },
{ "name": "Product B", "price": "39.99" }
],
"total": "69.98"
}
}

Basic Helpers

Common built-in Handlebars helpers:

{{!-- Comments --}}
{{! This is a comment and won't appear in output }}

{{!-- Unless (opposite of if) --}}
{{#unless params.isSubscribed}}
<p>Subscribe to our newsletter!</p>
{{/unless}}

{{!-- With (change context) --}}
{{#with params.user}}
<p>{{firstName}} {{lastName}}</p>
<p>{{email}}</p>
{{/with}}

SendGrid-Compatible Helpers

EmailEngine provides additional helpers that are compatible with SendGrid's dynamic templates. These helpers enable advanced templating capabilities for comparisons, date formatting, and default values.

Comparison Helpers

equals

Check if two values are equal. Uses loose equality (==) for automatic type coercion.

{{#equals params.status "active"}}
<p>Your account is active.</p>
{{else}}
<p>Your account is inactive.</p>
{{/equals}}
{{#equals params.customerCode params.winningCode}}
<p>Congratulations! You have a winning code.</p>
{{/equals}}
notEquals

Check if two values are not equal. Uses loose inequality (!=) for automatic type coercion.

{{#notEquals params.role "admin"}}
<p>You don't have admin privileges.</p>
{{/notEquals}}
greaterThan

Check if the first numeric value is greater than the second.

{{#greaterThan params.score 90}}
<p>Excellent score! You're in the top tier.</p>
{{else}}
<p>Keep working to improve your score.</p>
{{/greaterThan}}
{{#greaterThan params.cartTotal 100}}
<p>You qualify for free shipping!</p>
{{/greaterThan}}
lessThan

Check if the first numeric value is less than the second.

{{#lessThan params.daysRemaining 7}}
<p>Your subscription expires soon. Renew now!</p>
{{/lessThan}}
{{#lessThan params.inventory 10}}
<p>Low stock - only {{params.inventory}} items left!</p>
{{/lessThan}}

Logical Helpers

and

Renders content only when all conditions are true. Accepts multiple arguments.

{{#and params.isVerified params.hasSubscription}}
<p>Welcome back, verified subscriber!</p>
{{else}}
<p>Please verify your email or subscribe to continue.</p>
{{/and}}
{{#and params.inStock params.hasDiscount params.isPremiumMember}}
<p>Exclusive deal available for you!</p>
{{/and}}
or

Renders content when at least one condition is true. Accepts multiple arguments.

{{#or params.isAdmin params.isModerator}}
<p>You have moderation privileges.</p>
{{/or}}
{{#or params.hasCoupon params.isPremium params.isFirstOrder}}
<p>You're eligible for a discount!</p>
{{/or}}

Value Helpers

insert

Insert a value with an optional default if the value is missing or empty.

<p>Hello {{insert params.firstName "default=Customer"}}!</p>

If params.firstName is empty or undefined, displays "Customer" instead.

<p>Your membership level: {{insert params.tier "default=Standard"}}</p>
length

Get the length of an array. Useful in conditionals to check if an array has items.

{{#if (length params.items)}}
<p>You have {{length params.items}} items in your cart.</p>
{{else}}
<p>Your cart is empty.</p>
{{/if}}
{{#greaterThan (length params.orders) 10}}
<p>Thank you for being a loyal customer with over 10 orders!</p>
{{/greaterThan}}

Date Formatting

formatDate

Format dates using Moment.js format tokens. Accepts an optional timezone offset.

Syntax: {{formatDate timestamp format [timezoneOffset]}}

Common format tokens:

TokenOutputExample
YYYY4-digit year2025
YY2-digit year25
MMMMFull month nameJanuary
MMMShort month nameJan
MMMonth number (padded)01
DDDay of month (padded)05
DDay of month5
ddddFull weekday nameMonday
dddShort weekday nameMon
HHHour (24h, padded)14
hhHour (12h, padded)02
hHour (12h)2
mmMinutes (padded)05
ssSeconds (padded)09
AAM/PMPM
aam/pmpm
ZZTimezone offset+0000

Examples:

<p>Order date: {{formatDate params.orderDate "MMMM DD, YYYY"}}</p>
<!-- Output: Order date: January 15, 2025 -->
<p>Event time: {{formatDate params.eventTime "dddd, MMMM D, YYYY [at] h:mm A"}}</p>
<!-- Output: Event time: Monday, January 15, 2025 at 2:30 PM -->
<p>Delivery: {{formatDate params.deliveryDate "MMM D, YYYY" "-0500"}}</p>
<!-- Output with EST timezone offset: Delivery: Jan 15, 2025 -->
<p>Created: {{formatDate params.timestamp "YYYY-MM-DD HH:mm:ss"}}</p>
<!-- Output: Created: 2025-01-15 14:30:00 -->

Iteration Helpers

each with Special Variables

When iterating over arrays, you have access to special variables:

<ol>
{{#each params.steps}}
<li>Step {{@index}}: {{this}}</li>
{{/each}}
</ol>
VariableDescription
{{@index}}Zero-based index of the current item
{{@first}}True if this is the first item
{{@last}}True if this is the last item
{{this}}The current item value
<ul>
{{#each params.items}}
<li{{#if @first}} class="first"{{/if}}{{#if @last}} class="last"{{/if}}>
{{this.name}}
</li>
{{/each}}
</ul>

Root Context Access

Access top-level variables from within nested blocks using @root:

{{#each params.orders}}
<div class="order">
<p>Order #{{this.id}}</p>
<p>Customer: {{@root.params.customerName}}</p>
<p>Support: {{@root.service.url}}/support</p>
</div>
{{/each}}

Helpers Quick Reference

HelperPurposeExample
{{#if}}Conditional rendering{{#if params.active}}...{{/if}}
{{#unless}}Inverse conditional{{#unless params.disabled}}...{{/unless}}
{{#each}}Iterate over arrays{{#each params.items}}...{{/each}}
{{#with}}Change context{{#with params.user}}...{{/with}}
{{#equals}}Equality check{{#equals a b}}...{{/equals}}
{{#notEquals}}Inequality check{{#notEquals a b}}...{{/notEquals}}
{{#greaterThan}}Numeric greater than{{#greaterThan a b}}...{{/greaterThan}}
{{#lessThan}}Numeric less than{{#lessThan a b}}...{{/lessThan}}
{{#and}}All conditions true{{#and a b c}}...{{/and}}
{{#or}}Any condition true{{#or a b c}}...{{/or}}
{{insert}}Value with default{{insert var "default=fallback"}}
{{length}}Array length{{length params.items}}
{{formatDate}}Format dates{{formatDate date "MMM D, YYYY"}}

Template Examples

Welcome Email

Subject: Welcome to {{{params.companyName}}}, {{{params.firstName}}}!

HTML:
<html>
<head>
<style>
body { font-family: Arial, sans-serif; }
.header { background: #4CAF50; color: white; padding: 20px; }
.content { padding: 20px; }
</style>
</head>
<body>
<div class="header">
<h1>Welcome to {{params.companyName}}!</h1>
</div>
<div class="content">
<p>Hello {{params.firstName}},</p>
<p>Thank you for joining {{params.companyName}}. We're excited to have you on board!</p>

{{#if params.nextSteps}}
<h2>Next Steps:</h2>
<ul>
{{#each params.nextSteps}}
<li>{{this}}</li>
{{/each}}
</ul>
{{/if}}

<p>If you have any questions, reply to this email or visit our <a href="{{service.url}}/help">help center</a>.</p>

<p>Best regards,<br>
The {{params.companyName}} Team</p>
</div>
</body>
</html>

Text:
Welcome to {{params.companyName}}!

Hello {{params.firstName}},

Thank you for joining {{params.companyName}}. We're excited to have you on board!

{{#if params.nextSteps}}
Next Steps:
{{#each params.nextSteps}}
- {{this}}
{{/each}}
{{/if}}

If you have any questions, reply to this email or visit our help center at {{service.url}}/help.

Best regards,
The {{params.companyName}} Team

Order Confirmation

Subject: Order #{{{params.orderNumber}}} Confirmed

HTML:
<h1>Order Confirmation</h1>
<p>Hello {{params.customerName}},</p>
<p>Your order <strong>#{{params.orderNumber}}</strong> has been confirmed!</p>

<h2>Order Details:</h2>
<table style="width: 100%; border-collapse: collapse;">
<thead>
<tr style="background: #f5f5f5;">
<th style="padding: 10px; text-align: left;">Item</th>
<th style="padding: 10px; text-align: right;">Qty</th>
<th style="padding: 10px; text-align: right;">Price</th>
</tr>
</thead>
<tbody>
{{#each params.items}}
<tr style="border-bottom: 1px solid #ddd;">
<td style="padding: 10px;">{{this.name}}</td>
<td style="padding: 10px; text-align: right;">{{this.quantity}}</td>
<td style="padding: 10px; text-align: right;">${{this.price}}</td>
</tr>
{{/each}}
</tbody>
<tfoot>
<tr style="font-weight: bold; background: #f5f5f5;">
<td colspan="2" style="padding: 10px; text-align: right;">Total:</td>
<td style="padding: 10px; text-align: right;">${{params.total}}</td>
</tr>
</tfoot>
</table>

<p>Estimated delivery: {{params.estimatedDelivery}}</p>

<p>Track your order: <a href="{{params.trackingUrl}}">{{params.trackingNumber}}</a></p>

Password Reset

Subject: Reset Your Password

HTML:
<h1>Password Reset Request</h1>
<p>Hello {{params.firstName}},</p>
<p>We received a request to reset your password for your {{account.name}} account.</p>

<p>Click the button below to reset your password:</p>

<p style="text-align: center; margin: 30px 0;">
<a href="{{params.resetUrl}}" style="background: #4CAF50; color: white; padding: 15px 30px; text-decoration: none; border-radius: 5px; display: inline-block;">
Reset Password
</a>
</p>

<p>Or copy and paste this link into your browser:</p>
<p style="word-break: break-all; color: #666;">{{params.resetUrl}}</p>

<p><strong>This link will expire in {{params.expiryHours}} hours.</strong></p>

<p>If you didn't request this password reset, you can safely ignore this email.</p>

<p>Best regards,<br>
{{account.name}}</p>

Template Management

List All Templates

Retrieve all templates using the list templates API:

curl "https://ee.example.com/v1/templates?account=example" \
-H "Authorization: Bearer <token>"

Response:

{
"account": "example",
"total": 2,
"page": 0,
"pages": 1,
"templates": [
{
"id": "AAABgUIbuG0AAAAE",
"name": "Welcome Email",
"description": "Welcome new users",
"format": "html",
"created": "2025-05-14T10:00:00.000Z",
"updated": "2025-05-14T12:00:00.000Z"
},
{
"id": "AAABgUIbuG0AAAAF",
"name": "Order Confirmation",
"description": "Confirm orders",
"format": "html",
"created": "2025-05-14T11:00:00.000Z",
"updated": "2025-05-14T11:00:00.000Z"
}
]
}

Get Template Details

Use the get template API:

curl "https://ee.example.com/v1/templates/template/AAABgUIbuG0AAAAE" \
-H "Authorization: Bearer <token>"

Response:

{
"account": "example",
"id": "AAABgUIbuG0AAAAE",
"name": "Welcome Email",
"description": "Welcome new users to the platform",
"format": "html",
"created": "2025-05-14T10:00:00.000Z",
"updated": "2025-05-14T12:00:00.000Z",
"content": {
"subject": "Welcome to {{{params.companyName}}}!",
"text": "Hello {{params.firstName}}...",
"html": "<h1>Hello {{params.firstName}}</h1>..."
}
}

Update Template

Use the update template API:

curl -XPUT "https://ee.example.com/v1/templates/template/AAABgUIbuG0AAAAE" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"content": {
"subject": "Welcome to {{{params.companyName}}}, {{{params.firstName}}}!",
"html": "<h1>Updated content</h1>"
}
}'

Only include fields you want to update.

Delete Template

Use the delete template API:

curl -XDELETE "https://ee.example.com/v1/templates/template/AAABgUIbuG0AAAAE" \
-H "Authorization: Bearer <token>"

Using Templates with Mail Merge

Templates work great with mail merge for bulk personalized sending:

curl -XPOST "https://ee.example.com/v1/account/example/submit" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"template": "AAABgUIbuG0AAAAE",
"mailMerge": [
{
"to": { "name": "Alice", "address": "alice@example.com" },
"params": {
"firstName": "Alice",
"companyName": "Acme Corp",
"isPremium": true
}
},
{
"to": { "name": "Bob", "address": "bob@example.com" },
"params": {
"firstName": "Bob",
"companyName": "Acme Corp",
"isPremium": false
}
}
]
}'

Each recipient gets a personalized email based on their params.