Repository navigation
Templates
Template management is done through ServerClient. Template pushes across servers use AccountClient.
import postmark
client = postmark.ServerClient("your-server-token")
account = postmark.AccountClient("your-account-token")Send a single email using a Postmark template. Reference the template by ID or alias.
import asyncio
from postmark.models.templates import TemplateEmail
async def main():
# By template ID
response = await client.outbound.send_with_template(
TemplateEmail(
sender="you@example.com",
to="recipient@example.com",
template_id=12345,
template_model={
"name": "Alice",
"action_url": "https://example.com/confirm",
},
)
)
print(f"Sent: {response.message_id}")
# By template alias (using a dict)
response = await client.outbound.send_with_template({
"From": "you@example.com",
"To": "recipient@example.com",
"TemplateAlias": "welcome-email",
"TemplateModel": {
"name": "Bob",
"action_url": "https://example.com/confirm",
},
})
print(f"Sent: {response.message_id}")
asyncio.run(main())Send multiple templated emails in a single API call.
import asyncio
async def main():
responses = await client.outbound.send_batch_with_template([
{
"From": "you@example.com",
"To": "alice@example.com",
"TemplateAlias": "welcome-email",
"TemplateModel": {"name": "Alice"},
},
{
"From": "you@example.com",
"To": "bob@example.com",
"TemplateAlias": "welcome-email",
"TemplateModel": {"name": "Bob"},
},
])
for resp in responses:
if resp.success:
print(f" Sent: {resp.message_id}")
else:
print(f" Failed [error_code={resp.error_code}]: {resp.message}")
asyncio.run(main())As with plain batches, Postmark answers HTTP 200 even when an entry fails (for example an unknown template alias), so check success on every result. A failed entry has no message_id.
async def main():
result = await client.templates.list()
print(f"Total: {result.total}")
for t in result.items:
print(f" [{t.template_id}] {t.name} alias={t.alias}")from postmark.models.templates import TemplateTypeFilter
async def main():
# Only standard templates, or only layouts
standard = await client.templates.list(template_type=TemplateTypeFilter.STANDARD)
layouts = await client.templates.list(template_type=TemplateTypeFilter.LAYOUT)
# Only the templates that use a particular layout (give the layout's alias)
branded = await client.templates.list(layout_template="my-layout")
for t in branded.items:
print(f" {t.alias} uses {t.layout_template}")-
layout_templatetakes the layout's alias, not its numeric ID. An alias that does not exist is an error (ValidationException), not an empty list. - Both filters can be combined with
countandoffset(countup to 500).
Retrieve a template by its ID or alias.
async def main():
# By ID
template = await client.templates.get(12345)
print(f"Name: {template.name}")
print(f"Subject: {template.subject}")
# By alias
template = await client.templates.get("welcome-email")import asyncio
from postmark.models.templates import CreateTemplateRequest
async def main():
result = await client.templates.create(
CreateTemplateRequest(
name="Welcome Email",
alias="welcome-email",
subject="Welcome, {{name}}!",
html_body="<p>Hi {{name}}, thanks for joining!</p>",
text_body="Hi {{name}}, thanks for joining!",
)
)
print(f"Created: id={result.template_id} alias={result.alias}")
asyncio.run(main())async def main():
result = await client.templates.edit(
12345,
{"Subject": "Updated subject — {{name}}!"},
)
print(f"Updated: {result.template_id}")async def main():
await client.templates.delete(12345)
print("Deleted")Check template syntax and preview rendered output before sending.
async def main():
result = await client.templates.validate({
"subject": "Hello, {{name}}!",
"html_body": "<p>Hi {{name}}</p>",
"test_render_model": {"name": "Alice"}, # sample data used to render a preview
})
print(f"All content valid: {result.all_content_is_valid}")
print(f"Subject valid: {result.subject.content_is_valid}")
print(f"Rendered subject: {result.subject.rendered_content}")
for err in result.html_body.validation_errors:
print(f" line {err.line}: {err.message}")The request is a flat set of fields (subject, html_body, text_body, test_render_model, ...) and the result has one entry per part you sent. A part you did not send is None.
Copy templates from one server to another using AccountClient. Server IDs are the numeric IDs shown in Postmark; they can be passed as integers or as strings.
import asyncio
from postmark.models.templates import PushTemplatesRequest
async def main():
# Dry run — preview what would change
result = await account.templates.push({
"SourceServerID": 12345,
"DestinationServerID": 67890,
"PerformChanges": False,
})
print(f"Dry run — {result.total_count} template(s) would be affected:")
for t in result.templates:
print(f" action={t.action} [{t.template_id}] {t.name}")
# Apply — push templates for real
result = await account.templates.push(
PushTemplatesRequest(
SourceServerID=12345,
DestinationServerID=67890,
PerformChanges=True,
)
)
print(f"Pushed — {result.total_count} template(s) updated.")
asyncio.run(main())