sendgrid dynamic template data not working

SendGrid dynamic template data renders blank when key names don't match Handlebars variables, data is outside personalizations, or a legacy ID is used.

Updated Jul 1, 2026

The short answer

"SendGrid dynamic template data not working" means your email sends but the Handlebars variables render blank or literal instead of your values. The usual causes are mismatched field names (the JSON key must exactly match the {{handlebar}}, alphanumeric/underscore only), dynamic_template_data placed outside personalizations, using a legacy template ID instead of a d- dynamic ID, or mixing substitutions with dynamic templates.

"Dynamic template data not working" is not an SMTP reply code or a named SendGrid API error — it's the common description of a successful send (HTTP 202 Accepted) in which the Handlebars variables in your dynamic template come out empty or appear as literal {{text}} instead of your data. Because the API returns 202, nothing fails loudly; the breakdown is in how the template and the dynamic_template_data payload line up. Below are the concrete causes, most common first.

What causes SendGrid dynamic template data to render blank?

  1. Field name mismatch. A Handlebars tag only resolves if a key with the exact same name exists in dynamic_template_data. {{firstName}} will not be filled by first_name. Missing or misspelled keys render as empty strings silently (Using Handlebars).
  2. Illegal characters in the field name. Field names must use only alphanumeric characters (A–Z, 0–9) and underscores, and must start with a letter or underscore. Spaces are the classic trap: {{first name}} saves without an error but never substitutes (Handlebars Syntax Errors).
  3. dynamic_template_data in the wrong place. It must live inside each personalizations object, not at the top level of the request body. Top-level data is ignored.
  4. Wrong template type / ID. The template_id must be a Dynamic template ID — it starts with d- followed by a 32-character hexadecimal string (a UUID with its dashes removed), for example d-f43daeeaef504760851f727007e0b5d0. A legacy transactional template ID will not interpret Handlebars; legacy templates use substitutions with -tag- syntax instead.
  5. Mixing substitutions with a dynamic template. These are mutually exclusive. Sending both returns the API error "Substitutions may not be used with dynamic templating."
  6. No active version. A dynamic template renders the version marked active in the UI. If you never clicked "Make Active," there is no content to populate.
  7. Unbalanced braces or HTML escaping. {{unbalanced}}} breaks rendering. Values containing HTML or ' " & are HTML-escaped by {{ }}; use triple braces {{{ value }}} to output raw HTML.

How do I fix SendGrid dynamic template data?

Step 1 — Confirm placement and ID. Put dynamic_template_data inside personalizations and use the d-... ID:

{
"from": { "email": "you@example.com" },
"personalizations": [
{
"to": [{ "email": "user@example.com" }],
"dynamic_template_data": { "first_name": "Ada", "order_id": 1234 }
}
],
"template_id": "d-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}

Step 2 — Match names exactly. Open the active version of the template and make every {{key}} match a key in your JSON character-for-character. Keep names to letters, digits, and underscores; no spaces.

Step 3 — Remove conflicts. Delete any substitutions block from the request when using a dynamic template. Use one system or the other.

Step 4 — Test with the editor's preview data. The template editor lets you paste test JSON. If it renders there but not in your send, the data shape differs from your live payload — log the exact body you send. (Note: variables nested inside greaterThan/equals/and/or conditionals can look blank in the editor preview yet render correctly in the delivered email — a known editor-only display bug.)

Step 5 — Handle nested data and HTML. Use dot notation for nested objects ({{user.profile.name}}), {{#each items}}...{{this.name}}...{{/each}} for arrays, and {{{html_block}}} when the value is intended to be raw HTML.

With Courier

If you route SendGrid through Courier, you do not hand-build dynamic_template_data — Courier maps the data you pass to send() into the template variables. Confirm your SendGrid provider is configured, the variable names in the SendGrid template match the keys in your Courier data object, and that you are referencing the correct dynamic template.

References

Authoritative sources

DOCS

SendGrid Docs — Using Handlebars

HELP

SendGrid Support — Handlebars Syntax Errors in Templates

DOCS

sendgrid-ruby Issue #299 — Substitutions may not be used with dynamic templating

DOCS

sendgrid/docs Issue #6237 — Conditional substitution editor-preview bug

FAQ

Common questions

A successful send returns HTTP 202 regardless of whether variables resolved. Blank values mean the keys in dynamic_template_data don't exactly match the {{handlebar}} names, the data was placed outside the personalizations object, or you used a legacy template ID instead of a dynamic (d-) one. SendGrid substitutes missing keys with an empty string rather than erroring.

It means your request included both a substitutions block and a dynamic (d-) template_id. They are mutually exclusive: legacy templates use substitutions with -tag- syntax, while dynamic templates use dynamic_template_data with Handlebars {{ }}. Remove the substitutions block and pass your values only in dynamic_template_data.

Literal {{ }} usually means the template_id is a legacy transactional template (not a d- dynamic template), so Handlebars is never evaluated. It can also mean no template version is set active. Switch to a Dynamic template ID and mark a version active.

Keep going

Related SendGrid errors

sendgrid substitutions not working

sendgrid css not working

sendgrid from name not working

Mandrill Merge Vars Not Working

406

SendGrid 406: Not Acceptable

View all errors →

One API, every provider

Stop debugging raw provider errors

Courier connects to your email, SMS, and push providers, handles retries and failover, and surfaces delivery errors in plain language.

Start building freeRead the docs

Last reviewed Jul 1, 2026. Courier is not affiliated with third-party providers; error behavior may vary by implementation.

Multichannel Notifications Platform for SaaS

Products

Platform

Integrations

Customers

Blog

API Status

Subprocessors

© 2026 Courier. All rights reserved.