Official Python SDK for PappyMail - Send transactional emails, manage campaigns, and build email experiences.
Project description
PappyMail SDK for Python
Official Python SDK for PappyMail - A powerful email platform combining transactional email, campaigns, and inbox capabilities.
Installation
pip install pappymail
Quick Start
import asyncio
from pappymail import PappyMail
async def main():
client = PappyMail("pm_live_your_api_key")
response = await client.send_simple(
from_email="noreply@yourdomain.com",
to="user@example.com",
subject="Hello from PappyMail!",
html="<h1>Welcome!</h1><p>Thanks for signing up.</p>",
)
print(f"Message ID: {response.message_id}")
await client.close()
asyncio.run(main())
Or use as context manager:
async with PappyMail("pm_live_your_api_key") as client:
response = await client.send_simple(
from_email="noreply@yourdomain.com",
to="user@example.com",
subject="Hello!",
html="<h1>Welcome!</h1>",
)
Features
Send Transactional Emails
from pappymail import SendEmailRequest, EmailAddress, TrackingSettings
response = await client.mail.send(SendEmailRequest(
**{
"from": EmailAddress(email="noreply@yourdomain.com", name="Your App"),
"to": [EmailAddress(email="user@example.com", name="John Doe")],
"subject": "Order Confirmation",
"html_content": "<h1>Order #12345</h1><p>Thank you!</p>",
"text_content": "Order #12345 - Thank you!",
"tracking": TrackingSettings(track_opens=True, track_clicks=True),
}
))
Send with Templates
response = await client.mail.send(SendEmailRequest(
**{
"from": EmailAddress(email="noreply@yourdomain.com"),
"to": [EmailAddress(email="user@example.com")],
"template_id": "tmpl_welcome_email",
"template_data": {
"first_name": "John",
"order_id": "12345",
},
}
))
Send Bulk Personalized Emails
from pappymail import Personalization
response = await client.mail.send(SendEmailRequest(
**{
"from": EmailAddress(email="noreply@yourdomain.com"),
"subject": "Hello {{name}}!",
"html_content": "<p>Hi {{name}}, welcome!</p>",
"personalizations": [
Personalization(
to=[EmailAddress(email="alice@example.com")],
substitutions={"name": "Alice"},
),
Personalization(
to=[EmailAddress(email="bob@example.com")],
substitutions={"name": "Bob"},
),
],
}
))
With Attachments
from pappymail import Attachment
response = await client.mail.send(SendEmailRequest(
**{
"from": EmailAddress(email="noreply@yourdomain.com"),
"to": [EmailAddress(email="user@example.com")],
"subject": "Your Invoice",
"html_content": "<p>Please find your invoice attached.</p>",
"attachments": [
Attachment.from_file("/path/to/invoice.pdf"),
Attachment.from_bytes("data.csv", csv_bytes, "text/csv"),
],
}
))
Manage Email Templates
from pappymail import CreateTemplateRequest, UpdateTemplateRequest
# Create template
template = await client.templates.create(CreateTemplateRequest(
name="Welcome Email",
subject="Welcome, {{first_name}}!",
html_content="<h1>Welcome, {{first_name}}!</h1>",
))
# List templates
templates, pagination = await client.templates.list()
# Update template
await client.templates.update(template.id, UpdateTemplateRequest(
html_content="<h1>Welcome, {{first_name}}!</h1><p>We're excited!</p>",
))
# Delete template
await client.templates.delete(template.id)
Email Campaigns
from pappymail import CreateCampaignRequest
# Create campaign
campaign = await client.campaigns.create(CreateCampaignRequest(
name="Summer Sale",
subject="Don't miss our Summer Sale!",
**{
"from": EmailAddress(email="marketing@yourdomain.com", name="Your Store"),
"contact_list_id": "list_abc123",
"html_content": "<h1>Summer Sale!</h1><p>Up to 50% off!</p>",
"track_opens": True,
"track_clicks": True,
}
))
# Send campaign
result = await client.campaigns.send(campaign.id)
# Or schedule for later
from datetime import datetime, timedelta
await client.campaigns.schedule(campaign.id, datetime.utcnow() + timedelta(days=1))
# Get campaign stats
stats = await client.campaigns.get_stats(campaign.id)
print(f"Opens: {stats.opened}, Clicks: {stats.clicked}")
Contact Management
from pappymail import CreateContactListRequest, CreateContactRequest, UpsertContactRequest
# Create contact list
contact_list = await client.contacts.create_list(CreateContactListRequest(
name="Newsletter Subscribers",
description="Users subscribed to our newsletter",
))
# Add contact
contact = await client.contacts.add(contact_list.id, CreateContactRequest(
email="user@example.com",
first_name="John",
last_name="Doe",
custom_fields={"plan": "premium"},
))
# Import contacts
import_result = await client.contacts.import_contacts(contact_list.id, [
CreateContactRequest(email="alice@example.com", first_name="Alice"),
CreateContactRequest(email="bob@example.com", first_name="Bob"),
])
# Upsert contact
await client.contacts.upsert(UpsertContactRequest(
email="user@example.com",
list_id=contact_list.id,
first_name="John Updated",
))
Domain Management
# Add domain
domain = await client.domains.add("yourdomain.com")
# Get DNS records to configure
for record in domain.dns_records or []:
print(f"{record.type}: {record.name} -> {record.value}")
# Verify domain
verification = await client.domains.verify(domain.id)
if verification.is_verified:
print("Domain verified successfully!")
Webhooks
# Create webhook
webhook = await client.webhooks.create(
"https://yourapp.com/webhooks/email",
["delivered", "opened", "clicked", "bounced"],
)
# List webhooks
webhooks = await client.webhooks.list()
# Delete webhook
await client.webhooks.delete(webhook.id)
# Verify webhook signature (in your Flask/FastAPI endpoint)
from pappymail import PappyMail
@app.post("/webhooks/email")
async def handle_webhook(request: Request):
payload = await request.body()
signature = request.headers.get("X-PappyMail-Signature", "")
if not PappyMail.verify_webhook_signature(payload.decode(), signature, "your_secret"):
raise HTTPException(status_code=401)
# Process webhook...
return {"status": "ok"}
Analytics
from datetime import datetime, timedelta
# Get stats for date range
stats = await client.stats.get(
start_date=datetime.utcnow() - timedelta(days=30),
end_date=datetime.utcnow(),
aggregation="day",
)
for day in stats:
print(f"{day.date}: Sent={day.sent}, Opened={day.opened}")
# Get global account stats
global_stats = await client.stats.get_global()
print(f"Daily usage: {global_stats.daily_usage}/{global_stats.daily_limit}")
Configuration
Full Options
from pappymail import PappyMail, PappyMailOptions
client = PappyMail(PappyMailOptions(
api_key="pm_live_your_api_key",
base_url="https://pappymall.com/api/v1", # default
timeout=30.0, # seconds
max_retries=3, # default
enable_retry=True, # default
))
Sandbox Environment
client = PappyMail(PappyMailOptions(
api_key="pm_test_your_api_key",
base_url="https://pappymall.com/api/v1/sandbox",
))
Error Handling
from pappymail import (
PappyMailError,
ValidationError,
RateLimitError,
UnauthorizedError,
)
try:
response = await client.mail.send(request)
except ValidationError as e:
print(f"Validation error: {e.message}")
for detail in e.details:
print(f" - {detail.field}: {detail.message}")
except RateLimitError as e:
print(f"Rate limited. Retry after: {e.retry_after} seconds")
except UnauthorizedError:
print("Invalid API key")
except PappyMailError as e:
print(f"Error: {e.code} - {e.message}")
Requirements
- Python 3.8+
- httpx
- pydantic
License
MIT License - see LICENSE file for details.
Support
- Documentation: https://pappymall.com/Public/MailApi
- Contact: https://pappymall.com/Public/Contact
- Email: support@pappymall.com
Project details
Release history Release notifications | RSS feed
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 pappymail-1.0.0.tar.gz.
File metadata
- Download URL: pappymail-1.0.0.tar.gz
- Upload date:
- Size: 13.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
524cae015936a49dd7dbf3de0ced48d6671a8a96621e2a4e5384b58650840826
|
|
| MD5 |
077c347f5b7bd3565f634a4347ae7606
|
|
| BLAKE2b-256 |
5e95dc7962b633d82c870c70d806f196c1f6875c7c2a28fb1bc903bb2143ea4c
|
File details
Details for the file pappymail-1.0.0-py3-none-any.whl.
File metadata
- Download URL: pappymail-1.0.0-py3-none-any.whl
- Upload date:
- Size: 18.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f44c5daef1ab1d34c448f1ddda1aafdcc11531e304b00f99d0bd45b964288c68
|
|
| MD5 |
9b1bf0d030e09c8aaff5769bb634c81e
|
|
| BLAKE2b-256 |
bbb9f6ef5ea5231a937c883488b665578302b871a7215e8324e46b6cc0682570
|