SendGrid Substitution Tags — Legacy Template Personalization with -name- Syntax
In this tutorial, you will learn about SendGrid Substitution Tags. We cover key concepts, practical examples, and best practices to help you master this topic.
SendGrid substitution tags use -name- syntax (hyphen-delimited) to replace placeholders in legacy templates with dynamic values, enabling personalization without the need for Handlebars-based dynamic templates.
What You'll Learn
- How substitution tags work in legacy templates
- How to pass substitution values in API requests
- When to use substitution tags vs dynamic templates
Why It Matters
Many existing SendGrid integrations use substitution tags. Understanding them is essential for maintaining legacy systems. While dynamic templates are preferred for new projects, substitution tags remain widely deployed and supported.
Real-World Use
DodaTech's legacy billing system uses substitution tags for invoice emails. The template contains -customerName-, -invoiceId-, and -totalAmount- tags. The billing system substitutes these values before sending, producing personalized invoices without modifying the template HTML.
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail, Substitution
def send_legacy_invoice(to_email, customer_name, invoice_id, amount):
message = Mail(
from_email='billing@dodatech.com',
to_emails=to_email,
subject=f'Invoice #{invoice_id}',
html_content='''
<h1>Invoice #-invoiceId-</h1>
<p>Dear -customerName-,</p>
<p>Your invoice for -totalAmount- is ready.</p>
<p>View invoice: <a href="https://dodatech.com/invoices/-invoiceId-">Link</a></p>
'''
)
# Add substitutions
message.add_substitution(Substitution('-customerName-', customer_name))
message.add_substitution(Substitution('-invoiceId-', str(invoice_id)))
message.add_substitution(Substitution('-totalAmount-', f'${amount:.2f}'))
sg = SendGridAPIClient(SENDGRID_API_KEY)
response = sg.send(message)
return response.status_code == 202
Per-Recipient Substitutions
def send_bulk_with_substitutions(recipients):
"""Send the same email to multiple recipients with different substitutions"""
personalizations = []
for r in recipients:
personalization = {
"to": [{"email": r["email"]}],
"substitutions": {
"-name-": r["name"],
"-product-": r["product"],
"-price-": f"${r['price']:.2f}",
"-discount-": r.get("discount", "0%"),
"-expiry-": r["expiry"]
}
}
personalizations.append(personalization)
response = requests.post(
"https://api.sendgrid.com/v3/mail/send",
headers={
"Authorization": f"Bearer {SENDGRID_API_KEY}",
"Content-Type": "application/json"
},
json={
"personalizations": personalizations,
"from": {"email": "offers@dodatech.com"},
"subject": "Special Offer for -name-!",
"content": [{"type": "text/html", "value": '''
<h1>Hi -name-!</h1>
<p>We have a special offer on -product-.</p>
<p>Regular price: -price-</p>
<p>Your discount: -discount-</p>
<p>Offer expires: -expiry-</p>
'''}]
}
)
return response.status_code == 202
Section Tags
Section tags reuse common HTML blocks across templates:
def send_with_sections(to_email, section_name):
message = Mail(
from_email='noreply@dodatech.com',
to_emails=to_email,
subject='Your DodaTech Update'
)
# Define sections
sections = {
"-header-": '''
<div style="background:#2563eb;color:white;padding:20px;">
<h1>DodaTech</h1>
</div>
''',
"-footer-": '''
<div style="background:#f3f4f6;padding:10px;margin-top:20px;">
<p>DodaTech Inc. - Built by the developers of Doda Browser</p>
<p><a href="https://dodatech.com/unsubscribe">Unsubscribe</a></p>
</div>
''',
"-promo-" if section_name == "premium" else "-standard-": '''
<div style="border:2px solid gold;padding:15px;">
<h2>Premium Feature Available!</h2>
</div>
''' if section_name == "premium" else '''
<div style="padding:15px;">
<p>Standard features included.</p>
</div>
'''
}
message.html_content = '''
-header-
<div style="padding:20px;">
<h2>Welcome, -name-!</h2>
<p>Your account is ready.</p>
-content-
</div>
-footer-
'''
Common Mistakes
1. Using Substitution Tags in Dynamic Templates
Substitution tags only work with legacy templates. Dynamic templates use Handlebars {{ }} syntax. Mixing them causes rendering errors.
2. Not Escaping HTML in Substitutions
If a substitution value contains HTML, it is rendered as HTML. Escape or strip HTML tags from user-provided substitution values.
3. Forgetting Hyphen Delimiters
Substitution tags require hyphens around the tag name: -name-, not -name or name-.
4. Case-Sensitive Tag Mismatches
Substitution tags are case-sensitive. -Name- and -name- are different tags. Be consistent.
5. Using Substitutions for Conditional Logic
Substitution tags do not support conditions or loops. For conditional content, use dynamic templates with Handlebars.
Practice Questions
- What syntax do substitution tags use?
- How do you add a substitution to a Mail object?
- Can substitution tags support conditional logic?
- What is the difference between substitution and section tags?
- When should you use dynamic templates instead of substitution tags?
Answers
- Hyphen-delimited:
-tagName-. 2.message.add_substitution(Substitution('-tag-', 'value')). 3. No, they are simple replacements without logic. 4. Sections are reusable blocks; substitutions are simple value replacements. 5. When you need conditional logic, loops, or easier template management.
Challenge
Build a legacy template compatibility layer that accepts both dynamic template data and substitution data, detects which template type is being used, and correctly applies the appropriate rendering method.
FAQ
Mini Project
Build a template rendering service that supports both substitution tags and dynamic templates. Include detection of template type, proper substitution value escaping, section tag support, and a Migration tool that converts legacy templates to dynamic templates.
What's Next
- Learn about section tags for reusable content blocks
- Explore scheduling emails for delayed delivery
- Continue to sandbox mode for testing without sending
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro