emailit 2.0.1__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 (109) hide show
  1. emailit-2.0.1/LICENSE +21 -0
  2. emailit-2.0.1/PKG-INFO +574 -0
  3. emailit-2.0.1/README.md +541 -0
  4. emailit-2.0.1/pyproject.toml +50 -0
  5. emailit-2.0.1/setup.cfg +4 -0
  6. emailit-2.0.1/src/emailit/__init__.py +65 -0
  7. emailit-2.0.1/src/emailit/_api_resource.py +27 -0
  8. emailit-2.0.1/src/emailit/_api_response.py +23 -0
  9. emailit-2.0.1/src/emailit/_base_client.py +125 -0
  10. emailit-2.0.1/src/emailit/_client.py +77 -0
  11. emailit-2.0.1/src/emailit/_collection.py +20 -0
  12. emailit-2.0.1/src/emailit/_emailit_object.py +84 -0
  13. emailit-2.0.1/src/emailit/_util.py +55 -0
  14. emailit-2.0.1/src/emailit/_webhook_signature.py +67 -0
  15. emailit-2.0.1/src/emailit/events/__init__.py +77 -0
  16. emailit-2.0.1/src/emailit/events/audience_created.py +5 -0
  17. emailit-2.0.1/src/emailit/events/audience_deleted.py +5 -0
  18. emailit-2.0.1/src/emailit/events/audience_updated.py +5 -0
  19. emailit-2.0.1/src/emailit/events/contact_created.py +5 -0
  20. emailit-2.0.1/src/emailit/events/contact_deleted.py +5 -0
  21. emailit-2.0.1/src/emailit/events/contact_updated.py +5 -0
  22. emailit-2.0.1/src/emailit/events/domain_created.py +5 -0
  23. emailit-2.0.1/src/emailit/events/domain_deleted.py +5 -0
  24. emailit-2.0.1/src/emailit/events/domain_updated.py +5 -0
  25. emailit-2.0.1/src/emailit/events/email_accepted.py +5 -0
  26. emailit-2.0.1/src/emailit/events/email_attempted.py +5 -0
  27. emailit-2.0.1/src/emailit/events/email_bounced.py +5 -0
  28. emailit-2.0.1/src/emailit/events/email_clicked.py +5 -0
  29. emailit-2.0.1/src/emailit/events/email_complained.py +5 -0
  30. emailit-2.0.1/src/emailit/events/email_delivered.py +5 -0
  31. emailit-2.0.1/src/emailit/events/email_failed.py +5 -0
  32. emailit-2.0.1/src/emailit/events/email_loaded.py +5 -0
  33. emailit-2.0.1/src/emailit/events/email_received.py +5 -0
  34. emailit-2.0.1/src/emailit/events/email_rejected.py +5 -0
  35. emailit-2.0.1/src/emailit/events/email_scheduled.py +5 -0
  36. emailit-2.0.1/src/emailit/events/email_suppressed.py +5 -0
  37. emailit-2.0.1/src/emailit/events/email_verification_created.py +5 -0
  38. emailit-2.0.1/src/emailit/events/email_verification_deleted.py +5 -0
  39. emailit-2.0.1/src/emailit/events/email_verification_list_created.py +5 -0
  40. emailit-2.0.1/src/emailit/events/email_verification_list_deleted.py +5 -0
  41. emailit-2.0.1/src/emailit/events/email_verification_list_updated.py +5 -0
  42. emailit-2.0.1/src/emailit/events/email_verification_updated.py +5 -0
  43. emailit-2.0.1/src/emailit/events/subscriber_created.py +5 -0
  44. emailit-2.0.1/src/emailit/events/subscriber_deleted.py +5 -0
  45. emailit-2.0.1/src/emailit/events/subscriber_updated.py +5 -0
  46. emailit-2.0.1/src/emailit/events/suppression_created.py +5 -0
  47. emailit-2.0.1/src/emailit/events/suppression_deleted.py +5 -0
  48. emailit-2.0.1/src/emailit/events/suppression_updated.py +5 -0
  49. emailit-2.0.1/src/emailit/events/template_created.py +5 -0
  50. emailit-2.0.1/src/emailit/events/template_deleted.py +5 -0
  51. emailit-2.0.1/src/emailit/events/template_updated.py +5 -0
  52. emailit-2.0.1/src/emailit/events/webhook_event.py +109 -0
  53. emailit-2.0.1/src/emailit/exceptions/__init__.py +15 -0
  54. emailit-2.0.1/src/emailit/exceptions/api_connection.py +5 -0
  55. emailit-2.0.1/src/emailit/exceptions/api_error.py +44 -0
  56. emailit-2.0.1/src/emailit/exceptions/authentication.py +5 -0
  57. emailit-2.0.1/src/emailit/exceptions/invalid_request.py +5 -0
  58. emailit-2.0.1/src/emailit/exceptions/rate_limit.py +5 -0
  59. emailit-2.0.1/src/emailit/exceptions/unprocessable_entity.py +5 -0
  60. emailit-2.0.1/src/emailit/resources/__init__.py +27 -0
  61. emailit-2.0.1/src/emailit/resources/api_key.py +5 -0
  62. emailit-2.0.1/src/emailit/resources/audience.py +5 -0
  63. emailit-2.0.1/src/emailit/resources/contact.py +5 -0
  64. emailit-2.0.1/src/emailit/resources/domain.py +5 -0
  65. emailit-2.0.1/src/emailit/resources/email.py +5 -0
  66. emailit-2.0.1/src/emailit/resources/email_verification.py +5 -0
  67. emailit-2.0.1/src/emailit/resources/email_verification_list.py +5 -0
  68. emailit-2.0.1/src/emailit/resources/event.py +5 -0
  69. emailit-2.0.1/src/emailit/resources/subscriber.py +5 -0
  70. emailit-2.0.1/src/emailit/resources/suppression.py +5 -0
  71. emailit-2.0.1/src/emailit/resources/template.py +5 -0
  72. emailit-2.0.1/src/emailit/resources/webhook.py +5 -0
  73. emailit-2.0.1/src/emailit/services/__init__.py +27 -0
  74. emailit-2.0.1/src/emailit/services/_abstract_service.py +63 -0
  75. emailit-2.0.1/src/emailit/services/api_key_service.py +25 -0
  76. emailit-2.0.1/src/emailit/services/audience_service.py +25 -0
  77. emailit-2.0.1/src/emailit/services/contact_service.py +25 -0
  78. emailit-2.0.1/src/emailit/services/domain_service.py +28 -0
  79. emailit-2.0.1/src/emailit/services/email_service.py +40 -0
  80. emailit-2.0.1/src/emailit/services/email_verification_list_service.py +33 -0
  81. emailit-2.0.1/src/emailit/services/email_verification_service.py +12 -0
  82. emailit-2.0.1/src/emailit/services/event_service.py +16 -0
  83. emailit-2.0.1/src/emailit/services/subscriber_service.py +43 -0
  84. emailit-2.0.1/src/emailit/services/suppression_service.py +25 -0
  85. emailit-2.0.1/src/emailit/services/template_service.py +28 -0
  86. emailit-2.0.1/src/emailit/services/webhook_service.py +25 -0
  87. emailit-2.0.1/src/emailit.egg-info/PKG-INFO +574 -0
  88. emailit-2.0.1/src/emailit.egg-info/SOURCES.txt +107 -0
  89. emailit-2.0.1/src/emailit.egg-info/dependency_links.txt +1 -0
  90. emailit-2.0.1/src/emailit.egg-info/requires.txt +5 -0
  91. emailit-2.0.1/src/emailit.egg-info/top_level.txt +1 -0
  92. emailit-2.0.1/tests/test_api_key_service.py +83 -0
  93. emailit-2.0.1/tests/test_api_response.py +29 -0
  94. emailit-2.0.1/tests/test_audience_service.py +85 -0
  95. emailit-2.0.1/tests/test_client.py +60 -0
  96. emailit-2.0.1/tests/test_contact_service.py +84 -0
  97. emailit-2.0.1/tests/test_domain_service.py +119 -0
  98. emailit-2.0.1/tests/test_email_service.py +555 -0
  99. emailit-2.0.1/tests/test_email_verification_list_service.py +129 -0
  100. emailit-2.0.1/tests/test_email_verification_service.py +47 -0
  101. emailit-2.0.1/tests/test_event_service.py +60 -0
  102. emailit-2.0.1/tests/test_exceptions.py +83 -0
  103. emailit-2.0.1/tests/test_resource.py +210 -0
  104. emailit-2.0.1/tests/test_subscriber_service.py +109 -0
  105. emailit-2.0.1/tests/test_suppression_service.py +74 -0
  106. emailit-2.0.1/tests/test_template_service.py +85 -0
  107. emailit-2.0.1/tests/test_webhook_event.py +139 -0
  108. emailit-2.0.1/tests/test_webhook_service.py +80 -0
  109. emailit-2.0.1/tests/test_webhook_signature.py +118 -0
emailit-2.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Emailit
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
emailit-2.0.1/PKG-INFO ADDED
@@ -0,0 +1,574 @@
1
+ Metadata-Version: 2.4
2
+ Name: emailit
3
+ Version: 2.0.1
4
+ Summary: Official Python SDK for the Emailit Email API
5
+ Author-email: Emailit <support@emailit.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://emailit.com
8
+ Project-URL: Documentation, https://emailit.com/docs
9
+ Project-URL: Repository, https://github.com/emailit/emailit-python
10
+ Project-URL: Changelog, https://github.com/emailit/emailit-python/releases
11
+ Keywords: emailit,email,api,sdk
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.7
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Communications :: Email
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.7
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: requests>=2.20
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=7.0; extra == "dev"
31
+ Requires-Dist: responses>=0.20; extra == "dev"
32
+ Dynamic: license-file
33
+
34
+ # Emailit Python
35
+
36
+ [![Tests](https://img.shields.io/github/actions/workflow/status/emailit/emailit-python/tests.yml?label=tests&style=for-the-badge&labelColor=111827)](https://github.com/emailit/emailit-python/actions)
37
+ [![PyPI Version](https://img.shields.io/pypi/v/emailit?style=for-the-badge&labelColor=111827)](https://pypi.org/project/emailit/)
38
+ [![License](https://img.shields.io/github/license/emailit/emailit-python?style=for-the-badge&labelColor=111827)](https://github.com/emailit/emailit-python/blob/main/LICENSE)
39
+
40
+ The official Python SDK for the [Emailit](https://emailit.com) Email API.
41
+
42
+ ## Requirements
43
+
44
+ - Python 3.7+
45
+ - [Requests](https://requests.readthedocs.io/) 2.20+
46
+
47
+ ## Installation
48
+
49
+ ```bash
50
+ pip install emailit
51
+ ```
52
+
53
+ ## Getting Started
54
+
55
+ ```python
56
+ from emailit import EmailitClient
57
+
58
+ client = EmailitClient("your_api_key")
59
+
60
+ email = client.emails.send({
61
+ "from": "hello@yourdomain.com",
62
+ "to": ["user@example.com"],
63
+ "subject": "Hello from Emailit",
64
+ "html": "<h1>Welcome!</h1><p>Thanks for signing up.</p>",
65
+ })
66
+
67
+ print(email.id) # em_abc123...
68
+ print(email.status) # pending
69
+ ```
70
+
71
+ All service methods return typed resource objects (`Email`, `Domain`, `Contact`, etc.) with direct attribute access -- just like the Stripe SDK.
72
+
73
+ ## Available Services
74
+
75
+ | Service | Property | Description |
76
+ |---------|----------|-------------|
77
+ | Emails | `client.emails` | Send, list, get, cancel, retry emails |
78
+ | Domains | `client.domains` | Create, verify, list, manage sending domains |
79
+ | API Keys | `client.api_keys` | Create, list, manage API keys |
80
+ | Audiences | `client.audiences` | Create, list, manage audiences |
81
+ | Subscribers | `client.subscribers` | Add, list, manage subscribers in audiences |
82
+ | Templates | `client.templates` | Create, list, publish email templates |
83
+ | Suppressions | `client.suppressions` | Create, list, manage suppressed addresses |
84
+ | Email Verifications | `client.email_verifications` | Verify email addresses |
85
+ | Email Verification Lists | `client.email_verification_lists` | Create, list, get results, export |
86
+ | Webhooks | `client.webhooks` | Create, list, manage webhooks |
87
+ | Contacts | `client.contacts` | Create, list, manage contacts |
88
+ | Events | `client.events` | List and retrieve events |
89
+
90
+ ## Usage
91
+
92
+ ### Emails
93
+
94
+ #### Send an email
95
+
96
+ ```python
97
+ email = client.emails.send({
98
+ "from": "hello@yourdomain.com",
99
+ "to": ["user@example.com"],
100
+ "subject": "Hello from Emailit",
101
+ "html": "<h1>Welcome!</h1>",
102
+ })
103
+
104
+ print(email.id)
105
+ print(email.status)
106
+ ```
107
+
108
+ #### Send with a template
109
+
110
+ ```python
111
+ email = client.emails.send({
112
+ "from": "hello@yourdomain.com",
113
+ "to": "user@example.com",
114
+ "template": "welcome_email",
115
+ "variables": {
116
+ "name": "John Doe",
117
+ "company": "Acme Inc",
118
+ },
119
+ })
120
+ ```
121
+
122
+ #### Send with attachments
123
+
124
+ ```python
125
+ import base64
126
+
127
+ email = client.emails.send({
128
+ "from": "invoices@yourdomain.com",
129
+ "to": "customer@example.com",
130
+ "subject": "Your Invoice #12345",
131
+ "html": "<p>Please find your invoice attached.</p>",
132
+ "attachments": [
133
+ {
134
+ "filename": "invoice.pdf",
135
+ "content": base64.b64encode(open("invoice.pdf", "rb").read()).decode(),
136
+ "content_type": "application/pdf",
137
+ },
138
+ ],
139
+ })
140
+ ```
141
+
142
+ #### Schedule an email
143
+
144
+ ```python
145
+ email = client.emails.send({
146
+ "from": "reminders@yourdomain.com",
147
+ "to": "user@example.com",
148
+ "subject": "Appointment Reminder",
149
+ "html": "<p>Your appointment is tomorrow at 2 PM.</p>",
150
+ "scheduled_at": "2026-01-10T09:00:00Z",
151
+ })
152
+
153
+ print(email.status) # scheduled
154
+ print(email.scheduled_at) # 2026-01-10T09:00:00Z
155
+ ```
156
+
157
+ #### List emails
158
+
159
+ ```python
160
+ emails = client.emails.list({"page": 1, "limit": 10})
161
+
162
+ for email in emails:
163
+ print(f"{email.id} — {email.status}")
164
+
165
+ if emails.has_more():
166
+ # fetch next page
167
+ pass
168
+ ```
169
+
170
+ #### Cancel / Retry
171
+
172
+ ```python
173
+ client.emails.cancel("em_abc123")
174
+ client.emails.retry("em_abc123")
175
+ ```
176
+
177
+ ---
178
+
179
+ ### Domains
180
+
181
+ ```python
182
+ # Create a domain
183
+ domain = client.domains.create({
184
+ "name": "example.com",
185
+ "track_loads": True,
186
+ "track_clicks": True,
187
+ })
188
+ print(domain.id)
189
+
190
+ # Verify DNS
191
+ domain = client.domains.verify("sd_123")
192
+
193
+ # List all domains
194
+ domains = client.domains.list()
195
+
196
+ # Get a domain
197
+ domain = client.domains.get("sd_123")
198
+
199
+ # Update a domain
200
+ domain = client.domains.update("sd_123", {"track_clicks": False})
201
+
202
+ # Delete a domain
203
+ client.domains.delete("sd_123")
204
+ ```
205
+
206
+ ---
207
+
208
+ ### API Keys
209
+
210
+ ```python
211
+ # Create an API key
212
+ key = client.api_keys.create({
213
+ "name": "Production Key",
214
+ "scope": "full",
215
+ })
216
+ print(key.key) # only available on create
217
+
218
+ # List all API keys
219
+ keys = client.api_keys.list()
220
+
221
+ # Get an API key
222
+ key = client.api_keys.get("ak_123")
223
+
224
+ # Update an API key
225
+ client.api_keys.update("ak_123", {"name": "Renamed Key"})
226
+
227
+ # Delete an API key
228
+ client.api_keys.delete("ak_123")
229
+ ```
230
+
231
+ ---
232
+
233
+ ### Audiences
234
+
235
+ ```python
236
+ # Create an audience
237
+ audience = client.audiences.create({"name": "Newsletter"})
238
+ print(audience.id)
239
+ print(audience.token)
240
+
241
+ # List audiences
242
+ audiences = client.audiences.list()
243
+
244
+ # Get an audience
245
+ audience = client.audiences.get("aud_123")
246
+
247
+ # Update an audience
248
+ client.audiences.update("aud_123", {"name": "Updated Newsletter"})
249
+
250
+ # Delete an audience
251
+ client.audiences.delete("aud_123")
252
+ ```
253
+
254
+ ---
255
+
256
+ ### Subscribers
257
+
258
+ Subscribers belong to an audience, so the audience ID is always the first argument.
259
+
260
+ ```python
261
+ # Add a subscriber
262
+ subscriber = client.subscribers.create("aud_123", {
263
+ "email": "user@example.com",
264
+ "first_name": "John",
265
+ "last_name": "Doe",
266
+ })
267
+
268
+ # List subscribers in an audience
269
+ subscribers = client.subscribers.list("aud_123")
270
+
271
+ # Get a subscriber
272
+ subscriber = client.subscribers.get("aud_123", "sub_456")
273
+
274
+ # Update a subscriber
275
+ client.subscribers.update("aud_123", "sub_456", {
276
+ "first_name": "Jane",
277
+ })
278
+
279
+ # Delete a subscriber
280
+ client.subscribers.delete("aud_123", "sub_456")
281
+ ```
282
+
283
+ ---
284
+
285
+ ### Templates
286
+
287
+ ```python
288
+ # Create a template
289
+ result = client.templates.create({
290
+ "name": "Welcome",
291
+ "subject": "Welcome!",
292
+ "html": "<h1>Hi {{name}}</h1>",
293
+ })
294
+
295
+ # List templates
296
+ templates = client.templates.list()
297
+
298
+ # Get a template
299
+ template = client.templates.get("tem_123")
300
+
301
+ # Update a template
302
+ client.templates.update("tem_123", {"subject": "New Subject"})
303
+
304
+ # Publish a template
305
+ client.templates.publish("tem_123")
306
+
307
+ # Delete a template
308
+ client.templates.delete("tem_123")
309
+ ```
310
+
311
+ ---
312
+
313
+ ### Suppressions
314
+
315
+ ```python
316
+ # Create a suppression
317
+ suppression = client.suppressions.create({
318
+ "email": "spam@example.com",
319
+ "type": "hard_bounce",
320
+ "reason": "Manual suppression",
321
+ })
322
+
323
+ # List suppressions
324
+ suppressions = client.suppressions.list()
325
+
326
+ # Get a suppression
327
+ suppression = client.suppressions.get("sup_123")
328
+
329
+ # Update a suppression
330
+ client.suppressions.update("sup_123", {"reason": "Updated"})
331
+
332
+ # Delete a suppression
333
+ client.suppressions.delete("sup_123")
334
+ ```
335
+
336
+ ---
337
+
338
+ ### Email Verifications
339
+
340
+ ```python
341
+ result = client.email_verifications.verify({
342
+ "email": "test@example.com",
343
+ })
344
+
345
+ print(result.status) # valid
346
+ print(result.score) # 0.95
347
+ print(result.risk) # low
348
+ ```
349
+
350
+ ---
351
+
352
+ ### Email Verification Lists
353
+
354
+ ```python
355
+ # Create a verification list
356
+ vlist = client.email_verification_lists.create({
357
+ "name": "Marketing List Q1",
358
+ "emails": [
359
+ "user1@example.com",
360
+ "user2@example.com",
361
+ "user3@example.com",
362
+ ],
363
+ })
364
+ print(vlist.id) # evl_abc123...
365
+ print(vlist.status) # pending
366
+
367
+ # List all verification lists
368
+ vlists = client.email_verification_lists.list()
369
+
370
+ # Get a verification list
371
+ vlist = client.email_verification_lists.get("evl_abc123")
372
+ print(vlist.stats["successful_verifications"])
373
+
374
+ # Get verification results
375
+ results = client.email_verification_lists.results("evl_abc123", {"page": 1, "limit": 50})
376
+
377
+ for result in results:
378
+ print(f"{result.email} — {result.result}")
379
+
380
+ # Export results as XLSX
381
+ response = client.email_verification_lists.export("evl_abc123")
382
+ with open("results.xlsx", "wb") as f:
383
+ f.write(response.body.encode())
384
+ ```
385
+
386
+ ---
387
+
388
+ ### Webhooks
389
+
390
+ ```python
391
+ # Create a webhook
392
+ webhook = client.webhooks.create({
393
+ "name": "My Webhook",
394
+ "url": "https://example.com/hook",
395
+ "all_events": True,
396
+ "enabled": True,
397
+ })
398
+ print(webhook.id)
399
+
400
+ # List webhooks
401
+ webhooks = client.webhooks.list()
402
+
403
+ # Get a webhook
404
+ webhook = client.webhooks.get("wh_123")
405
+
406
+ # Update a webhook
407
+ client.webhooks.update("wh_123", {"enabled": False})
408
+
409
+ # Delete a webhook
410
+ client.webhooks.delete("wh_123")
411
+ ```
412
+
413
+ ---
414
+
415
+ ### Contacts
416
+
417
+ ```python
418
+ # Create a contact
419
+ contact = client.contacts.create({
420
+ "email": "user@example.com",
421
+ "first_name": "John",
422
+ "last_name": "Doe",
423
+ })
424
+ print(contact.id)
425
+
426
+ # List contacts
427
+ contacts = client.contacts.list()
428
+
429
+ # Get a contact
430
+ contact = client.contacts.get("con_123")
431
+
432
+ # Update a contact
433
+ client.contacts.update("con_123", {"first_name": "Jane"})
434
+
435
+ # Delete a contact
436
+ client.contacts.delete("con_123")
437
+ ```
438
+
439
+ ---
440
+
441
+ ### Events
442
+
443
+ ```python
444
+ # List events
445
+ events = client.events.list({"type": "email.delivered"})
446
+
447
+ for event in events:
448
+ print(event.type)
449
+
450
+ # Get an event
451
+ event = client.events.get("evt_123")
452
+ print(event.type)
453
+ print(event.data["email_id"])
454
+ ```
455
+
456
+ ## Webhook Events
457
+
458
+ The SDK provides typed event classes for all Emailit webhook event types under the `emailit.events` module, plus a `WebhookSignature` class for verifying webhook request signatures.
459
+
460
+ ### Verifying Webhook Signatures
461
+
462
+ ```python
463
+ from emailit import WebhookSignature, ApiErrorException
464
+ from emailit.events import EmailDelivered
465
+
466
+ raw_body = request.body # raw request body string
467
+ signature = request.headers["x-emailit-signature"]
468
+ timestamp = request.headers["x-emailit-timestamp"]
469
+ secret = "your_webhook_signing_secret"
470
+
471
+ try:
472
+ event = WebhookSignature.verify(raw_body, signature, timestamp, secret)
473
+
474
+ # event is automatically typed based on the event type
475
+ print(event.type) # e.g. "email.delivered"
476
+ print(event.event_id) # e.g. "evt_abc123"
477
+
478
+ # Access the event data
479
+ data = event.get_event_data()
480
+
481
+ if isinstance(event, EmailDelivered):
482
+ # Handle delivered email
483
+ pass
484
+
485
+ except ApiErrorException as e:
486
+ return Response(e.args[0], status=401)
487
+ ```
488
+
489
+ You can disable replay protection by passing `tolerance=None`, or set a custom tolerance in seconds:
490
+
491
+ ```python
492
+ # Skip replay check
493
+ event = WebhookSignature.verify(raw_body, signature, timestamp, secret, tolerance=None)
494
+
495
+ # Custom 10-minute tolerance
496
+ event = WebhookSignature.verify(raw_body, signature, timestamp, secret, tolerance=600)
497
+ ```
498
+
499
+ ### Available Event Types
500
+
501
+ **Emails:** `email.accepted`, `email.scheduled`, `email.delivered`, `email.bounced`, `email.attempted`, `email.failed`, `email.rejected`, `email.suppressed`, `email.received`, `email.complained`, `email.clicked`, `email.loaded`
502
+
503
+ **Domains:** `domain.created`, `domain.updated`, `domain.deleted`
504
+
505
+ **Audiences:** `audience.created`, `audience.updated`, `audience.deleted`
506
+
507
+ **Subscribers:** `subscriber.created`, `subscriber.updated`, `subscriber.deleted`
508
+
509
+ **Contacts:** `contact.created`, `contact.updated`, `contact.deleted`
510
+
511
+ **Templates:** `template.created`, `template.updated`, `template.deleted`
512
+
513
+ **Suppressions:** `suppression.created`, `suppression.updated`, `suppression.deleted`
514
+
515
+ **Email Verifications:** `email_verification.created`, `email_verification.updated`, `email_verification.deleted`
516
+
517
+ **Email Verification Lists:** `email_verification_list.created`, `email_verification_list.updated`, `email_verification_list.deleted`
518
+
519
+ Each event type has a corresponding class under `emailit.events` (e.g. `EmailDelivered`, `DomainCreated`). You can use `isinstance` checks or the `EVENT_TYPE` constant for routing:
520
+
521
+ ```python
522
+ from emailit.events import EmailDelivered, EmailBounced, ContactCreated
523
+
524
+ if isinstance(event, EmailDelivered):
525
+ handle_delivered(event)
526
+ elif isinstance(event, EmailBounced):
527
+ handle_bounce(event)
528
+ elif isinstance(event, ContactCreated):
529
+ handle_new_contact(event)
530
+ else:
531
+ print(f"Unhandled: {event.type}")
532
+ ```
533
+
534
+ ## Error Handling
535
+
536
+ The SDK raises typed exceptions for API errors:
537
+
538
+ ```python
539
+ from emailit import (
540
+ ApiErrorException,
541
+ AuthenticationException,
542
+ InvalidRequestException,
543
+ RateLimitException,
544
+ UnprocessableEntityException,
545
+ ApiConnectionException,
546
+ )
547
+
548
+ try:
549
+ client.emails.send({...})
550
+ except AuthenticationException:
551
+ # Invalid API key (401)
552
+ pass
553
+ except InvalidRequestException:
554
+ # Bad request or not found (400, 404)
555
+ pass
556
+ except RateLimitException:
557
+ # Too many requests (429)
558
+ pass
559
+ except UnprocessableEntityException:
560
+ # Validation failed (422)
561
+ pass
562
+ except ApiConnectionException:
563
+ # Network error
564
+ pass
565
+ except ApiErrorException as e:
566
+ # Any other API error
567
+ print(e.http_status)
568
+ print(e.http_body)
569
+ print(e.json_body)
570
+ ```
571
+
572
+ ## License
573
+
574
+ MIT -- see [LICENSE](LICENSE) for details.