Dynamic data is a plain dict passed as the second positional argument (or dynamic_data= keyword) to any render method.
Structure
# TypedDict equivalent
DynamicData = {
"merge_tags": { # optional
"text": dict[str, str], # paragraph, heading, button text
"url": dict[str, str], # href, src, image URLs
"attr": dict[str, str], # alt, aria-label, etc.
},
"context": dict[str, str | int | float | bool], # optional
}
All fields are optional.
Merge tags
text — inline content
client.render_html("template-id", {
"merge_tags": {
"text": {
"name": "Noruwa",
"company": "Maildeno",
"reset_name": "Password",
},
},
})
Values are HTML-escaped before insertion.
Context
client.render_html("template-id", {
"context": {
"plan": "pro",
"country": "usa",
"age": 25,
},
})
Context values drive visibility rules and are never rendered into content.
Full example
client.render(
template_id="550e8400-e29b-41d4-a716-446655440000",
target="mjml",
dynamic_data={
"merge_tags": {
"text": {
"name": "Noruwa",
"company": "Maildeno",
"reset_name": "Password",
},
"url": {
"reset_url": "https://app.example.com/reset/abc123",
},
"attr": {
"alt_text": "Cave image",
},
},
"context": {
"country": "usa",
"country_rank": "2",
"expiry": "2028",
},
},
)
TypedDict usage (type-safe)
Because DynamicData is a TypedDict, you can pass typed dicts for IDE autocompletion and static analysis:
from maildeno import DynamicData, MergeTagGroup
data: DynamicData = {
"merge_tags": MergeTagGroup(
text={"name": "Noruwa"},
url={"reset_url": "https://app.example.com/reset/abc123"},
),
"context": {"plan": "pro"},
}
html = client.render_html("template-id", data)