Skip to main content

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

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

pappymail-1.0.0.tar.gz (13.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pappymail-1.0.0-py3-none-any.whl (18.1 kB view details)

Uploaded Python 3

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

Hashes for pappymail-1.0.0.tar.gz
Algorithm Hash digest
SHA256 524cae015936a49dd7dbf3de0ced48d6671a8a96621e2a4e5384b58650840826
MD5 077c347f5b7bd3565f634a4347ae7606
BLAKE2b-256 5e95dc7962b633d82c870c70d806f196c1f6875c7c2a28fb1bc903bb2143ea4c

See more details on using hashes here.

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

Hashes for pappymail-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f44c5daef1ab1d34c448f1ddda1aafdcc11531e304b00f99d0bd45b964288c68
MD5 9b1bf0d030e09c8aaff5769bb634c81e
BLAKE2b-256 bbb9f6ef5ea5231a937c883488b665578302b871a7215e8324e46b6cc0682570

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page