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
Metadata
Release files for pappymail 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| pappymail-1.0.0.tar.gz | 13.0 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pappymail-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 31.1 kB
Release files / pappymail-1.0.0.tar.gz
| Download URL | pappymail-1.0.0.tar.gz |
|---|---|
| Size | 13.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
524cae015936a49dd7dbf3de0ced48d6671a8a96621e2a4e5384b58650840826
|
|
BLAKE2b-256 checksum How to use checksums |
5e95dc7962b633d82c870c70d806f196c1f6875c7c2a28fb1bc903bb2143ea4c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.6
|
Release files / pappymail-1.0.0-py3-none-any.whl
| Download URL | pappymail-1.0.0-py3-none-any.whl |
|---|---|
| Size | 18.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f44c5daef1ab1d34c448f1ddda1aafdcc11531e304b00f99d0bd45b964288c68
|
|
BLAKE2b-256 checksum How to use checksums |
bbb9f6ef5ea5231a937c883488b665578302b871a7215e8324e46b6cc0682570
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.6
|
Security for pappymail 1.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.