Skip to content

Email Sending

Greg Svoboda edited this page Mar 4, 2026 · 1 revision

Email Sending

All email sending is done through ServerClient. You will need a server API token.

import postmark

client = postmark.ServerClient("your-server-token")

Simple Email

Send a single email using either a dict or the Email model.

import asyncio
from postmark import Email

async def main():
    # Using a dict
    response = await client.outbound.send({
        "sender": "you@example.com",
        "to": "recipient@example.com",
        "subject": "Hello from Postmark",
        "text_body": "Hello!",
        "html_body": "<p>Hello!</p>",
        "message_stream": "outbound",
    })
    print(f"Message ID: {response.message_id}")

    # Using the Email model (recommended)
    response = await client.outbound.send(
        Email(
            sender="you@example.com",
            to="recipient@example.com",
            subject="Hello via Model",
            text_body="Hello!",
            metadata={"user_id": "12345"},
        )
    )
    print(f"Message ID: {response.message_id}")

asyncio.run(main())

Tracking Options

Enable open and link tracking on outgoing messages.

response = await client.outbound.send(
    Email(
        sender="you@example.com",
        to="recipient@example.com",
        subject="Track me",
        html_body="<p>Click <a href='https://example.com'>here</a>.</p>",
        track_opens=True,
        track_links="HtmlAndText",  # "None", "HtmlAndText", "HtmlOnly", "TextOnly"
    )
)

Custom Headers

Pass arbitrary email headers using the headers field.

from postmark.models.messages import Email, Header

response = await client.outbound.send(
    Email(
        sender="you@example.com",
        to="recipient@example.com",
        subject="Custom headers",
        text_body="Hello!",
        headers=[
            Header(name="X-Custom-Header", value="my-value"),
            Header(name="Reply-To", value="reply@example.com"),
        ],
    )
)

Attachments

Attachment content must be Base64-encoded. Use Python's standard base64 module.

import asyncio
import base64
from postmark.models.messages import Attachment, Email

async def main():
    # Attachment from in-memory content
    report = Attachment(
        name="report.txt",
        content=base64.b64encode(b"Q3 sales are up 12%.").decode("utf-8"),
        content_type="text/plain",
    )

    # Attachment from a file on disk
    with open("/path/to/document.pdf", "rb") as f:
        pdf = Attachment(
            name="document.pdf",
            content=base64.b64encode(f.read()).decode("utf-8"),
            content_type="application/pdf",
        )

    response = await client.outbound.send(
        Email(
            sender="you@example.com",
            to="recipient@example.com",
            subject="Your files",
            text_body="Please find your files attached.",
            attachments=[report, pdf],
        )
    )
    print(f"Sent: {response.message_id}")

asyncio.run(main())

Embedding Images in Emails

Use a content_id on an attachment to embed images inline. Reference it in the HTML body as <img src="cid:...">.

import base64
from postmark.models.messages import Attachment, Email

with open("/path/to/logo.png", "rb") as f:
    inline_logo = Attachment(
        name="logo.png",
        content=base64.b64encode(f.read()).decode("utf-8"),
        content_type="image/png",
        content_id="cid:logo",
    )

response = await client.outbound.send(
    Email(
        sender="you@example.com",
        to="recipient@example.com",
        subject="Check out our logo",
        html_body='<p>Here is our logo:</p><img src="cid:logo">',
        attachments=[inline_logo],
    )
)

Batch Sending

Send multiple distinct emails in a single API call.

Important: The API always returns HTTP 200, even when individual messages fail. Check response.success (or response.error_code == 0) on each item — a truthy HTTP status does not mean every message was accepted.

import asyncio

async def main():
    responses = await client.outbound.send_batch([
        {
            "sender": "you@example.com",
            "to": "alice@example.com",
            "subject": "Hi Alice",
            "text_body": "Hello Alice!",
        },
        {
            "sender": "you@example.com",
            "to": "bob@example.com",
            "subject": "Hi Bob",
            "text_body": "Hello 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())

Bulk Sending

Send the same message to many recipients, each with their own template variables. Uses BulkEmail and BulkRecipient.

import asyncio
from postmark.models.messages import BulkEmail, BulkRecipient

async def main():
    response = await client.outbound.send_bulk(
        BulkEmail(
            sender="you@example.com",
            subject="Hello {{FirstName}}, your order is ready",
            html_body="<p>Hi {{FirstName}}, your order <strong>{{OrderId}}</strong> is ready.</p>",
            text_body="Hi {{FirstName}}, your order {{OrderId}} is ready.",
            message_stream="broadcast",
            messages=[
                BulkRecipient(
                    to="alice@example.com",
                    template_model={"FirstName": "Alice", "OrderId": "ORD-001"},
                ),
                BulkRecipient(
                    to="bob@example.com",
                    template_model={"FirstName": "Bob", "OrderId": "ORD-002"},
                ),
            ],
        )
    )
    print(f"Bulk request ID: {response.id}  Status: {response.status}")

    # Poll for completion
    status = await client.outbound.get_bulk_status(response.id)
    print(f"{status.percentage_completed:.0f}% of {status.total_messages} messages sent")

asyncio.run(main())

Further Reading

Clone this wiki locally