SendGrid Email Templates — How to Update¶
How to manage SendGrid dynamic email templates via the API and UI.
Prerequisites¶
- SendGrid API key with template read/write permissions
curlandpython3available
Template IDs¶
| Template | ID | Env Var |
|---|---|---|
| Base activity / issues | d-7834854800bb437380ad64a82a16cdd6 |
SENDGRID_EMAIL_TEMPLATE_WITH_LINK |
| Default | d-dbaf71e4acd64f33b030460daffc8056 |
SENDGRID_DEFAULT_EMAIL_TEMPLATE_ID |
| Workspace Invite | d-59939d6fd2344479bd92cbf7fe177109 |
SENDGRID_WORKSPACE_INVITE_TEMPLATE_ID |
| Email Change | d-06c4bd01d9014aee98c179d32d80bb4e |
SENDGRID_EMAIL_CHANGE_TEMPLATE_ID |
| Email Change Confirm | d-e7dc01bce38c4aaca35e61e854f8f97c |
SENDGRID_EMAIL_CHANGE_CONFIRMATION_TEMPLATE_ID |
Step 1: Download a template¶
export SG_KEY="your-sendgrid-api-key"
TEMPLATE_ID="d-59939d6fd2344479bd92cbf7fe177109"
# Fetch template with all versions
curl -s "https://api.sendgrid.com/v3/templates/$TEMPLATE_ID" \
-H "Authorization: Bearer $SG_KEY" > template_raw.json
# Extract the active version's HTML
python3 -c "
import json
with open('template_raw.json') as f:
d = json.load(f)
versions = d.get('versions', [])
active = next((v for v in versions if v.get('active') == 1), versions[0])
with open('template.html', 'w') as f:
f.write(active.get('html_content', ''))
print(f'Saved: {active[\"name\"]} ({active[\"id\"]})')
"
Step 2: Edit the HTML¶
Edit template.html with your changes. For partner white-labeling, use Handlebars conditionals:
<!-- Dynamic logo with configurable width -->
<img style="height:auto !important;
{{#if logo_width}}max-width:{{logo_width}}px; width:{{logo_width}}px;
{{else}}max-width:100%; width:100%;{{/if}}"
alt="{{#if platform_name}}{{platform_name}}{{else}}Stacksync{{/if}}"
src="{{#if logo_url}}{{logo_url}}{{else}}default-logo-url{{/if}}">
<!-- Dynamic text -->
Welcome to {{#if platform_name}}{{platform_name}}{{else}}Stacksync{{/if}}
<!-- Dynamic button color -->
<a style="background-color:{{#if button_color}}{{button_color}}{{else}}#0000ee{{/if}};">
Click here
</a>
<!-- Dynamic support email -->
Contact us at <a href="mailto:{{#if support_email}}{{support_email}}{{else}}hello@stacksync.com{{/if}}">
{{#if support_email}}{{support_email}}{{else}}hello@stacksync.com{{/if}}
</a>
Subject line
The subject parameter in the SendGrid API sets the email envelope subject, but the template's own subject field overrides it. If the template subject is hardcoded (e.g. Stacksync Workspace Invite), the API subject is ignored. Use {{#if subject}}{{subject}}{{else}}Default Subject{{/if}} in the template subject. Also, {{subject}} reads from dynamic_template_data — so the subject must be in the body dict passed to send_email(), not just the function parameter.
Logo sizing
Don't use the HTML width attribute — SendGrid's CSS overrides it with max-width: 100% !important. Use inline style with max-width and width in pixels instead.
SVG logos
Most email clients don't render SVG. Always use PNG for email logos.
Step 3: Upload as inactive version¶
# Build the JSON payload
python3 -c "
import json
with open('template.html') as f:
html = f.read()
payload = {
'active': 0,
'name': 'My update v1 (inactive)',
'html_content': html,
'subject': '{{subject}}',
'editor': 'code',
'generate_plain_content': True,
}
with open('/tmp/sg_payload.json', 'w') as f:
json.dump(payload, f)
"
# Upload
curl -s -X POST "https://api.sendgrid.com/v3/templates/$TEMPLATE_ID/versions" \
-H "Authorization: Bearer $SG_KEY" \
-H "Content-Type: application/json" \
-d @/tmp/sg_payload.json
The new version is inactive — production is unaffected.
Step 4: Preview with test data¶
- Go to
https://mc.sendgrid.com/dynamic-templates/{TEMPLATE_ID}/version/{VERSION_ID}/editor/test_data - Click "test data" in the green bar
- Paste test JSON:
{
"platform_name": "Rillet",
"logo_url": "https://cdn.brandfetch.io/id9Bpy_H9O/w/1612/h/348/theme/dark/idTIk633FO.png",
"logo_width": "80",
"support_email": "hello@rillet.com",
"button_color": "#5B4CFF",
"subject": "Test email"
}
- Verify the preview renders correctly
- Test with empty vars (remove all partner keys) to verify Stacksync fallbacks
Step 5: Activate¶
Via SendGrid UI¶
- Dynamic Templates → click the template
- Find your version → click ⋮ → "Make Active"
To rollback: same steps on the previous version.
Via API¶
# List versions for a template
curl -s "https://api.sendgrid.com/v3/templates/$TEMPLATE_ID" \
-H "Authorization: Bearer $SG_KEY" | python3 -c "
import sys, json
d = json.load(sys.stdin)
for v in d.get('versions', []):
print(f' id={v[\"id\"]} name={v[\"name\"]} active={v[\"active\"]}')
"
# Activate a specific version
VERSION_ID="version-id-here"
curl -s -X POST "https://api.sendgrid.com/v3/templates/$TEMPLATE_ID/versions/$VERSION_ID/activate" \
-H "Authorization: Bearer $SG_KEY" \
-H "Content-Type: application/json"
Bulk activate across multiple templates¶
# Activate v6 versions for all 5 templates at once
for pair in \
"d-7834854800bb437380ad64a82a16cdd6:VERSION_ID_1" \
"d-dbaf71e4acd64f33b030460daffc8056:VERSION_ID_2" \
"d-59939d6fd2344479bd92cbf7fe177109:VERSION_ID_3" \
"d-06c4bd01d9014aee98c179d32d80bb4e:VERSION_ID_4" \
"d-e7dc01bce38c4aaca35e61e854f8f97c:VERSION_ID_5"; do
TMPL="${pair%%:*}" VER="${pair##*:}"
echo "Activating $TMPL..."
curl -s -X POST "https://api.sendgrid.com/v3/templates/$TMPL/versions/$VER/activate" \
-H "Authorization: Bearer $SG_KEY" -H "Content-Type: application/json" | python3 -c \
"import sys,json; d=json.load(sys.stdin); print(f' {d.get(\"name\")} active={d.get(\"active\")}')"
done
Step 6: Delete old versions (optional)¶
VERSION_ID="old-version-id-here"
curl -s -X DELETE "https://api.sendgrid.com/v3/templates/$TEMPLATE_ID/versions/$VERSION_ID" \
-H "Authorization: Bearer $SG_KEY"
Bulk operations¶
To download all templates at once:
# List all dynamic templates
curl -s "https://api.sendgrid.com/v3/templates?generations=dynamic&page_size=50" \
-H "Authorization: Bearer $SG_KEY" | python3 -c "
import sys, json
for t in json.load(sys.stdin).get('result', []):
print(f'{t[\"id\"]} — {t[\"name\"]}')
"
Always keep local backups of templates before and after editing in case you need to rollback manually.