SendGrid Email Personalization: Dynamic Content Per Recipient
In this tutorial, you will learn about SendGrid Email Personalization: Dynamic Content Per Recipient. We cover key concepts, practical examples, and best practices to help you master this topic.
SendGrid personalization lets you send a single email to thousands of recipients with content customized per recipient using personalizations blocks and substitution tags.
What You'll Learn
How to use SendGrid personalizations for batch sending with per-recipient dynamic data, using substitution tags, custom arguments, and the personalizations array.
Why It Matters
Sending individual API calls for each recipient is inefficient and slow. Personalizations send to 1000 recipients in one API call. DodaTech sends weekly security reports to 50K users in 50 API calls.
Real-World Use
A weekly security report: each user gets their personalized threat summary. One API call with 1000 personalizations sends 1000 customized emails from a single template.
flowchart LR
A["One API Call\nPOST /v3/mail/send"] --> B["Personalizations\nArray [1000 items]"]
B --> C["Recipient 1\nDynamic Data A"]
B --> D["Recipient 2\nDynamic Data B"]
B --> E["Recipient N\nDynamic Data N"]
C --> F["SendGrid\n1000 Individual Emails"]
style A fill:#dbeafe,stroke:#2563eb
style B fill:#fef3c7,stroke:#d97706
style F fill:#bbf7d0,stroke:#16a34a
Basic Personalization
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail, Personalization, Email
def send_personalized_batch(users):
message = Mail()
message.from_email = Email("reports@dodatech.com", "DodaTech Security")
message.subject = "Your Weekly Security Report"
message.template_id = "d-template123"
for user in users:
personalization = Personalization()
personalization.add_to(Email(user["email"]))
personalization.dynamic_template_data = {
"name": user["name"],
"threat_count": user["threats"],
"report_url": user["report_url"],
"scan_date": user["last_scan"]
}
message.add_personalization(personalization)
response = sg.send(message)
print(f"Sent {len(users)} personalized emails: {response.status_code}")
users = [
{"email": "alice@example.com", "name": "Alice", "threats": 0, "report_url": "https://dodatech.com/reports/alice", "last_scan": "2026-06-27"},
{"email": "bob@example.com", "name": "Bob", "threats": 3, "report_url": "https://dodatech.com/reports/bob", "last_scan": "2026-06-28"},
{"email": "carol@example.com", "name": "Carol", "threats": 1, "report_url": "https://dodatech.com/reports/carol", "last_scan": "2026-06-26"},
]
send_personalized_batch(users)
# Expected output: Sent 3 personalized emails: 202
Substitution Tags
# Alternative to dynamic templates — substitution tags
# Template: "Hello {{name}}, you have {{threats}} threats"
message = Mail()
message.from_email = "reports@dodatech.com"
message.subject = "Security Alert - {{subject_custom}}"
message.plain_text_content = "Hello {{name}},\n\nYour report: {{report_url}}"
for user in users:
personalization = Personalization()
personalization.add_to(Email(user["email"]))
personalization.add_substitution("{{name}}", user["name"])
personalization.add_substitution("{{threats}}", str(user["threats"]))
personalization.add_substitution("{{report_url}}", user["report_url"])
message.add_personalization(personalization)
sg.send(message)
Custom Arguments
# Add metadata that comes back in event webhooks
personalization = Personalization()
personalization.add_to(Email("alice@example.com"))
personalization.dynamic_template_data = {"name": "Alice"}
personalization.add_custom_arg("user_id", "usr_12345")
personalization.add_custom_arg("campaign", "weekly_report")
personalization.add_custom_arg("tier", "pro")
# These appear in Event Webhook payloads:
# {
# "event": "open",
# "email": "alice@example.com",
# "custom_args": {
# "user_id": "usr_12345",
# "campaign": "weekly_report"
# }
# }
Common Mistakes
1. Exceeding 1000 Personalizations
The API limit is 1000 personalizations per call. Split batches larger than 1000 into multiple API calls.
2. Missing Required Dynamic Data
If a template uses {{name}} but a personalization doesn't provide it, SendGrid renders it as empty. Always validate that all required fields exist for each recipient.
3. Using Wrong Personalization Type
For dynamic templates (template_id), use dynamic_template_data. For legacy templates, use substitutions. Mixing them causes unexpected results.
4. Not Setting a Subject Fallback
When using personalizations, the subject can be overridden per personalization. If some personalizations lack a subject, the message-level subject is used as fallback.
5. Forgetting to Call add_personalization
Creating a Personalization object doesn't add it to the message. You must call message.add_personalization(p) for each one.
Practice Questions
- What is the maximum recipients per API call?
- How do personalizations differ from individual sends?
- What are custom arguments used for?
- How do you handle per-recipient subject lines?
Answers:
- 1000 personalizations per API call. For more, split into multiple calls.
- Personalizations send to multiple recipients in one API call with per-recipient data. Individual sends require one API call per recipient.
- Custom arguments are metadata attached to each personalization that return in event Webhooks, enabling per-recipient tracking and analysis.
- Set
personalization.subjecton each Personalization object. If not set, the message-level subject is used.
Challenge: Build a batch notification system: send personalized threat alerts to 500 users using personalizations, include per-user custom arguments, handle the 1000-recipient limit with pagination, and verify each personalization has all required data.
FAQ
Mini Project
Build a batch email system: send personalized weekly security reports to 100 users using personalizations with dynamic template data, include custom arguments for tracking, handle the 1000-personalizations limit, and validate all dynamic data before sending.
What's Next
Dynamic Templates — design and use Handlebars templates for scalable email sending.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro