-
Notifications
You must be signed in to change notification settings - Fork 2
Getting Started
Before you begin, you'll need:
- A Postmark account
- A Server API token (for sending email and server-level operations)
- An Account API token (for account-level operations like managing domains and servers)
- A verified sender signature or domain
You can find your tokens under API Tokens in the Postmark app.
pip install postmark-python
Python 3.10 or higher is required.
The library is fully async. All API calls must be awaited inside an async context.
import asyncio
import postmark
async def main():
async with postmark.ServerClient("your-server-token") as client:
response = await client.outbound.send({
"sender": "you@verified-domain.com",
"to": "recipient@example.com",
"subject": "Hello from Postmark!",
"text_body": "This is my first email via postmark-python.",
"html_body": "<p>This is my first email via <strong>postmark-python</strong>.</p>",
})
print(f"Sent! Message ID: {response.message_id}")
print(f"Accepted: {response.success}") # True when error_code == 0
asyncio.run(main())Note: You must use a verified sender address and a valid server token from your account.
Using the client as an async context manager (async with) is recommended. It ensures the underlying HTTP connection pool is cleanly closed when the block exits. For long-lived processes (servers, workers) you can also instantiate the client once and call await client.close() when shutting down.
Testing in Jupyter Notebooks? See the Jupyter Notebooks guide.
If you prefer a synchronous API — for one-off scripts, Flask apps, or just to avoid the async/await boilerplate — use postmark.sync:
import postmark
with postmark.sync.ServerClient("your-server-token") as client:
response = client.outbound.send({
"sender": "sender@example.com",
"to": "recipient@example.com",
"subject": "Hello from Postmark!",
"text_body": "This is my first email via postmark-python.",
})
print(f"Message ID: {response.message_id}")The sync client runs requests on a background event loop thread and blocks until each call completes. It supports all the same methods as the async client, and works correctly inside Jupyter notebooks.
For full documentation — both client types, streaming behaviour, context manager usage, and how it works under the hood — see the Sync Client page.
| Client | Token | Use for |
|---|---|---|
ServerClient |
Server API token | Sending email, bounces, templates, stats, webhooks, streams |
AccountClient |
Account API token | Domains, sender signatures, managing servers, data removals |
import postmark
# Server-level operations
server_client = postmark.ServerClient("your-server-token")
# Account-level operations
account_client = postmark.AccountClient("your-account-token")Note on the examples: All example files in
examples/async/andexamples/sync/use hardcoded placeholder strings (e.g."xxx-YOUR-SERVER-TOKEN-xxxx-xxxxxxx") in place of real credentials, to keep them as readable as possible. Replace those placeholders with your actual tokens before running — ideally by reading them from environment variables as shown below.
Store tokens in environment variables rather than hard-coding them in source files.
Create a .env file:
export POSTMARK_SERVER_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export POSTMARK_ACCOUNT_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export POSTMARK_SENDER_EMAIL=you@verified-domain.com
Then load it at the top of your script:
import os
import postmark
from dotenv import load_dotenv # pip install python-dotenv
load_dotenv()
client = postmark.ServerClient(os.environ["POSTMARK_SERVER_TOKEN"])If you source your .env file in the shell before running (source .env), or if your deployment environment injects variables directly (Heroku, Railway, AWS Lambda, Docker, etc.), you can read them without the dotenv library:
import os
import postmark
client = postmark.ServerClient(os.environ["POSTMARK_SERVER_TOKEN"])The same pattern applies to the sync client:
import os
import postmark
load_dotenv() # if using python-dotenv
with postmark.sync.ServerClient(os.environ["POSTMARK_SERVER_TOKEN"]) as client:
response = client.outbound.send({
"sender": os.environ["POSTMARK_SENDER_EMAIL"],
"to": "recipient@example.com",
"subject": "Hello",
"text_body": "Hello from Postmark!",
})In production, inject secrets via your platform's secret management rather than .env files — environment variables are already available in the process without any extra loading:
| Platform | How to set |
|---|---|
| Heroku | heroku config:set POSTMARK_SERVER_TOKEN=... |
| AWS Lambda | Environment variables in function configuration |
| Docker |
--env flag or env_file in docker-compose.yml
|
| GitHub Actions | Repository secrets, referenced as ${{ secrets.POSTMARK_SERVER_TOKEN }}
|
| Kubernetes |
Secret objects mounted as env vars |
Most methods accept either a plain dict or a typed Pydantic model. Using models gives you IDE autocompletion and catches errors before they reach the API.
from postmark import Email
# Using a dict
await client.outbound.send({
"sender": "you@example.com",
"to": "recipient@example.com",
"subject": "Hello",
"text_body": "Hello!",
})
# Using a model (recommended)
await client.outbound.send(
Email(
sender="you@example.com",
to="recipient@example.com",
subject="Hello",
text_body="Hello!",
)
)Default timeouts differ by client: ServerClient defaults to 5 seconds, AccountClient to 30 seconds. Override per instance:
client = postmark.ServerClient("your-server-token", timeout=60.0)timeout must be a positive number — passing 0 or a negative value raises PostmarkException immediately at construction.
Override the API base URL — useful for pointing at a local mock server during tests:
client = postmark.ServerClient("your-server-token", base_url="http://localhost:8080")The client automatically retries on RateLimitException, ServerException, and TimeoutException using exponential backoff with jitter. By default it retries up to 3 times. Adjust with the retries parameter:
# Retry up to 5 times before raising
client = postmark.ServerClient("your-server-token", retries=5)
# Disable retries entirely
client = postmark.ServerClient("your-server-token", retries=0)retries must be a non-negative integer — passing a negative value raises PostmarkException immediately at construction.
Worst-case blocking time: With
AccountClientdefaults (timeout=30,retries=3), a single call that repeatedly times out can block for up to ~90 seconds before raising.ServerClientdefaults (timeout=5,retries=3) cap this at ~15 seconds. Tune both values to match your application's latency budget.
SSL verification can be disabled for development or testing. Do not disable in production.
import os
os.environ["POSTMARK_SSL_VERIFY"] = "false"import logging
logging.getLogger("postmark").setLevel(logging.DEBUG)- Email Sending — Attachments, batch sends, tracking options
- Templates — Send with pre-built templates
- Error Handling — Handle API errors gracefully
- Postmark API Reference