Skip to content
Greg Svoboda edited this page Oct 28, 2026 · 2 revisions

Stats

Statistics retrieval is done through ServerClient. You will need a server API token.

import postmark

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

All stat methods support optional from_date, to_date, tag, and message_stream filters.

Postmark leaves out any category whose count is zero (for example a day with only desktop opens has no Mobile entry). The SDK reports those as 0, and an empty result has days == [], so you can read every field without checking for it first.

Outbound Overview

Get a high-level summary of all outbound activity.

import asyncio
from datetime import date

async def main():
    result = await client.stats.overview(
        from_date=date(2024, 1, 1),
        to_date=date(2024, 1, 31),
    )

    print(f"Sent:            {result.sent}")
    print(f"Bounced:         {result.bounced}")
    print(f"Bounce rate:     {result.bounce_rate:.2%}")
    print(f"Spam complaints: {result.spam_complaints}")
    print(f"Opens:           {result.opens}")
    print(f"Unique opens:    {result.unique_opens}")
    print(f"Total clicks:    {result.total_clicks}")

asyncio.run(main())

Sent Counts

Daily breakdown of sent message counts.

from datetime import date

async def main():
    result = await client.stats.sent_counts(
        from_date=date(2024, 1, 1),
        to_date=date(2024, 1, 31),
    )

    print(f"Total sent: {result.sent}")
    for day in result.days:
        print(f"  {day.date}:  {day.sent} sent")

Bounce Counts

Daily breakdown of bounces by type.

from datetime import date

async def main():
    result = await client.stats.bounce_counts(
        from_date=date(2024, 1, 1),
        to_date=date(2024, 1, 31),
    )

    print(f"Hard bounces:  {result.hard_bounce}")
    print(f"Soft bounces:  {result.soft_bounce}")
    for day in result.days:
        print(f"  {day.date}:  hard={day.hard_bounce}  soft={day.soft_bounce}")

Spam Complaints

async def main():
    result = await client.stats.spam_counts(
        from_date=date(2024, 1, 1),
        to_date=date(2024, 1, 31),
    )
    print(f"Spam complaints: {result.spam_complaint}")
    for day in result.days:
        print(f"  {day.date}:  {day.spam_complaint}")

Tracked Email Counts

async def main():
    result = await client.stats.tracked_counts(
        from_date=date(2024, 1, 1),
        to_date=date(2024, 1, 31),
    )
    print(f"Tracked: {result.tracked}")

Email Opens

async def main():
    result = await client.stats.open_counts(
        from_date=date(2024, 1, 1),
        to_date=date(2024, 1, 31),
    )
    print(f"Opens: {result.opens}  Unique: {result.unique}")
    for day in result.days:
        print(f"  {day.date}:  opens={day.opens}  unique={day.unique}")

Platform Usage

Device/platform breakdown for opens (desktop, mobile, webmail).

async def main():
    result = await client.stats.platform_usage(
        from_date=date(2024, 1, 1),
        to_date=date(2024, 1, 31),
    )
    print(f"Desktop: {result.desktop}")
    print(f"Mobile:  {result.mobile}")
    print(f"Unknown: {result.unknown}")
    print(f"Webmail: {result.web_mail}")

Email Client Usage

async def main():
    result = await client.stats.email_client_usage(
        from_date=date(2024, 1, 1),
        to_date=date(2024, 1, 31),
    )
    # Client names are dynamic keys, so each day is a plain dict
    for day in result.days:
        print(day)  # {"Date": "2024-01-01", "Apple Mail": 12, "Gmail": 7, ...}
    print(result.model_extra)  # totals per client, e.g. {"Apple Mail": 12, "Gmail": 7}

Read Times

async def main():
    result = await client.stats.read_times(
        from_date=date(2024, 1, 1),
        to_date=date(2024, 1, 31),
    )
    # Read-time buckets are dynamic keys, so each day is a plain dict
    for day in result.days:
        print(day)  # {"Date": "2024-01-01", "<bucket>": 5, ...}
    print(result.model_extra)  # totals per bucket

Click Counts

async def main():
    result = await client.stats.click_counts(
        from_date=date(2024, 1, 1),
        to_date=date(2024, 1, 31),
    )
    print(f"Clicks: {result.clicks}  Unique: {result.unique}")
    for day in result.days:
        print(f"  {day.date}:  clicks={day.clicks}  unique={day.unique}")

Browser Usage

async def main():
    result = await client.stats.browser_usage(
        from_date=date(2024, 1, 1),
        to_date=date(2024, 1, 31),
    )
    # Browser names are dynamic keys, so each day is a plain dict
    for day in result.days:
        print(day)  # {"Date": "2024-01-01", "Chrome": 12, "Safari": 3, ...}
    print(result.model_extra)  # totals per browser, e.g. {"Chrome": 12, "Safari": 3}

Browser Platform Clicks

async def main():
    result = await client.stats.browser_platform_usage(
        from_date=date(2024, 1, 1),
        to_date=date(2024, 1, 31),
    )
    print(f"Desktop: {result.desktop}")
    print(f"Mobile:  {result.mobile}")
    print(f"Unknown: {result.unknown}")

Click Location

async def main():
    result = await client.stats.click_location(
        from_date=date(2024, 1, 1),
        to_date=date(2024, 1, 31),
    )
    print(f"HTML: {result.html}  Text: {result.text}")
    for day in result.days:
        print(f"  {day.date}:  html={day.html}  text={day.text}")

Further Reading

Clone this wiki locally