Python SDK

Install

pip install pigeons-py

Initialize

from pigeons import Pigeons

pigeons = Pigeons(
    base_url="https://pigeons.yourdomain.com",
    api_key="pgn_your_key_here",
)

Both options can be set via environment variables instead:

OptionEnv varDescription
base_urlPIGEONS_URLPigeons server URL (no default)
api_keyPIGEONS_API_KEYAPI key starting with pgn_

Constructor values take precedence over environment variables.


Emails

Send an email

email = pigeons.emails.send(
    to="alice@example.com",
    subject="Welcome!",
    html="<h1>Welcome aboard</h1>",
)

print(email.id)      # msg_abc123...
print(email.status)  # "queued"

Send with a template

email = pigeons.emails.send(
    to="alice@example.com",
    template_id="welcome-email",  # slug or tpl_ ID
    data={"name": "Alice", "company": "Acme"},
)

Multiple recipients

email = pigeons.emails.send(
    to=["alice@example.com", "bob@example.com"],
    cc=["manager@example.com"],
    subject="Team Update",
    html="<p>Here is your update.</p>",
)

Schedule an email

email = pigeons.emails.send(
    to="alice@example.com",
    subject="Reminder",
    text="Your trial expires tomorrow.",
    scheduled_at="2024-06-01T09:00:00Z",
)

Get email status

email = pigeons.emails.get("msg_abc123...")
print(email.status)  # "delivered"

List emails

page = pigeons.emails.list(limit=10, status="delivered")

for email in page.data:
    print(email.id, email.subject, email.status)

if page.has_more:
    next_page = pigeons.emails.list(cursor=page.next_cursor)

Cancel a queued email

cancelled = pigeons.emails.cancel("msg_abc123...")

Batch send

results = pigeons.emails.batch([
    {"to": "alice@example.com", "subject": "Hi Alice", "text": "Hello!"},
    {"to": "bob@example.com", "subject": "Hi Bob", "text": "Hello!"},
])

Idempotent sends

email = pigeons.emails.send(
    to="alice@example.com",
    subject="Order #123",
    html="...",
    idempotency_key="order-123-confirmation",
)

Contacts

Create a contact

contact = pigeons.contacts.create(
    email="alice@example.com",
    first_name="Alice",
    last_name="Smith",
    tags=["newsletter", "beta"],
    properties={"plan": "pro", "signup_source": "website"},
)

Get a contact

# By ID
contact = pigeons.contacts.get("ctc_abc123...")

# By email
contact = pigeons.contacts.get("alice@example.com")

Update a contact

updated = pigeons.contacts.update("ctc_abc123...", tags=["newsletter", "vip"])

List contacts

page = pigeons.contacts.list(tag="newsletter", limit=50)

Delete a contact

pigeons.contacts.delete("ctc_abc123...")

Batch create

result = pigeons.contacts.batch_create([
    {"email": "alice@example.com", "first_name": "Alice", "tags": ["import"]},
    {"email": "bob@example.com", "first_name": "Bob", "tags": ["import"]},
])
print(result.created)  # 2
print(result.skipped)  # 0

Unsubscribe / Resubscribe

# Global unsubscribe
pigeons.contacts.unsubscribe("ctc_abc123...")

# Per-topic unsubscribe
pigeons.contacts.unsubscribe("ctc_abc123...", topic_id="top_xyz...")

# Resubscribe
pigeons.contacts.resubscribe("ctc_abc123...")

Templates

Create a template

template = pigeons.templates.create(
    slug="welcome-email",
    name="Welcome Email",
    subject="Welcome, {{name}}!",
    body="<h1>Welcome to {{company}}</h1><p>Hi {{name}},</p>",
    sample_data={"name": "Alice", "company": "Acme"},
)

Get / Update / Delete

tpl = pigeons.templates.get("welcome-email")  # by slug or ID
updated = pigeons.templates.update("welcome-email", subject="New subject")
pigeons.templates.delete("welcome-email")

Preview a template

preview = pigeons.templates.preview("welcome-email", data={"name": "Alice", "company": "Acme"})
print(preview.subject)  # "Welcome, Alice!"
print(preview.html)     # rendered HTML

Campaigns

Create and send a campaign

campaign = pigeons.campaigns.create(
    name="Product Launch",
    template_id="product-launch",
    template_data={"launch_date": "2024-06-01"},
    audience_filter={"tags": {"contains": "newsletter"}},
)

# Send immediately
pigeons.campaigns.send(campaign.id)

# Or schedule
pigeons.campaigns.send(campaign.id, scheduled_at="2024-06-01T09:00:00Z")

Campaign lifecycle

pigeons.campaigns.pause(campaign.id)
pigeons.campaigns.resume(campaign.id)
pigeons.campaigns.cancel(campaign.id)

Monitor stats

campaign = pigeons.campaigns.get("cmp_abc123...")
print(campaign.stats)
# {"total": 500, "sent": 450, "delivered": 440, "opened": 120, ...}

Sequences

Create a sequence

sequence = pigeons.sequences.create(
    name="Onboarding",
    steps=[
        {"type": "email", "template_id": "welcome-email", "subject": "Welcome!"},
        {"type": "delay", "delay_duration": "1d"},
        {"type": "email", "template_id": "getting-started", "subject": "Getting Started"},
        {"type": "delay", "delay_duration": "3d"},
        {"type": "email", "template_id": "tips", "subject": "Pro Tips"},
    ],
)

Activate and enroll

pigeons.sequences.activate(sequence.id)

enrollment = pigeons.sequences.enroll(sequence.id, "ctc_abc123...")
print(enrollment.status)  # "active"

Monitor enrollments

page = pigeons.sequences.enrollments(sequence.id, status="active")

Inboxes

Create an inbox

inbox = pigeons.inboxes.create(
    name="Support",
    email_address="support@yourdomain.com",
    webhook_url="https://your-app.com/webhooks/email",
)

Inbox operations

pigeons.inboxes.pause(inbox.id)
pigeons.inboxes.resume(inbox.id)
pigeons.inboxes.set_catch_all(inbox.id)
pigeons.inboxes.delete(inbox.id)

Read messages

messages = pigeons.inboxes.messages.list(inbox.id, direction="inbound", limit=10)

message = pigeons.inboxes.messages.get(inbox.id, "imsg_abc123...")

Send from an inbox

sent = pigeons.inboxes.messages.create(
    inbox.id,
    to="customer@example.com",
    subject="Re: Your ticket",
    html="<p>Thanks for reaching out.</p>",
    in_reply_to="imsg_previous...",  # maintains thread
)

Threads

threads = pigeons.inboxes.threads.list(inbox.id, status="open")

thread = pigeons.inboxes.threads.get(inbox.id, "thr_abc123...")

pigeons.inboxes.threads.update(inbox.id, "thr_abc123...", status="closed")

Webhooks

webhook = pigeons.webhooks.create(
    url="https://your-app.com/webhooks/pigeons",
    events=["email.delivered", "email.bounced", "inbox.message.received"],
)

# The secret is only returned on creation
print(webhook.secret)  # "whsec_..."

Domains

domain = pigeons.domains.add(domain="example.com")
print(domain.records)  # DNS records to add

# Re-check verification
verified = pigeons.domains.verify(domain.id)
print(verified.verification_status)  # "verified"

Error Handling

All errors extend PigeonsError:

from pigeons import PigeonsError, NotFoundError, AuthError, RateLimitError

try:
    pigeons.emails.get("msg_nonexistent")
except NotFoundError as e:
    print(e.message)      # "Email 'msg_nonexistent' not found"
    print(e.status_code)  # 404
    print(e.code)         # "not_found"
except PigeonsError as e:
    print(f"API error: {e.message}")

Error classes:

ClassStatusWhen
PigeonsErrorvariesBase class for all errors
ValidationError400Invalid request parameters
AuthError401Missing or invalid API key
NotFoundError404Resource not found
RateLimitError429Rate limit exceeded