Skip to content

Commit 07516b0

Browse files
committed
Simplify examples, enhance type safety ease of use
1 parent 7c77192 commit 07516b0

4 files changed

Lines changed: 178 additions & 286 deletions

File tree

‎examples/get_messages.py‎

Lines changed: 29 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -1,157 +1,39 @@
1-
import os
2-
import asyncio
3-
from dotenv import load_dotenv
4-
import postmark
5-
6-
# Load the variables from the .env file into os.environ
7-
load_dotenv()
8-
9-
10-
async def list_example():
11-
"""
12-
Example using .list() (formerly .find())
13-
Best for: Getting a specific page of results or checking total counts.
14-
"""
15-
server_token: str = os.getenv("POSTMARK_SERVER_TOKEN")
16-
server = postmark.ServerClient(server_token=server_token)
17-
18-
try:
19-
messages, total = await server.outbound.list(count=10)
20-
21-
print(f"Total matches on server: {total}")
22-
print(f"Retrieved in this page: {len(messages)}")
23-
24-
if messages:
25-
for msg in messages[:3]:
26-
print(f" - {msg.subject}")
27-
print(f" ID: {msg.message_id}")
28-
print(f" FROM: {msg.sender}")
29-
print(f" TO: {', '.join([e.email for e in msg.to])}")
30-
print(f" DATE: {msg.received_at}")
31-
else:
32-
print("No messages found")
33-
34-
except Exception as e:
35-
print(f"Error in list_example: {e}")
36-
37-
38-
async def stream_example():
39-
"""
40-
Use .stream() to iterate over large datasets without memory issues.
41-
"""
42-
server_token: str = os.getenv("POSTMARK_SERVER_TOKEN")
43-
server = postmark.ServerClient(server_token=server_token)
44-
45-
print("Streaming messages...")
46-
47-
try:
48-
count = 0
49-
50-
# Get 20 messages
51-
async for msg in server.outbound.stream(max_messages=20):
52-
count += 1
1+
"""
2+
Examples for retrieving outbound messages.
533
54-
# Print details for the first 3...
55-
if count <= 3:
56-
print(f" #{count} - {msg.subject}")
57-
print(f" ID: {msg.message_id}")
58-
print(f" FROM: {msg.sender}")
59-
print(f" TO: {', '.join([e.email for e in msg.to])}")
4+
python examples/get_messages.py
5+
"""
606

61-
print(f"Successfully streamed {count} messages.")
62-
63-
except Exception as e:
64-
print(f"Error in stream_example: {e}")
65-
66-
67-
async def get_details_example(message_id: str):
68-
"""
69-
Example using .get() to retrieve full details of a specific message.
70-
Populates and prints all available data from the returned OutboundMessageDetails object.
71-
"""
72-
server_token: str = os.getenv("POSTMARK_SERVER_TOKEN")
73-
server = postmark.ServerClient(server_token=server_token)
74-
75-
print(f"Fetching details for message ID: {message_id}...")
76-
77-
try:
78-
# Get message details
79-
message = await server.outbound.get(message_id)
80-
81-
print("\n--- Message Details ---")
82-
print(f"Message ID: {message.message_id}")
83-
print(f"Status: {message.status}")
84-
print(f"Message Stream: {message.message_stream}")
85-
print(f"Received At: {message.received_at}")
86-
print(f"From: {message.sender}")
87-
88-
# Helper to format list of EmailAddress objects
89-
format_emails = lambda emails: ", ".join(
90-
[f"{e.name} <{e.email}>" if e.name else e.email for e in emails]
91-
)
92-
93-
print(f"To: {format_emails(message.to)}")
94-
if message.cc:
95-
print(f"Cc: {format_emails(message.cc)}")
96-
if message.bcc:
97-
print(f"Bcc: {format_emails(message.bcc)}")
98-
99-
print(f"Subject: {message.subject}")
100-
if message.tag:
101-
print(f"Tag: {message.tag}")
102-
103-
print("\n--- Content Bodies ---")
104-
print(
105-
f"Text Body: {len(message.text_body) if message.text_body else 'None'}"
106-
)
107-
print(
108-
f"HTML Body: {len(message.html_body) if message.html_body else 'None'}"
109-
)
110-
if message.body:
111-
print(f"Raw Body: {len(message.body)} chars")
112-
113-
print("\n--- Configuration ---")
114-
print(f"Track Opens: {message.track_opens}")
115-
print(f"Track Links: {message.track_links}")
116-
print(f"Sandboxed: {message.sandboxed}")
7+
import asyncio
8+
import os
1179

118-
if message.metadata:
119-
print(f"\n--- Metadata ---")
120-
for k, v in message.metadata.items():
121-
print(f" {k}: {v}")
10+
import postmark
11+
from dotenv import load_dotenv
12212

123-
if message.attachments:
124-
print(f"\n--- Attachments ({len(message.attachments)}) ---")
125-
for attachment in message.attachments:
126-
# Check if it's an object (OutboundMessageDetails usually returns Attachment objects)
127-
if hasattr(attachment, "name"):
128-
print(
129-
f" - {attachment.name} ({attachment.content_type}, {attachment.content_length} bytes)"
130-
)
131-
else:
132-
print(f" - {attachment}")
13+
load_dotenv()
14+
client = postmark.ServerClient(os.environ["POSTMARK_SERVER_TOKEN"])
13315

134-
if message.message_events:
135-
print(f"\n--- Message Events ({len(message.message_events)}) ---")
136-
for event in message.message_events:
137-
# Simplistic dump of details if present
138-
details_str = (
139-
f" - {event.details.model_dump(exclude_none=True)}"
140-
if event.details
141-
else ""
142-
)
143-
print(f" - [{event.received_at}] {event.type}{details_str}")
14416

145-
except Exception as e:
146-
print(f"Error getting message details: {e}")
17+
async def main():
18+
# --- List (one page) ---
19+
messages, total = await client.outbound.list(count=10)
20+
print(f"List: {total} total on server, showing {len(messages)}")
21+
for msg in messages:
22+
print(f" {msg.received_at:%Y-%m-%d} {msg.subject} → {msg.recipients}")
14723

24+
# --- Stream (auto-paginated) ---
25+
print("\nStream: first 50 messages")
26+
async for msg in client.outbound.stream(max_messages=50):
27+
print(f" {msg.message_id} {msg.subject}")
14828

149-
if __name__ == "__main__":
150-
print("--- GET SINGLE MESSAGE BY ID EXAMPLE ---")
151-
asyncio.run(get_details_example(message_id="a32a9dc7-a81c-4c4d-a8a3-32124297d0dd"))
29+
# --- Get full detail for the first message from the list ---
30+
if messages:
31+
print(f"\nDetail for message: {messages[0].message_id}")
32+
detail = await client.outbound.get(messages[0].message_id)
33+
print(f" Status: {detail.status}")
34+
print(f" Events: {[e.type for e in detail.message_events]}")
35+
else:
36+
print("No messages found to fetch details for.")
15237

153-
# print("--- RUNNING LIST EXAMPLE ---")
154-
# asyncio.run(list_example())
15538

156-
# print("\n--- RUNNING STREAM EXAMPLE ---")
157-
# asyncio.run(stream_example())
39+
asyncio.run(main())

‎examples/send_messages.py‎

Lines changed: 49 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,111 +1,67 @@
1-
import os
1+
"""
2+
Examples for sending outbound messages.
3+
4+
python examples/send_messages.py
5+
"""
6+
27
import asyncio
3-
from dotenv import load_dotenv
8+
import os
9+
410
import postmark
511
from postmark import Email
12+
from dotenv import load_dotenv
613

7-
# Load the variables from the .env file into os.environ
814
load_dotenv()
9-
10-
11-
async def send_single_email_using_json():
12-
"""
13-
Example: Sending a single email using a raw dictionary.
14-
"""
15-
server_token = os.getenv("POSTMARK_SERVER_TOKEN")
16-
server = postmark.ServerClient(server_token=server_token)
17-
send_email = os.getenv("POSTMARK_SENDER_EMAIL")
18-
19-
print("--- Sending Single Email (JSON) ---")
20-
try:
21-
# You can pass a dictionary matching the API fields
22-
response = await server.outbound.send(
23-
{
24-
"From": send_email,
25-
"To": "receiver@example.com",
26-
"Subject": "Hello from Postmark Python SDK",
27-
"TextBody": "This is a test email sent using the Python SDK.",
28-
"HtmlBody": "<html><body><strong>Hello</strong> from Postmark Python SDK.</body></html>",
29-
"MessageStream": "outbound",
30-
}
31-
)
32-
33-
print(f"Email sent! Message ID: {response.message_id}")
34-
print(f"Status: {response.message}")
35-
36-
except Exception as e:
37-
print(f"Error sending email: {e}")
38-
39-
40-
async def send_single_email_using_model():
41-
"""
42-
Example: Sending an email using the Pydantic Email model for better type safety.
43-
"""
44-
server_token = os.getenv("POSTMARK_SERVER_TOKEN")
45-
server = postmark.ServerClient(server_token=server_token)
46-
send_email = os.getenv("POSTMARK_SENDER_EMAIL")
47-
48-
print("\n--- Sending Email (Model) ---")
49-
try:
50-
# Create the email object first
51-
email = Email(
52-
sender=send_email,
15+
client = postmark.ServerClient(os.environ["POSTMARK_SERVER_TOKEN"])
16+
SENDER = os.environ["POSTMARK_SENDER_EMAIL"]
17+
18+
19+
async def main():
20+
# --- Send via dict ---
21+
response = await client.outbound.send(
22+
{
23+
"sender": SENDER,
24+
"to": "receiver@example.com",
25+
"subject": "Hello from Postmark Python SDK",
26+
"text_body": "This is a test email sent using the Python SDK.",
27+
"html_body": "<html><body><strong>Hello</strong> from Postmark Python SDK.</body></html>",
28+
"message_stream": "outbound",
29+
}
30+
)
31+
print(f"Sent (dict): {response.message_id}")
32+
33+
# --- Send via Email model (recommended, offering better type safety) ---
34+
response = await client.outbound.send(
35+
Email(
36+
sender=SENDER,
5337
to="receiver@example.com",
5438
subject="Hello via Model",
5539
text_body="This email was built using the Pydantic model.",
5640
metadata={"user_id": "12345"},
5741
)
42+
)
43+
print(f"Sent (model): {response.message_id}")
5844

59-
response = await server.outbound.send(email)
60-
61-
print(f"Email sent! Message ID: {response.message_id}")
62-
63-
except Exception as e:
64-
print(f"Error sending email: {e}")
65-
66-
67-
async def send_batch_emails():
68-
"""
69-
Example: Sending a batch of emails (up to 500).
70-
"""
71-
server_token = os.getenv("POSTMARK_SERVER_TOKEN")
72-
server = postmark.ServerClient(server_token=server_token)
73-
send_email = os.getenv("POSTMARK_SENDER_EMAIL")
74-
75-
print("\n--- Sending Batch Emails (JSON) ---")
76-
try:
77-
messages = [
45+
# --- Send batch ---
46+
responses = await client.outbound.send_batch(
47+
[
7848
{
79-
"From": send_email,
80-
"To": "receiver1@example.com",
81-
"Subject": "Batch Email 1",
82-
"TextBody": "Hello Receiver 1",
49+
"sender": SENDER,
50+
"to": "receiver1@example.com",
51+
"subject": "Batch 1",
52+
"text_body": "Hello Receiver 1",
8353
},
8454
{
85-
"From": send_email,
86-
"To": "receiver2@example.com",
87-
"Subject": "Batch Email 2",
88-
"TextBody": "Hello Receiver 2",
55+
"sender": SENDER,
56+
"to": "receiver2@example.com",
57+
"subject": "Batch 2",
58+
"text_body": "Hello Receiver 2",
8959
},
9060
]
61+
)
62+
print(f"Batch: {len(responses)} sent")
63+
for i, resp in enumerate(responses, start=1):
64+
print(f" {i}: {resp.message_id}")
9165

92-
responses = await server.outbound.send_batch(messages)
93-
94-
print(f"Batch processed. Sent {len(responses)} emails.")
95-
for i, resp in enumerate(responses):
96-
print(
97-
f" Email {i+1}: ID {resp.message_id} (Error Code: {resp.error_code})"
98-
)
99-
100-
except Exception as e:
101-
print(f"Error sending batch: {e}")
102-
103-
104-
if __name__ == "__main__":
105-
106-
async def main():
107-
await send_single_email_using_json()
108-
await send_single_email_using_model()
109-
await send_batch_emails()
11066

111-
asyncio.run(main())
67+
asyncio.run(main())

‎postmark/models/messages/manager.py‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@
1414

1515
def _parse_email(message: Union[Email, Dict[str, Any]]) -> Email:
1616
"""
17-
Coerce a dict to an Email model, raising InvalidEmailPayloadException
18-
on validation failure. Passes through an Email instance unchanged.
17+
Coerce a dict to an Email model using snake_case field names,
18+
raising InvalidEmailPayloadException on validation failure.
19+
Passes through an Email instance unchanged.
1920
"""
2021
if isinstance(message, Email):
2122
return message
2223
try:
23-
return Email(**message)
24+
return Email.model_validate(message)
2425
except ValidationError as e:
2526
raise InvalidEmailPayloadException(e.errors()) from e
2627

0 commit comments

Comments
 (0)