mailerbot 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. mailerbot-0.1.0/.gitignore +17 -0
  2. mailerbot-0.1.0/PKG-INFO +343 -0
  3. mailerbot-0.1.0/README.md +318 -0
  4. mailerbot-0.1.0/mailerbot/__init__.py +50 -0
  5. mailerbot-0.1.0/mailerbot/_http.py +53 -0
  6. mailerbot-0.1.0/mailerbot/_pagination.py +43 -0
  7. mailerbot-0.1.0/mailerbot/async_client.py +604 -0
  8. mailerbot-0.1.0/mailerbot/client.py +64 -0
  9. mailerbot-0.1.0/mailerbot/exceptions.py +30 -0
  10. mailerbot-0.1.0/mailerbot/models/__init__.py +28 -0
  11. mailerbot-0.1.0/mailerbot/models/assets.py +13 -0
  12. mailerbot-0.1.0/mailerbot/models/auth.py +62 -0
  13. mailerbot-0.1.0/mailerbot/models/campaigns.py +12 -0
  14. mailerbot-0.1.0/mailerbot/models/common.py +22 -0
  15. mailerbot-0.1.0/mailerbot/models/contacts.py +43 -0
  16. mailerbot-0.1.0/mailerbot/models/coupons.py +38 -0
  17. mailerbot-0.1.0/mailerbot/models/dashboard.py +25 -0
  18. mailerbot-0.1.0/mailerbot/models/documents.py +16 -0
  19. mailerbot-0.1.0/mailerbot/models/mailings.py +102 -0
  20. mailerbot-0.1.0/mailerbot/models/merge_tags.py +9 -0
  21. mailerbot-0.1.0/mailerbot/models/oauth.py +28 -0
  22. mailerbot-0.1.0/mailerbot/models/payments.py +20 -0
  23. mailerbot-0.1.0/mailerbot/models/postcards.py +32 -0
  24. mailerbot-0.1.0/mailerbot/models/qr.py +37 -0
  25. mailerbot-0.1.0/mailerbot/resources/__init__.py +0 -0
  26. mailerbot-0.1.0/mailerbot/resources/assets.py +40 -0
  27. mailerbot-0.1.0/mailerbot/resources/auth.py +58 -0
  28. mailerbot-0.1.0/mailerbot/resources/campaigns.py +62 -0
  29. mailerbot-0.1.0/mailerbot/resources/contact_lists.py +53 -0
  30. mailerbot-0.1.0/mailerbot/resources/contacts.py +97 -0
  31. mailerbot-0.1.0/mailerbot/resources/coupons.py +73 -0
  32. mailerbot-0.1.0/mailerbot/resources/dashboard.py +26 -0
  33. mailerbot-0.1.0/mailerbot/resources/documents.py +62 -0
  34. mailerbot-0.1.0/mailerbot/resources/mailings.py +145 -0
  35. mailerbot-0.1.0/mailerbot/resources/merge_tags.py +16 -0
  36. mailerbot-0.1.0/mailerbot/resources/oauth.py +38 -0
  37. mailerbot-0.1.0/mailerbot/resources/payments.py +33 -0
  38. mailerbot-0.1.0/mailerbot/resources/postcards.py +57 -0
  39. mailerbot-0.1.0/mailerbot/resources/pricing.py +35 -0
  40. mailerbot-0.1.0/mailerbot/resources/qr.py +54 -0
  41. mailerbot-0.1.0/pyproject.toml +44 -0
  42. mailerbot-0.1.0/test_local.py +681 -0
  43. mailerbot-0.1.0/tests/test_client.py +51 -0
  44. mailerbot-0.1.0/tests/test_contacts.py +62 -0
  45. mailerbot-0.1.0/tests/test_pagination.py +39 -0
  46. mailerbot-0.1.0/uv.lock +439 -0
@@ -0,0 +1,17 @@
1
+ # Environment
2
+ .env
3
+ .venv/
4
+
5
+ # Python
6
+ __pycache__/
7
+ *.pyc
8
+ *.pyo
9
+ dist/
10
+ build/
11
+ *.egg-info/
12
+ .pytest_cache/
13
+
14
+ # IDE
15
+ .idea/
16
+ .vscode/
17
+ *.swp
@@ -0,0 +1,343 @@
1
+ Metadata-Version: 2.5
2
+ Name: mailerbot
3
+ Version: 0.1.0
4
+ Summary: Python SDK for the MailerBot direct mail API
5
+ Project-URL: Homepage, https://mailerbot.com
6
+ Project-URL: Documentation, https://mailerbot.com/docs
7
+ Project-URL: Repository, https://github.com/mailerbot-hq/MailerBot/tree/dev/sdk/python
8
+ Project-URL: Bug Tracker, https://github.com/mailerbot-hq/MailerBot/issues
9
+ Author-email: MailerBot <dev@mailerbot.com>
10
+ License: MIT
11
+ Keywords: api,direct mail,mailerbot,postal,sdk
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: httpx>=0.27
23
+ Requires-Dist: pydantic[email]>=2.0
24
+ Description-Content-Type: text/markdown
25
+
26
+ # MailerBot Python SDK
27
+
28
+ Official Python SDK for the [MailerBot](https://mailerbot.com) direct mail API. Send letters and postcards programmatically.
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ pip install mailerbot
34
+ ```
35
+
36
+ Requires Python 3.10+.
37
+
38
+ ## Authentication
39
+
40
+ Generate an API key in your [MailerBot dashboard](https://app.mailerbot.com/settings/api-keys), then pass it to the client:
41
+
42
+ ```python
43
+ import mailerbot
44
+
45
+ client = mailerbot.MailerBot(api_key="mb_live_...")
46
+ ```
47
+
48
+ ## Quick Start
49
+
50
+ ### Synchronous
51
+
52
+ ```python
53
+ import mailerbot
54
+
55
+ with mailerbot.MailerBot(api_key="mb_live_...") as client:
56
+ # List contacts
57
+ page = client.contacts.list(page=1, page_size=25)
58
+ print(f"{page.total} total contacts")
59
+ for contact in page.items:
60
+ print(f" {contact.first_name} {contact.last_name}, {contact.city}, {contact.state}")
61
+
62
+ # Dashboard stats
63
+ stats = client.dashboard.stats()
64
+ print(f"Letters sent: {stats.letters_sent}")
65
+ print(f"Total spent: ${stats.total_spent:.2f}")
66
+ ```
67
+
68
+ ### Asynchronous
69
+
70
+ ```python
71
+ import asyncio
72
+ import mailerbot
73
+
74
+ async def main():
75
+ async with mailerbot.AsyncMailerBot(api_key="mb_live_...") as client:
76
+ page = await client.contacts.list()
77
+ print(f"{page.total} contacts")
78
+
79
+ asyncio.run(main())
80
+ ```
81
+
82
+ ## Common Workflows
83
+
84
+ ### Create and send a letter mailing
85
+
86
+ ```python
87
+ import mailerbot
88
+
89
+ with mailerbot.MailerBot(api_key="mb_live_...") as client:
90
+ # 1. Create a contact list
91
+ contact_list = client.contact_lists.create("My Campaign List")
92
+
93
+ # 2. Add contacts (country_code defaults to "US" if omitted)
94
+ contact = client.contacts.create(
95
+ first_name="Jane",
96
+ last_name="Smith",
97
+ address_line1="123 Main St",
98
+ city="Austin",
99
+ state="TX",
100
+ zip="78701",
101
+ )
102
+ client.contact_lists.add_contacts(contact_list.id, [contact.id])
103
+
104
+ # 3. Write a letter document
105
+ doc = client.documents.create(
106
+ title="Spring Promo Letter",
107
+ content="<p>Dear {{first_name}},</p><p>Check out our spring deals!</p>",
108
+ )
109
+
110
+ # 4. Create the mailing (postage defaults to cheapest rate per zone)
111
+ mailing = client.mailings.create(
112
+ name="Spring 2026 Promo",
113
+ type="letter",
114
+ contact_list_id=contact_list.id,
115
+ document_id=doc.id,
116
+ )
117
+
118
+ # 5. Review the cost
119
+ cost = client.mailings.calculate_cost(mailing.id)
120
+ print(f"Product cost: ${cost.total_product_cost:.2f} ({cost.recipient_count} recipients)")
121
+ for zone in cost.zone_counts:
122
+ print(f" {zone.postage_zone_name}: {zone.recipient_count} recipients")
123
+
124
+ # 6. Create a Stripe payment intent, complete payment on your end, then send
125
+ intent = client.payments.create_payment_intent(mailing.id)
126
+ # ... complete Stripe payment using intent.client_secret ...
127
+
128
+ # 7. Send
129
+ sent = client.mailings.send(mailing.id)
130
+ print(f"Mailing status: {sent.status}")
131
+ ```
132
+
133
+ ### Bulk import contacts from CSV
134
+
135
+ ```python
136
+ with mailerbot.MailerBot(api_key="mb_live_...") as client:
137
+ result = client.contacts.import_csv(
138
+ contacts=[
139
+ {"firstName": "Alice", "lastName": "Wu", "addressLine1": "456 Oak Ave",
140
+ "city": "Dallas", "state": "TX", "zip": "75201"},
141
+ {"firstName": "Bob", "lastName": "Smith", "addressLine1": "789 Pine Rd",
142
+ "city": "Houston", "state": "TX", "zip": "77001"},
143
+ ],
144
+ list_name="Imported List",
145
+ )
146
+ print(f"Imported {result.imported_count} contacts into list {result.list_id}")
147
+ ```
148
+
149
+ ### International contacts
150
+
151
+ ```python
152
+ with mailerbot.MailerBot(api_key="mb_live_...") as client:
153
+ # List available countries
154
+ countries = client.pricing.countries()
155
+ for c in countries:
156
+ print(f" {c['code']} — {c['name']} (active: {c['isActive']})")
157
+
158
+ # Create a Canadian contact
159
+ contact = client.contacts.create(
160
+ first_name="Marie",
161
+ last_name="Tremblay",
162
+ address_line1="350 Rue Saint-Paul",
163
+ city="Montréal",
164
+ state="QC",
165
+ zip="H2Y 1H2",
166
+ country_code="CA",
167
+ )
168
+ ```
169
+
170
+ ### Estimate cost before creating a mailing
171
+
172
+ ```python
173
+ with mailerbot.MailerBot(api_key="mb_live_...") as client:
174
+ cost = client.mailings.estimate_cost(
175
+ type="postcard",
176
+ contact_list_id="<list_id>",
177
+ )
178
+ print(f"Product cost: ${cost.total_product_cost:.2f}")
179
+ for zone in cost.zone_counts:
180
+ print(f" {zone.postage_zone_name}: {zone.recipient_count} recipients")
181
+ for u in cost.unavailable:
182
+ print(f" ⚠ {u.country_name}: {u.recipient_count} recipients ({u.reason})")
183
+
184
+ # See available postage rates
185
+ rates = client.pricing.postage_rates(product_type="postcard")
186
+ for r in rates:
187
+ print(f" {r['postageZoneName']} — {r['label']}: ${r['costPerPiece']:.2f}")
188
+ ```
189
+
190
+ ### Iterate over all contacts (auto-pagination)
191
+
192
+ ```python
193
+ with mailerbot.MailerBot(api_key="mb_live_...") as client:
194
+ for contact in client.contacts.iter_all(page_size=100):
195
+ print(contact.first_name, contact.last_name)
196
+ ```
197
+
198
+ Async equivalent:
199
+
200
+ ```python
201
+ async with mailerbot.AsyncMailerBot(api_key="mb_live_...") as client:
202
+ async for contact in client.contacts.iter_all():
203
+ print(contact.first_name, contact.last_name)
204
+ ```
205
+
206
+ ### Send a postcard mailing
207
+
208
+ ```python
209
+ with mailerbot.MailerBot(api_key="mb_live_...") as client:
210
+ # Browse available templates
211
+ templates = client.postcards.list_templates()
212
+ print(f"{len(templates)} templates available")
213
+
214
+ # Create a postcard from scratch (or use the canvas builder in the dashboard)
215
+ postcard = client.postcards.create(title="Summer Sale Card")
216
+
217
+ mailing = client.mailings.create(
218
+ name="Summer Postcard Drop",
219
+ type="postcard",
220
+ contact_list_id="<list_id>",
221
+ postcard_id=postcard.id,
222
+ )
223
+ cost = client.mailings.calculate_cost(mailing.id)
224
+ print(f"${cost.total_product_cost:.2f} for {cost.recipient_count} postcards")
225
+ ```
226
+
227
+ ### Track QR code scans
228
+
229
+ ```python
230
+ with mailerbot.MailerBot(api_key="mb_live_...") as client:
231
+ # Create a trackable short link
232
+ link = client.qr.create("https://yoursite.com/promo")
233
+ print(f"Short URL: {link.short_url}")
234
+
235
+ # Get analytics
236
+ analytics = client.qr.analytics(days=30)
237
+ print(f"{analytics.total_scans} scans across {analytics.unique_links} links")
238
+ for day in analytics.scans_by_day:
239
+ print(f" {day.date}: {day.count} scans")
240
+ ```
241
+
242
+ ### Track USPS delivery per piece
243
+
244
+ ```python
245
+ with mailerbot.MailerBot(api_key="mb_live_...") as client:
246
+ mailing = client.mailings.get("<mailing_id>")
247
+ print(f"{mailing.items_delivered}/{mailing.item_count} delivered, {mailing.items_returned} returned")
248
+
249
+ # Pieces USPS flagged as return-to-sender (filter: none, in_transit,
250
+ # out_for_delivery, forwarded, delivered, returned)
251
+ for item in client.mailings.iter_items(mailing.id, tracking_status="returned"):
252
+ print(f"{item.recipient_name}: {item.last_scan_label} at {item.last_scan_location}")
253
+
254
+ # Full scan history for one piece
255
+ page = client.mailings.list_items(mailing.id, page_size=1)
256
+ for scan in client.mailings.list_item_scans(mailing.id, page.items[0].id):
257
+ print(f" {scan.scan_datetime} {scan.label} ({scan.facility_city}, {scan.facility_state})")
258
+ ```
259
+
260
+ Or subscribe to the `mail_delivered` and `mail_returned` webhook events to be pushed these updates instead of polling.
261
+
262
+ ### Use coupon codes in mailings
263
+
264
+ ```python
265
+ with mailerbot.MailerBot(api_key="mb_live_...") as client:
266
+ # Create a coupon list and import codes
267
+ coupon_list = client.coupons.create("Spring Sale Coupons")
268
+ result = client.coupons.import_codes(coupon_list.id, ["SAVE10", "SAVE20", "SAVE30"])
269
+ print(f"Imported {result.imported_count} codes")
270
+
271
+ # Check there are enough codes before sending
272
+ avail = client.coupons.check_availability(coupon_list.id, count=500)
273
+ if not avail.sufficient:
274
+ print(f"Only {avail.available_codes} codes available, need 500")
275
+
276
+ # Attach to a mailing — each recipient gets a unique code
277
+ mailing = client.mailings.create(
278
+ name="Spring Promo",
279
+ type="letter",
280
+ contact_list_id="<list_id>",
281
+ document_id="<doc_id>",
282
+ coupon_list_id=coupon_list.id,
283
+ )
284
+ ```
285
+
286
+ ## Error Handling
287
+
288
+ ```python
289
+ import mailerbot
290
+
291
+ try:
292
+ with mailerbot.MailerBot(api_key="bad_key") as client:
293
+ client.contacts.list()
294
+ except mailerbot.AuthenticationError as e:
295
+ print(f"Auth failed: {e}")
296
+ except mailerbot.NotFoundError as e:
297
+ print(f"Resource not found: {e}")
298
+ except mailerbot.ValidationError as e:
299
+ print(f"Bad request: {e} — details: {e.response}")
300
+ except mailerbot.MailerBotError as e:
301
+ print(f"API error {e.status_code}: {e}")
302
+ ```
303
+
304
+ ### Exception hierarchy
305
+
306
+ | Exception | HTTP status |
307
+ |-----------|-------------|
308
+ | `AuthenticationError` | 401 |
309
+ | `PermissionError` | 403 |
310
+ | `NotFoundError` | 404 |
311
+ | `ValidationError` | 422 |
312
+ | `RateLimitError` | 429 |
313
+ | `ServerError` | 5xx |
314
+ | `MailerBotError` | base class / other |
315
+
316
+ ## Configuration
317
+
318
+ ```python
319
+ client = mailerbot.MailerBot(
320
+ api_key="mb_live_...",
321
+ base_url="https://api.mailerbot.com/api/v1", # default
322
+ timeout=30.0, # seconds, default 30
323
+ )
324
+ ```
325
+
326
+ You can also inject your own `httpx.Client` (or `httpx.AsyncClient` for the async variant) for custom transport, proxies, or test mocking:
327
+
328
+ ```python
329
+ import httpx
330
+ import mailerbot
331
+
332
+ transport = httpx.MockTransport(...)
333
+ with mailerbot.MailerBot(api_key="...", http_client=httpx.Client(transport=transport)) as client:
334
+ ...
335
+ ```
336
+
337
+ ## Full API Reference
338
+
339
+ See [https://mailerbot.com/docs](https://mailerbot.com/docs) for complete endpoint documentation.
340
+
341
+ ## License
342
+
343
+ MIT
@@ -0,0 +1,318 @@
1
+ # MailerBot Python SDK
2
+
3
+ Official Python SDK for the [MailerBot](https://mailerbot.com) direct mail API. Send letters and postcards programmatically.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install mailerbot
9
+ ```
10
+
11
+ Requires Python 3.10+.
12
+
13
+ ## Authentication
14
+
15
+ Generate an API key in your [MailerBot dashboard](https://app.mailerbot.com/settings/api-keys), then pass it to the client:
16
+
17
+ ```python
18
+ import mailerbot
19
+
20
+ client = mailerbot.MailerBot(api_key="mb_live_...")
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ### Synchronous
26
+
27
+ ```python
28
+ import mailerbot
29
+
30
+ with mailerbot.MailerBot(api_key="mb_live_...") as client:
31
+ # List contacts
32
+ page = client.contacts.list(page=1, page_size=25)
33
+ print(f"{page.total} total contacts")
34
+ for contact in page.items:
35
+ print(f" {contact.first_name} {contact.last_name}, {contact.city}, {contact.state}")
36
+
37
+ # Dashboard stats
38
+ stats = client.dashboard.stats()
39
+ print(f"Letters sent: {stats.letters_sent}")
40
+ print(f"Total spent: ${stats.total_spent:.2f}")
41
+ ```
42
+
43
+ ### Asynchronous
44
+
45
+ ```python
46
+ import asyncio
47
+ import mailerbot
48
+
49
+ async def main():
50
+ async with mailerbot.AsyncMailerBot(api_key="mb_live_...") as client:
51
+ page = await client.contacts.list()
52
+ print(f"{page.total} contacts")
53
+
54
+ asyncio.run(main())
55
+ ```
56
+
57
+ ## Common Workflows
58
+
59
+ ### Create and send a letter mailing
60
+
61
+ ```python
62
+ import mailerbot
63
+
64
+ with mailerbot.MailerBot(api_key="mb_live_...") as client:
65
+ # 1. Create a contact list
66
+ contact_list = client.contact_lists.create("My Campaign List")
67
+
68
+ # 2. Add contacts (country_code defaults to "US" if omitted)
69
+ contact = client.contacts.create(
70
+ first_name="Jane",
71
+ last_name="Smith",
72
+ address_line1="123 Main St",
73
+ city="Austin",
74
+ state="TX",
75
+ zip="78701",
76
+ )
77
+ client.contact_lists.add_contacts(contact_list.id, [contact.id])
78
+
79
+ # 3. Write a letter document
80
+ doc = client.documents.create(
81
+ title="Spring Promo Letter",
82
+ content="<p>Dear {{first_name}},</p><p>Check out our spring deals!</p>",
83
+ )
84
+
85
+ # 4. Create the mailing (postage defaults to cheapest rate per zone)
86
+ mailing = client.mailings.create(
87
+ name="Spring 2026 Promo",
88
+ type="letter",
89
+ contact_list_id=contact_list.id,
90
+ document_id=doc.id,
91
+ )
92
+
93
+ # 5. Review the cost
94
+ cost = client.mailings.calculate_cost(mailing.id)
95
+ print(f"Product cost: ${cost.total_product_cost:.2f} ({cost.recipient_count} recipients)")
96
+ for zone in cost.zone_counts:
97
+ print(f" {zone.postage_zone_name}: {zone.recipient_count} recipients")
98
+
99
+ # 6. Create a Stripe payment intent, complete payment on your end, then send
100
+ intent = client.payments.create_payment_intent(mailing.id)
101
+ # ... complete Stripe payment using intent.client_secret ...
102
+
103
+ # 7. Send
104
+ sent = client.mailings.send(mailing.id)
105
+ print(f"Mailing status: {sent.status}")
106
+ ```
107
+
108
+ ### Bulk import contacts from CSV
109
+
110
+ ```python
111
+ with mailerbot.MailerBot(api_key="mb_live_...") as client:
112
+ result = client.contacts.import_csv(
113
+ contacts=[
114
+ {"firstName": "Alice", "lastName": "Wu", "addressLine1": "456 Oak Ave",
115
+ "city": "Dallas", "state": "TX", "zip": "75201"},
116
+ {"firstName": "Bob", "lastName": "Smith", "addressLine1": "789 Pine Rd",
117
+ "city": "Houston", "state": "TX", "zip": "77001"},
118
+ ],
119
+ list_name="Imported List",
120
+ )
121
+ print(f"Imported {result.imported_count} contacts into list {result.list_id}")
122
+ ```
123
+
124
+ ### International contacts
125
+
126
+ ```python
127
+ with mailerbot.MailerBot(api_key="mb_live_...") as client:
128
+ # List available countries
129
+ countries = client.pricing.countries()
130
+ for c in countries:
131
+ print(f" {c['code']} — {c['name']} (active: {c['isActive']})")
132
+
133
+ # Create a Canadian contact
134
+ contact = client.contacts.create(
135
+ first_name="Marie",
136
+ last_name="Tremblay",
137
+ address_line1="350 Rue Saint-Paul",
138
+ city="Montréal",
139
+ state="QC",
140
+ zip="H2Y 1H2",
141
+ country_code="CA",
142
+ )
143
+ ```
144
+
145
+ ### Estimate cost before creating a mailing
146
+
147
+ ```python
148
+ with mailerbot.MailerBot(api_key="mb_live_...") as client:
149
+ cost = client.mailings.estimate_cost(
150
+ type="postcard",
151
+ contact_list_id="<list_id>",
152
+ )
153
+ print(f"Product cost: ${cost.total_product_cost:.2f}")
154
+ for zone in cost.zone_counts:
155
+ print(f" {zone.postage_zone_name}: {zone.recipient_count} recipients")
156
+ for u in cost.unavailable:
157
+ print(f" ⚠ {u.country_name}: {u.recipient_count} recipients ({u.reason})")
158
+
159
+ # See available postage rates
160
+ rates = client.pricing.postage_rates(product_type="postcard")
161
+ for r in rates:
162
+ print(f" {r['postageZoneName']} — {r['label']}: ${r['costPerPiece']:.2f}")
163
+ ```
164
+
165
+ ### Iterate over all contacts (auto-pagination)
166
+
167
+ ```python
168
+ with mailerbot.MailerBot(api_key="mb_live_...") as client:
169
+ for contact in client.contacts.iter_all(page_size=100):
170
+ print(contact.first_name, contact.last_name)
171
+ ```
172
+
173
+ Async equivalent:
174
+
175
+ ```python
176
+ async with mailerbot.AsyncMailerBot(api_key="mb_live_...") as client:
177
+ async for contact in client.contacts.iter_all():
178
+ print(contact.first_name, contact.last_name)
179
+ ```
180
+
181
+ ### Send a postcard mailing
182
+
183
+ ```python
184
+ with mailerbot.MailerBot(api_key="mb_live_...") as client:
185
+ # Browse available templates
186
+ templates = client.postcards.list_templates()
187
+ print(f"{len(templates)} templates available")
188
+
189
+ # Create a postcard from scratch (or use the canvas builder in the dashboard)
190
+ postcard = client.postcards.create(title="Summer Sale Card")
191
+
192
+ mailing = client.mailings.create(
193
+ name="Summer Postcard Drop",
194
+ type="postcard",
195
+ contact_list_id="<list_id>",
196
+ postcard_id=postcard.id,
197
+ )
198
+ cost = client.mailings.calculate_cost(mailing.id)
199
+ print(f"${cost.total_product_cost:.2f} for {cost.recipient_count} postcards")
200
+ ```
201
+
202
+ ### Track QR code scans
203
+
204
+ ```python
205
+ with mailerbot.MailerBot(api_key="mb_live_...") as client:
206
+ # Create a trackable short link
207
+ link = client.qr.create("https://yoursite.com/promo")
208
+ print(f"Short URL: {link.short_url}")
209
+
210
+ # Get analytics
211
+ analytics = client.qr.analytics(days=30)
212
+ print(f"{analytics.total_scans} scans across {analytics.unique_links} links")
213
+ for day in analytics.scans_by_day:
214
+ print(f" {day.date}: {day.count} scans")
215
+ ```
216
+
217
+ ### Track USPS delivery per piece
218
+
219
+ ```python
220
+ with mailerbot.MailerBot(api_key="mb_live_...") as client:
221
+ mailing = client.mailings.get("<mailing_id>")
222
+ print(f"{mailing.items_delivered}/{mailing.item_count} delivered, {mailing.items_returned} returned")
223
+
224
+ # Pieces USPS flagged as return-to-sender (filter: none, in_transit,
225
+ # out_for_delivery, forwarded, delivered, returned)
226
+ for item in client.mailings.iter_items(mailing.id, tracking_status="returned"):
227
+ print(f"{item.recipient_name}: {item.last_scan_label} at {item.last_scan_location}")
228
+
229
+ # Full scan history for one piece
230
+ page = client.mailings.list_items(mailing.id, page_size=1)
231
+ for scan in client.mailings.list_item_scans(mailing.id, page.items[0].id):
232
+ print(f" {scan.scan_datetime} {scan.label} ({scan.facility_city}, {scan.facility_state})")
233
+ ```
234
+
235
+ Or subscribe to the `mail_delivered` and `mail_returned` webhook events to be pushed these updates instead of polling.
236
+
237
+ ### Use coupon codes in mailings
238
+
239
+ ```python
240
+ with mailerbot.MailerBot(api_key="mb_live_...") as client:
241
+ # Create a coupon list and import codes
242
+ coupon_list = client.coupons.create("Spring Sale Coupons")
243
+ result = client.coupons.import_codes(coupon_list.id, ["SAVE10", "SAVE20", "SAVE30"])
244
+ print(f"Imported {result.imported_count} codes")
245
+
246
+ # Check there are enough codes before sending
247
+ avail = client.coupons.check_availability(coupon_list.id, count=500)
248
+ if not avail.sufficient:
249
+ print(f"Only {avail.available_codes} codes available, need 500")
250
+
251
+ # Attach to a mailing — each recipient gets a unique code
252
+ mailing = client.mailings.create(
253
+ name="Spring Promo",
254
+ type="letter",
255
+ contact_list_id="<list_id>",
256
+ document_id="<doc_id>",
257
+ coupon_list_id=coupon_list.id,
258
+ )
259
+ ```
260
+
261
+ ## Error Handling
262
+
263
+ ```python
264
+ import mailerbot
265
+
266
+ try:
267
+ with mailerbot.MailerBot(api_key="bad_key") as client:
268
+ client.contacts.list()
269
+ except mailerbot.AuthenticationError as e:
270
+ print(f"Auth failed: {e}")
271
+ except mailerbot.NotFoundError as e:
272
+ print(f"Resource not found: {e}")
273
+ except mailerbot.ValidationError as e:
274
+ print(f"Bad request: {e} — details: {e.response}")
275
+ except mailerbot.MailerBotError as e:
276
+ print(f"API error {e.status_code}: {e}")
277
+ ```
278
+
279
+ ### Exception hierarchy
280
+
281
+ | Exception | HTTP status |
282
+ |-----------|-------------|
283
+ | `AuthenticationError` | 401 |
284
+ | `PermissionError` | 403 |
285
+ | `NotFoundError` | 404 |
286
+ | `ValidationError` | 422 |
287
+ | `RateLimitError` | 429 |
288
+ | `ServerError` | 5xx |
289
+ | `MailerBotError` | base class / other |
290
+
291
+ ## Configuration
292
+
293
+ ```python
294
+ client = mailerbot.MailerBot(
295
+ api_key="mb_live_...",
296
+ base_url="https://api.mailerbot.com/api/v1", # default
297
+ timeout=30.0, # seconds, default 30
298
+ )
299
+ ```
300
+
301
+ You can also inject your own `httpx.Client` (or `httpx.AsyncClient` for the async variant) for custom transport, proxies, or test mocking:
302
+
303
+ ```python
304
+ import httpx
305
+ import mailerbot
306
+
307
+ transport = httpx.MockTransport(...)
308
+ with mailerbot.MailerBot(api_key="...", http_client=httpx.Client(transport=transport)) as client:
309
+ ...
310
+ ```
311
+
312
+ ## Full API Reference
313
+
314
+ See [https://mailerbot.com/docs](https://mailerbot.com/docs) for complete endpoint documentation.
315
+
316
+ ## License
317
+
318
+ MIT