Python SDK for Postwing email service API (api.postwing.app)
Project description
PostwingSDK
A Python SDK for the Postwing email service API. Provides both synchronous and asynchronous methods for sending emails via templates or simple HTML content.
Features
- Synchronous and Asynchronous API - Choose between blocking and non-blocking email sending
- Template Support - Send emails using pre-configured templates with parameters
- Simple HTML Emails - Send plain HTML emails directly
- Idempotency Keys - Prevent duplicate email sends with unique keys
- Multi-language Support - Send templated emails in different languages
- Thread Pool Execution - Efficient concurrent email sending with configurable worker threads
- Callback Support - Handle async results with callbacks for fire-and-forget patterns
- Fail Silently Mode - Option to suppress exceptions for graceful degradation
- Comprehensive Logging - Configurable logging levels with detailed request/response information for debugging
Installation
Install the required dependencies:
pip install -r requirements.txt
Dependencies
requests- For making HTTP API callsfaker- For running tests (development only)
Quick Start
from postwing.sdk import PostwingSdk
# Initialize the SDK
sdk = PostwingSdk(
username="your-domain@example.com",
password="your-api-token"
)
# Send a simple HTML email
sdk.send_simple(
recipient="user@example.com",
sender="noreply@example.com",
subject="Welcome!",
body="<h1>Hello World</h1>"
)
# Send a templated email
sdk.send(
tpl="welcome-template",
recipient="user@example.com",
sender="noreply@example.com",
lang="en",
params={"name": "John", "code": "123456"}
)
Usage
Synchronous Methods
Send Simple HTML Email
sdk.send_simple(
recipient="user@example.com",
sender="noreply@example.com",
subject="Important Notice",
body="<p>This is an important message.</p>",
idempotency_key="unique-key-123" # Optional: prevent duplicates
)
Send Templated Email
sdk.send(
tpl="password-reset",
recipient="user@example.com",
sender="noreply@example.com",
lang="en", # Optional: language code
params={"reset_link": "https://example.com/reset/token"}, # Template variables
idempotency_key="reset-user-123" # Optional: prevent duplicates
)
Asynchronous Methods
Async methods use a thread pool executor for non-blocking operation. They return Future objects that can be used in various ways:
Fire and Forget
Send emails without waiting for responses:
sdk.send_simple_async(
recipient="user@example.com",
sender="noreply@example.com",
subject="Newsletter",
body="<h1>Latest Updates</h1>"
)
# Continues immediately without blocking
Using Callbacks
Handle results with callback functions:
def on_complete(success, error):
if error:
print(f"Failed to send email: {error}")
else:
print("Email sent successfully!")
sdk.send_async(
tpl="notification",
recipient="user@example.com",
sender="noreply@example.com",
params={"message": "You have a new notification"},
callback=on_complete
)
Wait for Results
Send async but wait for completion when needed:
future = sdk.send_simple_async(
recipient="user@example.com",
sender="noreply@example.com",
subject="Confirmation",
body="<p>Please confirm your action</p>"
)
# Do other work...
# Wait for result (with timeout)
try:
result = future.result(timeout=10) # Wait up to 10 seconds
print(f"Email sent: {result}")
except Exception as e:
print(f"Email failed: {e}")
Batch Sending
Send multiple emails concurrently:
recipients = ["user1@example.com", "user2@example.com", "user3@example.com"]
futures = []
for recipient in recipients:
future = sdk.send_simple_async(
recipient=recipient,
sender="noreply@example.com",
subject="Batch Email",
body="<p>Hello!</p>",
idempotency_key=f"batch-{recipient}"
)
futures.append(future)
# Wait for all to complete
for future in futures:
try:
future.result(timeout=30)
except Exception as e:
print(f"Failed: {e}")
Check Status Without Blocking
future = sdk.send_simple_async(
recipient="user@example.com",
sender="noreply@example.com",
subject="Status Check",
body="<p>Testing</p>"
)
if future.done():
result = future.result()
print(f"Already completed: {result}")
else:
print("Still processing...")
Configuration
SDK Options
import logging
sdk = PostwingSdk(
username="your-domain@example.com",
password="your-api-token",
fail_silently=False, # If True, suppresses exceptions
max_workers=5, # Number of concurrent threads for async operations
log_level=logging.INFO # Logging level (default: INFO)
)
Logging
The SDK includes comprehensive logging capabilities to help with debugging and monitoring email operations.
Log Levels
The SDK supports standard Python logging levels:
logging.DEBUG- Detailed information including request/response data (recommended for development)logging.INFO- General operational messages about SDK lifecycle and email sends (default)logging.WARNING- Warning messageslogging.ERROR- Error messages only
Basic Logging Configuration
import logging
from postwing.sdk import PostwingSdk
# Enable DEBUG logging to see detailed request/response information
sdk = PostwingSdk(
username="your-domain@example.com",
password="your-api-token",
log_level=logging.DEBUG
)
# Send an email - you'll see detailed logs
sdk.send_simple(
recipient="user@example.com",
sender="noreply@example.com",
subject="Test Email",
body="<p>Testing with debug logs</p>"
)
What Gets Logged
INFO Level:
- SDK initialization with configuration
- ThreadPoolExecutor creation and shutdown
- Successful email sends
DEBUG Level:
- All INFO level messages
- Full request details (URL, parameters, sanitized payload)
- Full response details (status codes, response body)
- Async task lifecycle (submission, start, completion)
- Callback execution
ERROR Level:
- API errors (non-2xx responses)
- Network/connection errors
- Async task failures
Example Output
2025-11-21 17:36:05,170 - postwing.sdk.4472389120 - INFO - PostwingSdk initialized with username=test, max_workers=5, log_level=INFO
2025-11-21 17:36:05,171 - postwing.sdk.4472389120 - INFO - Initializing ThreadPoolExecutor with 5 workers
2025-11-21 17:36:05,171 - postwing.sdk.4472389120 - DEBUG - Sending simple email - URL: https://api.postwing.app/external/send_email_simple/, recipient: user@example.com, sender: noreply@example.com, subject: Test, idempotency_key: None
2025-11-21 17:36:05,171 - postwing.sdk.4472389120 - DEBUG - Request payload: {"recipient": "user@example.com", "sender": "noreply@example.com", "subject": "Test", "body": "<p>Test</p>", "auth": "***"}
2025-11-21 17:36:05,171 - postwing.sdk.4472389120 - DEBUG - Response received - status_code: 200, response: {"success": true}
2025-11-21 17:36:05,171 - postwing.sdk.4472389120 - INFO - Simple email sent successfully to user@example.com
Disable Logging
To disable all logging output:
sdk = PostwingSdk(
username="your-domain@example.com",
password="your-api-token",
log_level=logging.CRITICAL # Only critical errors
)
Production Recommendations
For production environments, we recommend:
- Use
logging.INFOorlogging.WARNINGto avoid logging sensitive data - Configure external log aggregation (e.g., CloudWatch, Datadog)
- Monitor ERROR level logs for operational issues
- Use DEBUG level only for troubleshooting specific issues
Custom Logging Configuration
If you need more control over logging format or handlers, you can configure Python's logging system before initializing the SDK:
import logging
# Configure global logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('postwing.log'),
logging.StreamHandler()
]
)
# SDK will use the configured logging system
sdk = PostwingSdk(
username="your-domain@example.com",
password="your-api-token",
log_level=logging.DEBUG
)
Cleanup
Properly shutdown the thread pool when done:
# Wait for all pending emails to complete before shutdown
sdk.shutdown(wait=True)
Or use a try-finally pattern:
try:
sdk.send_simple_async(...)
# ... more operations
finally:
sdk.shutdown(wait=True)
API Reference
PostwingSdk
Constructor
PostwingSdk(username: str, password: str, fail_silently=False, max_workers=5, log_level=logging.INFO)
username- Your Postwing account username (typically your domain)password- Your Postwing API tokenfail_silently- If True, suppresses exceptions on errorsmax_workers- Number of threads for async operations (default: 5)log_level- Logging level using Python's logging constants (default: logging.INFO). Use logging.DEBUG for detailed request/response logs
Methods
send_simple(recipient, sender, subject, body, idempotency_key=None) -> bool
Send a simple HTML email synchronously.
send(tpl, recipient, sender, lang=None, params=None, idempotency_key=None) -> bool
Send a templated email synchronously.
send_simple_async(recipient, sender, subject, body, idempotency_key=None, callback=None) -> Future
Send a simple HTML email asynchronously.
send_async(tpl, recipient, sender, lang=None, params=None, idempotency_key=None, callback=None) -> Future
Send a templated email asynchronously.
shutdown(wait=True)
Shutdown the thread pool executor.
Exceptions
PostwingSdkException
Raised when API requests fail or network errors occur. Can be suppressed with fail_silently=True.
Development
Using the Makefile
The project includes a Makefile for common development tasks:
# View all available commands
make help
# Install dependencies
make install
# Run all tests
make test
# Run synchronous tests only
make test-sync
# Run asynchronous tests only
make test-async
# Run a specific test
make test-specific TEST=tests.PostwingAsyncTestUtils.test_send_simple_async_success
# Build the package
make build
# Clean build artifacts
make clean
Testing
The SDK includes a comprehensive test suite covering both synchronous and asynchronous operations.
Quick Test Commands (Using Makefile)
# Run all tests
make test
# Run specific test classes
make test-sync # Synchronous tests only
make test-async # Asynchronous tests only
# Run a specific test
make test-specific TEST=tests.PostwingAsyncTestUtils.test_send_simple_async_success
Manual Test Commands (Without Makefile)
# Run all tests
source .venv/bin/activate
PYTHONPATH=/Users/skyman/Documents/My/Python:$PYTHONPATH python -m unittest tests
# Run specific test class
PYTHONPATH=/Users/skyman/Documents/My/Python:$PYTHONPATH python -m unittest tests.PostwingTestUtils
# Run specific test
PYTHONPATH=/Users/skyman/Documents/My/Python:$PYTHONPATH python -m unittest tests.PostwingAsyncTestUtils.test_send_simple_async_success
Building and Publishing
Build the package for distribution:
# Build the package
make build
# Publish to TestPyPI (for testing)
make publish-test
# Publish to PyPI (production)
make publish
Or manually:
# Build
python -m build
# Check the distribution
twine check dist/*
# Upload to PyPI
twine upload dist/*
Examples
See async_example.py for comprehensive examples of all async patterns including:
- Fire and forget
- Callback handling
- Waiting for results
- Batch sending
- Status checking
- Proper cleanup
API Endpoints
The SDK communicates with the following Postwing API endpoints:
- Base URL:
https://api.postwing.app/external/ - Simple Send:
POST /external/send_email_simple/ - Template Send:
POST /external/send_email_tpl/
Contributing
Contributions are welcome! Please ensure all tests pass before submitting a pull request.
License
[Add your license information here]
Support
For issues, questions, or feature requests, please contact Postwing support or open an issue in this repository.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file postwing-0.3.0.tar.gz.
File metadata
- Download URL: postwing-0.3.0.tar.gz
- Upload date:
- Size: 13.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4cacd01337b2b70ff6dbf874376771113b3e7902fb897c2788aa2fc645e08bf5
|
|
| MD5 |
7f13f030241fb08236dbbea707faf0a2
|
|
| BLAKE2b-256 |
cf3bcbdf141603641eb580595c3f552fdfbf8f513e4cfebc2a282408579454a9
|
File details
Details for the file postwing-0.3.0-py3-none-any.whl.
File metadata
- Download URL: postwing-0.3.0-py3-none-any.whl
- Upload date:
- Size: 8.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2244092a32fa583cc33b1c9aa78e3e6cbeeb7e760b4641d2945707e170652fa3
|
|
| MD5 |
ab22b44cf5f43a0f7f22bf8bbfbbe857
|
|
| BLAKE2b-256 |
f3d50f40b09856245911c77124cd2a0be94509829ec12a15b605852a3f912828
|