Skip to main content

MailerBot Python SDK

Official Python SDK for the MailerBot direct mail API. Send letters and postcards programmatically.

Installation

pip install mailerbot

Requires Python 3.10+.

Authentication

Generate an API key in your MailerBot dashboard, then pass it to the client:

import mailerbot

client = mailerbot.MailerBot(api_key="mb_live_...")

Quick Start

Synchronous

import mailerbot

with mailerbot.MailerBot(api_key="mb_live_...") as client:
    # List contacts
    page = client.contacts.list(page=1, page_size=25)
    print(f"{page.total} total contacts")
    for contact in page.items:
        print(f"  {contact.first_name} {contact.last_name}, {contact.city}, {contact.state}")

    # Dashboard stats
    stats = client.dashboard.stats()
    print(f"Letters sent: {stats.letters_sent}")
    print(f"Total spent: ${stats.total_spent:.2f}")

Asynchronous

import asyncio
import mailerbot

async def main():
    async with mailerbot.AsyncMailerBot(api_key="mb_live_...") as client:
        page = await client.contacts.list()
        print(f"{page.total} contacts")

asyncio.run(main())

Common Workflows

Create and send a letter mailing

import mailerbot

with mailerbot.MailerBot(api_key="mb_live_...") as client:
    # 1. Create a contact list
    contact_list = client.contact_lists.create("My Campaign List")

    # 2. Add contacts (country_code defaults to "US" if omitted)
    contact = client.contacts.create(
        first_name="Jane",
        last_name="Smith",
        address_line1="123 Main St",
        city="Austin",
        state="TX",
        zip="78701",
    )
    client.contact_lists.add_contacts(contact_list.id, [contact.id])

    # 3. Write a letter document
    doc = client.documents.create(
        title="Spring Promo Letter",
        content="<p>Dear {{first_name}},</p><p>Check out our spring deals!</p>",
    )

    # 4. Create the mailing (postage defaults to cheapest rate per zone)
    mailing = client.mailings.create(
        name="Spring 2026 Promo",
        type="letter",
        contact_list_id=contact_list.id,
        document_id=doc.id,
    )

    # 5. Review the cost
    cost = client.mailings.calculate_cost(mailing.id)
    print(f"Product cost: ${cost.total_product_cost:.2f} ({cost.recipient_count} recipients)")
    for zone in cost.zone_counts:
        print(f"  {zone.postage_zone_name}: {zone.recipient_count} recipients")

    # 6. Create a Stripe payment intent, complete payment on your end, then send
    intent = client.payments.create_payment_intent(mailing.id)
    # ... complete Stripe payment using intent.client_secret ...

    # 7. Send
    sent = client.mailings.send(mailing.id)
    print(f"Mailing status: {sent.status}")

Bulk import contacts from CSV

with mailerbot.MailerBot(api_key="mb_live_...") as client:
    result = client.contacts.import_csv(
        contacts=[
            {"firstName": "Alice", "lastName": "Wu", "addressLine1": "456 Oak Ave",
             "city": "Dallas", "state": "TX", "zip": "75201"},
            {"firstName": "Bob", "lastName": "Smith", "addressLine1": "789 Pine Rd",
             "city": "Houston", "state": "TX", "zip": "77001"},
        ],
        list_name="Imported List",
    )
    print(f"Imported {result.imported_count} contacts into list {result.list_id}")

International contacts

with mailerbot.MailerBot(api_key="mb_live_...") as client:
    # List available countries
    countries = client.pricing.countries()
    for c in countries:
        print(f"  {c['code']} — {c['name']} (active: {c['isActive']})")

    # Create a Canadian contact
    contact = client.contacts.create(
        first_name="Marie",
        last_name="Tremblay",
        address_line1="350 Rue Saint-Paul",
        city="Montréal",
        state="QC",
        zip="H2Y 1H2",
        country_code="CA",
    )

Estimate cost before creating a mailing

with mailerbot.MailerBot(api_key="mb_live_...") as client:
    cost = client.mailings.estimate_cost(
        type="postcard",
        contact_list_id="<list_id>",
    )
    print(f"Product cost: ${cost.total_product_cost:.2f}")
    for zone in cost.zone_counts:
        print(f"  {zone.postage_zone_name}: {zone.recipient_count} recipients")
    for u in cost.unavailable:
        print(f"  ⚠ {u.country_name}: {u.recipient_count} recipients ({u.reason})")

    # See available postage rates
    rates = client.pricing.postage_rates(product_type="postcard")
    for r in rates:
        print(f"  {r['postageZoneName']} — {r['label']}: ${r['costPerPiece']:.2f}")

Iterate over all contacts (auto-pagination)

with mailerbot.MailerBot(api_key="mb_live_...") as client:
    for contact in client.contacts.iter_all(page_size=100):
        print(contact.first_name, contact.last_name)

Async equivalent:

async with mailerbot.AsyncMailerBot(api_key="mb_live_...") as client:
    async for contact in client.contacts.iter_all():
        print(contact.first_name, contact.last_name)

Send a postcard mailing

with mailerbot.MailerBot(api_key="mb_live_...") as client:
    # Browse available templates
    templates = client.postcards.list_templates()
    print(f"{len(templates)} templates available")

    # Create a postcard from scratch (or use the canvas builder in the dashboard)
    postcard = client.postcards.create(title="Summer Sale Card")

    mailing = client.mailings.create(
        name="Summer Postcard Drop",
        type="postcard",
        contact_list_id="<list_id>",
        postcard_id=postcard.id,
    )
    cost = client.mailings.calculate_cost(mailing.id)
    print(f"${cost.total_product_cost:.2f} for {cost.recipient_count} postcards")

Track QR code scans

with mailerbot.MailerBot(api_key="mb_live_...") as client:
    # Create a trackable short link
    link = client.qr.create("https://yoursite.com/promo")
    print(f"Short URL: {link.short_url}")

    # Get analytics
    analytics = client.qr.analytics(days=30)
    print(f"{analytics.total_scans} scans across {analytics.unique_links} links")
    for day in analytics.scans_by_day:
        print(f"  {day.date}: {day.count} scans")

Track USPS delivery per piece

with mailerbot.MailerBot(api_key="mb_live_...") as client:
    mailing = client.mailings.get("<mailing_id>")
    print(f"{mailing.items_delivered}/{mailing.item_count} delivered, {mailing.items_returned} returned")

    # Pieces USPS flagged as return-to-sender (filter: none, in_transit,
    # out_for_delivery, forwarded, delivered, returned)
    for item in client.mailings.iter_items(mailing.id, tracking_status="returned"):
        print(f"{item.recipient_name}: {item.last_scan_label} at {item.last_scan_location}")

    # Full scan history for one piece
    page = client.mailings.list_items(mailing.id, page_size=1)
    for scan in client.mailings.list_item_scans(mailing.id, page.items[0].id):
        print(f"  {scan.scan_datetime} {scan.label} ({scan.facility_city}, {scan.facility_state})")

Or subscribe to the mail_delivered and mail_returned webhook events to be pushed these updates instead of polling.

Use coupon codes in mailings

with mailerbot.MailerBot(api_key="mb_live_...") as client:
    # Create a coupon list and import codes
    coupon_list = client.coupons.create("Spring Sale Coupons")
    result = client.coupons.import_codes(coupon_list.id, ["SAVE10", "SAVE20", "SAVE30"])
    print(f"Imported {result.imported_count} codes")

    # Check there are enough codes before sending
    avail = client.coupons.check_availability(coupon_list.id, count=500)
    if not avail.sufficient:
        print(f"Only {avail.available_codes} codes available, need 500")

    # Attach to a mailing — each recipient gets a unique code
    mailing = client.mailings.create(
        name="Spring Promo",
        type="letter",
        contact_list_id="<list_id>",
        document_id="<doc_id>",
        coupon_list_id=coupon_list.id,
    )

Error Handling

import mailerbot

try:
    with mailerbot.MailerBot(api_key="bad_key") as client:
        client.contacts.list()
except mailerbot.AuthenticationError as e:
    print(f"Auth failed: {e}")
except mailerbot.NotFoundError as e:
    print(f"Resource not found: {e}")
except mailerbot.ValidationError as e:
    print(f"Bad request: {e} — details: {e.response}")
except mailerbot.MailerBotError as e:
    print(f"API error {e.status_code}: {e}")

Exception hierarchy

Exception HTTP status
AuthenticationError 401
PermissionError 403
NotFoundError 404
ValidationError 422
RateLimitError 429
ServerError 5xx
MailerBotError base class / other

Configuration

client = mailerbot.MailerBot(
    api_key="mb_live_...",
    base_url="https://api.mailerbot.com/api/v1",  # default
    timeout=30.0,                                  # seconds, default 30
)

You can also inject your own httpx.Client (or httpx.AsyncClient for the async variant) for custom transport, proxies, or test mocking:

import httpx
import mailerbot

transport = httpx.MockTransport(...)
with mailerbot.MailerBot(api_key="...", http_client=httpx.Client(transport=transport)) as client:
    ...

Full API Reference

See https://mailerbot.com/docs for complete endpoint documentation.

License

MIT

Release files for mailerbot 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mailerbot 0.1.0
File Size Uploaded
mailerbot-0.1.0.tar.gz 44.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for mailerbot 0.1.0
File Interpreter ABI Platform
mailerbot-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 75.5 kB

Release files / mailerbot-0.1.0.tar.gz

Download URL mailerbot-0.1.0.tar.gz
Size 44.6 kB
Tags Source
SHA-256 checksum
How to use checksums
0762e51cc78aad6fbd74697909a510cdf07919e5eef32b6294751471f5062893
BLAKE2b-256 checksum
How to use checksums
5e25299cdedf05428c3b30888120b7d228892b1eae728f78734d1f059f234c4b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.

Transparency log

Release files / mailerbot-0.1.0-py3-none-any.whl

Download URL mailerbot-0.1.0-py3-none-any.whl
Size 30.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3fb882fd320d12d13b289e62ce6f47a01034d8dcbcef93f2cd3e40830253e09d
BLAKE2b-256 checksum
How to use checksums
1f4f890fbbc5067aa1d551bb0409e1609652e860b42e260b4993a2a009cf401a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page