Skip to content

Error Handling

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

Error Handling

The library raises typed exceptions so you can handle specific error conditions precisely.

Exception Hierarchy

PostmarkException                   # Base for all library exceptions
├── InvalidEmailException           # Local Pydantic validation failure (before API call)
├── TimeoutException                # Request timed out
└── PostmarkAPIException            # Base for errors returned by the Postmark API
    ├── InvalidAPIKeyException      # 401 – Invalid or missing API key
    ├── InactiveRecipientException  # 406 – Recipient is inactive
    ├── ValidationException         # 422 – Invalid request parameters
    ├── RateLimitException          # 429 – Rate limit exceeded
    └── ServerException             # 500/503 – Postmark server error

All PostmarkAPIException subclasses carry:

  • message — Human-readable error description
  • error_code — Postmark error code (from the response body)
  • http_status — HTTP status code

Automatic Retries

The client automatically retries requests that fail with RateLimitException (429), ServerException (500/503), or TimeoutException. It uses exponential backoff with jitter and retries up to 3 times by default.

If the request still fails after all retries, the final exception is raised normally so you can handle it in your code.

To change the retry count, pass retries=N when constructing the client (see Getting Started).

Basic Error Handling

import asyncio
import postmark
from postmark.exceptions import (
    PostmarkAPIException,
    InvalidAPIKeyException,
    InactiveRecipientException,
    ValidationException,
    RateLimitException,
    ServerException,
    TimeoutException,
)

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

async def main():
    try:
        response = await client.outbound.send({
            "sender": "you@example.com",
            "to": "recipient@example.com",
            "subject": "Hello",
            "text_body": "Hello!",
        })
        print(f"Sent: {response.message_id}")

    except InvalidAPIKeyException as e:
        print(f"Bad API key: {e.message}")

    except InactiveRecipientException as e:
        print(f"Inactive recipients: {e.inactive_recipients}")

    except ValidationException as e:
        print(f"Validation error [{e.error_code}]: {e.message}")

    except RateLimitException:
        print("Rate limited — slow down requests")

    except ServerException as e:
        print(f"Postmark server error (HTTP {e.http_status}): {e.message}")

    except TimeoutException:
        print("Request timed out")

    except PostmarkAPIException as e:
        print(f"Postmark API error [{e.error_code}]: {e.message}")

asyncio.run(main())

Handling Inactive Recipients

InactiveRecipientException includes a parsed list of the inactive addresses.

from postmark.exceptions import InactiveRecipientException

try:
    await client.outbound.send({...})
except InactiveRecipientException as e:
    for addr in e.inactive_recipients:
        print(f"Inactive: {addr}")

Local Validation Errors

InvalidEmailException is raised before any API call when the email data fails Pydantic model validation. It carries the raw Pydantic errors.

It can be imported from either the top-level postmark package or directly from postmark.exceptions:

# Top-level import (recommended)
from postmark import InvalidEmailException

# Or from the exceptions module
from postmark.exceptions import InvalidEmailException

try:
    await client.outbound.send({
        "sender": "not-an-email",  # Invalid
        "to": "recipient@example.com",
        "subject": "Hello",
    })
except InvalidEmailException as e:
    print(e)  # e.g. "Invalid email: sender: value is not a valid email address"

PII warning: InvalidEmailException (and validate_formatted_email) includes the invalid email address in the exception message and error details. If your application ships exceptions to a central log aggregator, ensure this aligns with your data-handling policy before logging these at a level that persists to external systems.

Catching All Postmark Errors

Use PostmarkException as a catch-all for any error raised by the library.

from postmark.exceptions import PostmarkException

try:
    await client.outbound.send({...})
except PostmarkException as e:
    print(f"Error: {e}")

Further Reading

Clone this wiki locally