Skip to content

SendGrid Dynamic Templates: Design & Send Handlebars-Powered Emails

DodaTech Updated 2026-06-28 5 min read

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

  1. What is the difference between {{variable}} and {{{variable}}} in Handlebars?
  2. How do you create conditional sections in dynamic templates?
  3. How do you iterate over arrays in a template?
  4. How do you manage template versions?

Answers:

  1. {{variable}} HTML-escapes the value. {{{variable}}} renders raw HTML. Use triple brackets for trusted HTML content.
  2. Use {{#if condition}} and {{else}} blocks. Conditions can be truthy/falsy checks or comparison helpers like {{#if (gt threat_count 0)}}.
  3. Use {{#each array}}...{{/each}} to iterate. Inside the loop, {{this}} refers to the current item, and {{@index}} gives the index.
  4. 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

How many dynamic templates can I create?

SendGrid allows up to 300 dynamic templates per account.

Can I use Handlebars helpers like `{{#each}}`?

Yes, SendGrid supports standard Handlebars helpers: each, if, unless, with, and comparison helpers (gt, lt, eq, etc.).

What happens if dynamic data exceeds the variable size limit?

Individual variable values are limited to 32KB. Larger values are truncated. Use external URLs for large content.

Can I preview templates with my own data?

Yes, use the Preview tab in SendGrid Dashboard with sample JSON data to see rendered output before sending.

Can I use dynamic templates with A/B testing?

Yes, SendGrid supports A/B testing with dynamic templates, allowing subject line and content variation testing.

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