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)
| File | Size | Uploaded | |
|---|---|---|---|
| mailerbot-0.1.0.tar.gz | 44.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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 logRelease 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