SendGrid Dynamic Templates: Design & Send Handlebars-Powered Emails
In this tutorial, you will learn about SendGrid Dynamic Templates: Design & Send Handlebars. We cover key concepts, practical examples, and best practices to help you master this topic.
SendGrid dynamic templates use Handlebars syntax for email content that adapts per recipient, supporting variables, conditionals, loops, and formatting helpers.
What You'll Learn
How to design dynamic templates in SendGrid's editor, use Handlebars variables and helpers, test templates with sample data, manage template versions, and send with dynamic data from your app.
Why It Matters
Static templates require a separate template for every variation. Dynamic templates handle unlimited variations from a single design. DodaTech uses one template for all security reports, varying content per user via dynamic data.
Real-World Use
A single security report template with {{name}}, {{threats}}, and {{report_url}} variables. Each user receives the same template but different content based on their dynamic data.
flowchart LR
A["Design Template\nSendGrid Editor"] --> B["Add Handlebars\n{{variables}}"]
B --> C["Test with\nSample Data"]
C --> D["Save Version\nv1, v2, v3"]
D --> E["Send API Call\n+ dynamic_template_data"]
E --> F["Rendered Email\nPer Recipient"]
style A fill:#dbeafe,stroke:#2563eb
style E fill:#fef3c7,stroke:#d97706
style F fill:#bbf7d0,stroke:#16a34a
Creating a Template
# In SendGrid Dashboard:
# Email API > Dynamic Templates > Create Template
# Name: "security-report"
# Add version with HTML editor
# Template HTML:
"""
<!DOCTYPE html>
<html>
<head>
<title>Weekly Security Report</title>
</head>
<body>
<h1>Hi {{name}},</h1>
<p>Your weekly security report for {{scan_date}} is ready.</p>
{{#if (gt threat_count 0)}}
<div class="alert">
<h2>{{threat_count}} Threat(s) Detected</h2>
<ul>
{{#each threats}}
<li>{{this.name}} ({{this.severity}})</li>
{{/each}}
</ul>
</div>
{{else}}
<div class="clean">
<h2>No Threats Found</h2>
<p>Your device is clean. Last scan: {{scan_date}}.</p>
</div>
{{/if}}
<a href="{{report_url}}">View Full Report</a>
</body>
</html>
"""
Sending with Dynamic Template
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail
def send_dynamic_template(to_email, template_id, data):
message = Mail(
from_email="reports@dodatech.com",
to_emails=to_email,
)
message.template_id = template_id
message.dynamic_template_data = data
response = sg.send(message)
if response.status_code == 202:
print(f"Template email sent to {to_email}")
return response
template_id = "d-abc123def456"
send_dynamic_template(
to_email="alice@example.com",
template_id=template_id,
data={
"name": "Alice",
"scan_date": "June 28, 2026",
"threat_count": 2,
"threats": [
{"name": "Trojan.Generic", "severity": "High"},
{"name": "Adware.Bundle", "severity": "Low"}
],
"report_url": "https://dodatech.com/reports/alice"
}
)
# Expected output: Template email sent to alice@example.com
Template Versioning
# SendGrid tracks template versions
# Deactivate old version when deploying new one
def activate_template_version(template_id, version_id):
# In SendGrid Dashboard:
# Dynamic Templates > Select template > Versions
# Click "Activate" on the desired version
print(f"Activated version {version_id} of template {template_id}")
# You can also use the API:
def get_active_version(template_id):
sg = SendGridAPIClient(os.environ["SENDGRID_API_KEY"])
response = sg.client.templates._(template_id).get()
versions = response.to_dict().get("versions", [])
for version in versions:
if version.get("active") == 1:
print(f"Active version: {version['id']} ({version['name']})")
return version
return None
Testing Templates
# Test in SendGrid Dashboard before sending:
# Dynamic Templates > Preview > Test Data
test_data = {
"name": "Test User",
"scan_date": "June 28, 2026",
"threat_count": 3,
"threats": [
{"name": "Malware Sample", "severity": "Critical"},
{"name": "Phishing Link", "severity": "High"},
{"name": "Tracking Cookie", "severity": "Low"}
],
"report_url": "https://dodatech.com/reports/test"
}
# Send a test to yourself
def send_test(template_id, to_email):
send_dynamic_template(to_email, template_id, {
"name": "Test User",
"scan_date": "June 28, 2026",
"threat_count": 1,
"threats": [{"name": "Test Threat", "severity": "Low"}],
"report_url": "https://dodatech.com/reports/test"
})
Common Mistakes
1. Missing Required Variables in Dynamic Data
If the template uses {{name}} but you don't pass it, the rendered output has an empty space. Always validate that your dynamic data contains all template variables.
2. Not Escaping HTML in Variables
Variables containing HTML are rendered as raw HTML unless escaped. Use triple brackets {{{unescaped_var}}} for raw HTML and double {{var}} for escaped output.
3. Incorrect Handlebars Syntax
Handlebars is strict. {{#if condition}} needs {{/if}}. Missing closing tags break the entire template for all recipients.
4. Not Testing All Conditions
If you have {{#if threats.length}} and {{else}} blocks, test both paths. A template that works with threats may break with zero threats.
5. Over-Engineering Template Logic
Complex Handlebars logic is hard to debug. Keep templates simple and pre-Process data in your application before passing to the template.
Practice Questions
- What is the difference between
{{variable}}and{{{variable}}}in Handlebars? - How do you create conditional sections in dynamic templates?
- How do you iterate over arrays in a template?
- How do you manage template versions?
Answers:
{{variable}}HTML-escapes the value.{{{variable}}}renders raw HTML. Use triple brackets for trusted HTML content.- Use
{{#if condition}}and{{else}}blocks. Conditions can be truthy/falsy checks or comparison helpers like{{#if (gt threat_count 0)}}. - Use
{{#each array}}...{{/each}}to iterate. Inside the loop,{{this}}refers to the current item, and{{@index}}gives the index. - Use the SendGrid Dashboard's version management or the API
PATCH /v3/templates/{id}/versions/{version_id}to activate/deactivate versions.
Challenge: Design a dynamic template for Durga Antivirus Pro's security report with: user name, scan date, threat list (with conditional "no threats" message), report URL button, and a subscription tier callout. Test with sample data covering both threat and no-threat scenarios.
FAQ
Mini Project
Design and deploy a dynamic template: create a Handlebars template for threat alerts with conditionals and iteration, test with sample data covering all states, activate a version, and send a test email confirming correct rendering.
What's Next
Email Attachments — send emails with file attachments encoded in base64.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro