Skip to content

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
  • curl and python3 available

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

  1. Go to https://mc.sendgrid.com/dynamic-templates/{TEMPLATE_ID}/version/{VERSION_ID}/editor/test_data
  2. Click "test data" in the green bar
  3. 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"
}
  1. Verify the preview renders correctly
  2. Test with empty vars (remove all partner keys) to verify Stacksync fallbacks

Step 5: Activate

Via SendGrid UI

  1. Dynamic Templates → click the template
  2. 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.