sendio 1.0.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 (112) hide show
  1. sendio-1.0.0/LICENSE.md +23 -0
  2. sendio-1.0.0/MANIFEST.in +5 -0
  3. sendio-1.0.0/NOTICE +2 -0
  4. sendio-1.0.0/PKG-INFO +281 -0
  5. sendio-1.0.0/README.md +236 -0
  6. sendio-1.0.0/requirements.txt +2 -0
  7. sendio-1.0.0/sendio/__init__.py +302 -0
  8. sendio-1.0.0/sendio/_base_response.py +16 -0
  9. sendio-1.0.0/sendio/api_keys/__init__.py +0 -0
  10. sendio-1.0.0/sendio/api_keys/_api_key.py +22 -0
  11. sendio-1.0.0/sendio/api_keys/_api_keys.py +301 -0
  12. sendio-1.0.0/sendio/api_keys/py.typed +0 -0
  13. sendio-1.0.0/sendio/async_request.py +179 -0
  14. sendio-1.0.0/sendio/audiences/__init__.py +0 -0
  15. sendio-1.0.0/sendio/audiences/_audience.py +16 -0
  16. sendio-1.0.0/sendio/audiences/_audiences.py +285 -0
  17. sendio-1.0.0/sendio/audiences/py.typed +0 -0
  18. sendio-1.0.0/sendio/automations/__init__.py +47 -0
  19. sendio-1.0.0/sendio/automations/_automation.py +365 -0
  20. sendio-1.0.0/sendio/automations/_automation_template.py +236 -0
  21. sendio-1.0.0/sendio/automations/_automations.py +961 -0
  22. sendio-1.0.0/sendio/broadcasts/__init__.py +0 -0
  23. sendio-1.0.0/sendio/broadcasts/_broadcast.py +121 -0
  24. sendio-1.0.0/sendio/broadcasts/_broadcast_recipient.py +80 -0
  25. sendio-1.0.0/sendio/broadcasts/_broadcast_version.py +56 -0
  26. sendio-1.0.0/sendio/broadcasts/_broadcasts.py +1304 -0
  27. sendio-1.0.0/sendio/broadcasts/_clicked_link.py +21 -0
  28. sendio-1.0.0/sendio/broadcasts/py.typed +0 -0
  29. sendio-1.0.0/sendio/contact_properties/__init__.py +0 -0
  30. sendio-1.0.0/sendio/contact_properties/_contact_properties.py +362 -0
  31. sendio-1.0.0/sendio/contact_properties/_contact_property.py +43 -0
  32. sendio-1.0.0/sendio/contacts/__init__.py +4 -0
  33. sendio-1.0.0/sendio/contacts/_contact.py +45 -0
  34. sendio-1.0.0/sendio/contacts/_contact_topic.py +49 -0
  35. sendio-1.0.0/sendio/contacts/_contacts.py +567 -0
  36. sendio-1.0.0/sendio/contacts/_topics.py +257 -0
  37. sendio-1.0.0/sendio/contacts/imports/__init__.py +4 -0
  38. sendio-1.0.0/sendio/contacts/imports/_contact_import.py +44 -0
  39. sendio-1.0.0/sendio/contacts/imports/_contact_imports.py +243 -0
  40. sendio-1.0.0/sendio/contacts/py.typed +0 -0
  41. sendio-1.0.0/sendio/contacts/segments/__init__.py +0 -0
  42. sendio-1.0.0/sendio/contacts/segments/_contact_segment.py +16 -0
  43. sendio-1.0.0/sendio/contacts/segments/_contact_segments.py +332 -0
  44. sendio-1.0.0/sendio/contacts/segments/py.typed +0 -0
  45. sendio-1.0.0/sendio/domains/__init__.py +0 -0
  46. sendio-1.0.0/sendio/domains/_domain.py +77 -0
  47. sendio-1.0.0/sendio/domains/_domains.py +416 -0
  48. sendio-1.0.0/sendio/domains/_record.py +41 -0
  49. sendio-1.0.0/sendio/domains/py.typed +0 -0
  50. sendio-1.0.0/sendio/emails/__init__.py +0 -0
  51. sendio-1.0.0/sendio/emails/_attachment.py +52 -0
  52. sendio-1.0.0/sendio/emails/_attachments.py +163 -0
  53. sendio-1.0.0/sendio/emails/_batch.py +128 -0
  54. sendio-1.0.0/sendio/emails/_email.py +78 -0
  55. sendio-1.0.0/sendio/emails/_emails.py +795 -0
  56. sendio-1.0.0/sendio/emails/_received_email.py +280 -0
  57. sendio-1.0.0/sendio/emails/_receiving.py +338 -0
  58. sendio-1.0.0/sendio/emails/_tag.py +18 -0
  59. sendio-1.0.0/sendio/emails/py.typed +0 -0
  60. sendio-1.0.0/sendio/events/__init__.py +11 -0
  61. sendio-1.0.0/sendio/events/_event.py +88 -0
  62. sendio-1.0.0/sendio/events/_events.py +451 -0
  63. sendio-1.0.0/sendio/exceptions.py +322 -0
  64. sendio-1.0.0/sendio/http_client.py +22 -0
  65. sendio-1.0.0/sendio/http_client_async.py +22 -0
  66. sendio-1.0.0/sendio/http_client_httpx.py +47 -0
  67. sendio-1.0.0/sendio/http_client_requests.py +48 -0
  68. sendio-1.0.0/sendio/logs/__init__.py +4 -0
  69. sendio-1.0.0/sendio/logs/_log.py +38 -0
  70. sendio-1.0.0/sendio/logs/_logs.py +193 -0
  71. sendio-1.0.0/sendio/mailboxes/__init__.py +14 -0
  72. sendio-1.0.0/sendio/mailboxes/_mailbox.py +131 -0
  73. sendio-1.0.0/sendio/mailboxes/_mailboxes.py +332 -0
  74. sendio-1.0.0/sendio/oauth_grants/__init__.py +4 -0
  75. sendio-1.0.0/sendio/oauth_grants/_oauth_grant.py +45 -0
  76. sendio-1.0.0/sendio/oauth_grants/_oauth_grants.py +165 -0
  77. sendio-1.0.0/sendio/oauth_grants/py.typed +0 -0
  78. sendio-1.0.0/sendio/pagination_helper.py +34 -0
  79. sendio-1.0.0/sendio/py.typed +0 -0
  80. sendio-1.0.0/sendio/request.py +164 -0
  81. sendio-1.0.0/sendio/response.py +18 -0
  82. sendio-1.0.0/sendio/segments/__init__.py +0 -0
  83. sendio-1.0.0/sendio/segments/_segment.py +16 -0
  84. sendio-1.0.0/sendio/segments/_segments.py +328 -0
  85. sendio-1.0.0/sendio/segments/py.typed +0 -0
  86. sendio-1.0.0/sendio/suppressions/__init__.py +14 -0
  87. sendio-1.0.0/sendio/suppressions/_suppression.py +55 -0
  88. sendio-1.0.0/sendio/suppressions/_suppressions.py +259 -0
  89. sendio-1.0.0/sendio/suppressions/batch/__init__.py +4 -0
  90. sendio-1.0.0/sendio/suppressions/batch/_suppressions_batch.py +187 -0
  91. sendio-1.0.0/sendio/suppressions/py.typed +0 -0
  92. sendio-1.0.0/sendio/templates/__init__.py +1 -0
  93. sendio-1.0.0/sendio/templates/_template.py +133 -0
  94. sendio-1.0.0/sendio/templates/_templates.py +465 -0
  95. sendio-1.0.0/sendio/topics/__init__.py +0 -0
  96. sendio-1.0.0/sendio/topics/_topic.py +24 -0
  97. sendio-1.0.0/sendio/topics/_topics.py +329 -0
  98. sendio-1.0.0/sendio/usage/__init__.py +19 -0
  99. sendio-1.0.0/sendio/usage/_usage.py +331 -0
  100. sendio-1.0.0/sendio/version.py +8 -0
  101. sendio-1.0.0/sendio/webhooks/__init__.py +0 -0
  102. sendio-1.0.0/sendio/webhooks/_webhook.py +128 -0
  103. sendio-1.0.0/sendio/webhooks/_webhook_event.py +643 -0
  104. sendio-1.0.0/sendio/webhooks/_webhooks.py +830 -0
  105. sendio-1.0.0/sendio.egg-info/PKG-INFO +281 -0
  106. sendio-1.0.0/sendio.egg-info/SOURCES.txt +110 -0
  107. sendio-1.0.0/sendio.egg-info/dependency_links.txt +1 -0
  108. sendio-1.0.0/sendio.egg-info/not-zip-safe +1 -0
  109. sendio-1.0.0/sendio.egg-info/requires.txt +5 -0
  110. sendio-1.0.0/sendio.egg-info/top_level.txt +1 -0
  111. sendio-1.0.0/setup.cfg +4 -0
  112. sendio-1.0.0/setup.py +53 -0
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2022 Derich Pacheco
2
+ Copyright (c) 2026 send.io
3
+
4
+ MIT License
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining
7
+ a copy of this software and associated documentation files (the
8
+ "Software"), to deal in the Software without restriction, including
9
+ without limitation the rights to use, copy, modify, merge, publish,
10
+ distribute, sublicense, and/or sell copies of the Software, and to
11
+ permit persons to whom the Software is furnished to do so, subject to
12
+ the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be
15
+ included in all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,5 @@
1
+ include README.md
2
+ include LICENSE.md
3
+ include NOTICE
4
+ include requirements.txt
5
+ include sendio/py.typed
sendio-1.0.0/NOTICE ADDED
@@ -0,0 +1,2 @@
1
+ This SDK is based on https://github.com/resend/resend-python (MIT License), Copyright (c) Resend.
2
+ Original copyright notice: Copyright (c) 2022 Derich Pacheco. See LICENSE.md.
sendio-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,281 @@
1
+ Metadata-Version: 2.4
2
+ Name: sendio
3
+ Version: 1.0.0
4
+ Summary: The official send.io Python SDK
5
+ Home-page: https://send.io
6
+ Author: send.io
7
+ Author-email: support@send.io
8
+ License: MIT
9
+ Project-URL: Documentation, https://developer.send.io/docs/api
10
+ Project-URL: Homepage, https://send.io
11
+ Keywords: email,email api,transactional email,sendio,send.io
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Natural Language :: English
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3.7
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Requires-Python: >=3.7
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE.md
26
+ License-File: NOTICE
27
+ Requires-Dist: requests>=2.31.0
28
+ Requires-Dist: typing_extensions>=4.4.0
29
+ Provides-Extra: async
30
+ Requires-Dist: httpx>=0.24.0; extra == "async"
31
+ Dynamic: author
32
+ Dynamic: author-email
33
+ Dynamic: classifier
34
+ Dynamic: description
35
+ Dynamic: description-content-type
36
+ Dynamic: home-page
37
+ Dynamic: keywords
38
+ Dynamic: license
39
+ Dynamic: license-file
40
+ Dynamic: project-url
41
+ Dynamic: provides-extra
42
+ Dynamic: requires-dist
43
+ Dynamic: requires-python
44
+ Dynamic: summary
45
+
46
+ # send.io Python SDK
47
+
48
+ [![PyPI](https://img.shields.io/pypi/v/sendio)](https://pypi.org/project/sendio/)
49
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/sendio)](https://pypi.org/project/sendio)
50
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
51
+
52
+ The official Python SDK for the [send.io](https://send.io) email API.
53
+
54
+ Full API reference: https://developer.send.io/docs/api
55
+
56
+ ## Installation
57
+
58
+ > **Coming soon:** this send.io package is not published yet, so the install lines below are commented out. Do not install a package by this name until the send.io docs list it as available.
59
+
60
+ ```bash
61
+ # pip install sendio
62
+ ```
63
+
64
+ For async support (via `httpx`):
65
+
66
+ ```bash
67
+ # pip install "sendio[async]"
68
+ ```
69
+
70
+ Python 3.7 or newer is required.
71
+
72
+ ## Quick start
73
+
74
+ Create an API key in the send.io dashboard (keys start with `mx_`), then send an email:
75
+
76
+ ```py
77
+ import sendio
78
+
79
+ sendio.api_key = "mx_your_api_key"
80
+
81
+ params: sendio.Emails.SendParams = {
82
+ "from": "Acme <onboarding@yourdomain.com>",
83
+ "to": ["delivered@send.io"],
84
+ "subject": "Hello from send.io",
85
+ "html": "<strong>It works!</strong>",
86
+ }
87
+
88
+ email: sendio.Emails.SendResponse = sendio.Emails.send(params)
89
+ print(email["id"])
90
+ ```
91
+
92
+ You can also pass `cc`, `bcc`, `reply_to`, `text`, `headers`, `attachments`, `tags`,
93
+ `scheduled_at` and a `template`. Pass an idempotency key to make retries safe:
94
+
95
+ ```py
96
+ sendio.Emails.send(params, {"idempotency_key": "welcome-user-123"})
97
+ ```
98
+
99
+ ## Batch sending
100
+
101
+ Send up to 100 emails in one request:
102
+
103
+ ```py
104
+ batch: sendio.Batch.SendResponse = sendio.Batch.send([
105
+ {
106
+ "from": "Acme <onboarding@yourdomain.com>",
107
+ "to": ["delivered@send.io"],
108
+ "subject": "First",
109
+ "html": "<p>First email</p>",
110
+ },
111
+ {
112
+ "from": "Acme <onboarding@yourdomain.com>",
113
+ "to": ["delivered@send.io"],
114
+ "subject": "Second",
115
+ "html": "<p>Second email</p>",
116
+ },
117
+ ])
118
+ print([item["id"] for item in batch["data"]])
119
+ ```
120
+
121
+ ## Retrieve an email
122
+
123
+ ```py
124
+ email: sendio.Email = sendio.Emails.get("4ef9a417-02e9-4d39-ad75-9611e0fcc33c")
125
+ print(email["last_event"])
126
+ ```
127
+
128
+ List recent emails with `sendio.Emails.list()`, update a scheduled send with
129
+ `sendio.Emails.update(...)` and cancel it with `sendio.Emails.cancel(email_id)`.
130
+
131
+ ## Domains
132
+
133
+ ```py
134
+ domain = sendio.Domains.create({"name": "yourdomain.com"})
135
+ print(domain["records"]) # DNS records to add at your DNS provider
136
+
137
+ sendio.Domains.verify(domain["id"])
138
+ print(sendio.Domains.get(domain["id"])["status"])
139
+
140
+ for d in sendio.Domains.list()["data"]:
141
+ print(d["name"], d["status"])
142
+ ```
143
+
144
+ ### Receiving email on a domain
145
+
146
+ Pass `capabilities` to also accept inbound email. The create response then
147
+ includes a `Receiving` MX record to publish.
148
+
149
+ ```py
150
+ domain = sendio.Domains.create(
151
+ {"name": "yourdomain.com", "capabilities": {"receiving": "enabled"}}
152
+ )
153
+
154
+ # Stop accepting email for the domain; you can then remove its MX record.
155
+ sendio.Domains.update({"id": domain["id"], "capabilities": {"receiving": "disabled"}})
156
+
157
+ # Turn it back on. This asks for the domain's MX record; publish it to start receiving email.
158
+ sendio.Domains.update({"id": domain["id"], "capabilities": {"receiving": "enabled"}})
159
+ ```
160
+
161
+ Sending can't be turned off: `{"sending": "disabled"}` is rejected with a 422.
162
+
163
+ ## Mailboxes
164
+
165
+ Mailboxes are real email accounts with IMAP/SMTP login. Create one on a verified
166
+ domain (an address that already exists raises a 409); `credentials` comes back
167
+ once, when IMAP/SMTP is ready. The password is also an API key for that mailbox,
168
+ so store it like one.
169
+
170
+ ```py
171
+ mailbox = sendio.Mailboxes.create({"address": "ada@yourdomain.com"})
172
+ print(mailbox["credentials"]["smtp"]) # {"host": ..., "port": 587, "security": "starttls"}
173
+
174
+ for m in sendio.Mailboxes.list()["data"]: # every mailbox, not paginated
175
+ print(m["address"], m["imap_smtp"], m["health"])
176
+
177
+ sendio.Mailboxes.update({"id": mailbox["id"], "warmup_enabled": False})
178
+ creds = sendio.Mailboxes.reset_password(mailbox["id"]) # the old password stops working
179
+ sendio.Mailboxes.remove(mailbox["id"]) # also removes an agent inbox on it
180
+ ```
181
+
182
+ Pass `local_part` + `domain_id` instead of `address`, or `username` (with
183
+ optional `org` and `display_name`) for a free mailbox without a domain.
184
+
185
+ The SDK also covers API keys, contacts, segments, topics, broadcasts, templates,
186
+ webhooks (including signature verification), suppressions, logs, mailboxes and more. See
187
+ [`examples/`](examples) for a script per resource.
188
+
189
+ ## Async
190
+
191
+ Every method has an `_async` twin once the `async` extra is installed:
192
+
193
+ ```py
194
+ import asyncio
195
+ import sendio
196
+
197
+ sendio.api_key = "mx_your_api_key"
198
+
199
+ async def main() -> None:
200
+ email = await sendio.Emails.send_async({
201
+ "from": "Acme <onboarding@yourdomain.com>",
202
+ "to": ["delivered@send.io"],
203
+ "subject": "Hello",
204
+ "html": "<p>Sent asynchronously</p>",
205
+ })
206
+ print(email["id"])
207
+
208
+ asyncio.run(main())
209
+ ```
210
+
211
+ To customise the async client (for example its timeout):
212
+
213
+ ```py
214
+ sendio.default_async_http_client = sendio.HTTPXClient(timeout=60)
215
+ ```
216
+
217
+ A custom synchronous client can be plugged in the same way through
218
+ `sendio.default_http_client` (see `examples/with_custom_http_client.py`).
219
+
220
+ ## Configuration
221
+
222
+ | Setting | Environment variable | Module attribute | Default |
223
+ |----------|----------------------|-------------------|-----------------------|
224
+ | API key | `SENDIO_API_KEY` | `sendio.api_key` | none |
225
+ | Base URL | `SENDIO_BASE_URL` | `sendio.api_url` | `https://api.send.io` |
226
+
227
+ Environment variables are read when `sendio` is imported; module attributes can be
228
+ changed at any time. Point the SDK at a different endpoint (a staging environment or a
229
+ local mock server for tests) with:
230
+
231
+ ```bash
232
+ export SENDIO_BASE_URL=http://localhost:8080
233
+ ```
234
+
235
+ or in code:
236
+
237
+ ```py
238
+ sendio.api_url = "http://localhost:8080"
239
+ ```
240
+
241
+ Every request carries a `User-Agent: sendio-python:<version>` header.
242
+
243
+ ## Error handling
244
+
245
+ API errors raise a subclass of `sendio.exceptions.SendioError`:
246
+
247
+ ```py
248
+ import sendio
249
+ from sendio.exceptions import SendioError, RateLimitError
250
+
251
+ try:
252
+ sendio.Emails.send({
253
+ "from": "Acme <onboarding@yourdomain.com>",
254
+ "to": ["delivered@send.io"],
255
+ "subject": "Hello",
256
+ "html": "<p>Hi</p>",
257
+ })
258
+ except RateLimitError as e:
259
+ print("slow down, retry after", e.headers.get("retry-after"))
260
+ except SendioError as e:
261
+ print(e.code, e.error_type, e.message, e.suggested_action)
262
+ ```
263
+
264
+ Specific types include `MissingApiKeyError`, `InvalidApiKeyError`, `ValidationError`,
265
+ `MissingRequiredFieldsError`, `RateLimitError` and `ApplicationError`. Response headers
266
+ (request id, rate limit info) are available on `e.headers` and, for successful calls,
267
+ on `response["http_headers"]`.
268
+
269
+ ## Development
270
+
271
+ ```bash
272
+ pip install -r requirements.txt pytest pytest-asyncio
273
+ pytest tests
274
+ ```
275
+
276
+ `examples/smoke.py` is a live check against a real send.io environment (it sends one
277
+ email); it is not part of the unit tests.
278
+
279
+ ## License
280
+
281
+ MIT. See [LICENSE.md](LICENSE.md) and [NOTICE](NOTICE).
sendio-1.0.0/README.md ADDED
@@ -0,0 +1,236 @@
1
+ # send.io Python SDK
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/sendio)](https://pypi.org/project/sendio/)
4
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/sendio)](https://pypi.org/project/sendio)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ The official Python SDK for the [send.io](https://send.io) email API.
8
+
9
+ Full API reference: https://developer.send.io/docs/api
10
+
11
+ ## Installation
12
+
13
+ > **Coming soon:** this send.io package is not published yet, so the install lines below are commented out. Do not install a package by this name until the send.io docs list it as available.
14
+
15
+ ```bash
16
+ # pip install sendio
17
+ ```
18
+
19
+ For async support (via `httpx`):
20
+
21
+ ```bash
22
+ # pip install "sendio[async]"
23
+ ```
24
+
25
+ Python 3.7 or newer is required.
26
+
27
+ ## Quick start
28
+
29
+ Create an API key in the send.io dashboard (keys start with `mx_`), then send an email:
30
+
31
+ ```py
32
+ import sendio
33
+
34
+ sendio.api_key = "mx_your_api_key"
35
+
36
+ params: sendio.Emails.SendParams = {
37
+ "from": "Acme <onboarding@yourdomain.com>",
38
+ "to": ["delivered@send.io"],
39
+ "subject": "Hello from send.io",
40
+ "html": "<strong>It works!</strong>",
41
+ }
42
+
43
+ email: sendio.Emails.SendResponse = sendio.Emails.send(params)
44
+ print(email["id"])
45
+ ```
46
+
47
+ You can also pass `cc`, `bcc`, `reply_to`, `text`, `headers`, `attachments`, `tags`,
48
+ `scheduled_at` and a `template`. Pass an idempotency key to make retries safe:
49
+
50
+ ```py
51
+ sendio.Emails.send(params, {"idempotency_key": "welcome-user-123"})
52
+ ```
53
+
54
+ ## Batch sending
55
+
56
+ Send up to 100 emails in one request:
57
+
58
+ ```py
59
+ batch: sendio.Batch.SendResponse = sendio.Batch.send([
60
+ {
61
+ "from": "Acme <onboarding@yourdomain.com>",
62
+ "to": ["delivered@send.io"],
63
+ "subject": "First",
64
+ "html": "<p>First email</p>",
65
+ },
66
+ {
67
+ "from": "Acme <onboarding@yourdomain.com>",
68
+ "to": ["delivered@send.io"],
69
+ "subject": "Second",
70
+ "html": "<p>Second email</p>",
71
+ },
72
+ ])
73
+ print([item["id"] for item in batch["data"]])
74
+ ```
75
+
76
+ ## Retrieve an email
77
+
78
+ ```py
79
+ email: sendio.Email = sendio.Emails.get("4ef9a417-02e9-4d39-ad75-9611e0fcc33c")
80
+ print(email["last_event"])
81
+ ```
82
+
83
+ List recent emails with `sendio.Emails.list()`, update a scheduled send with
84
+ `sendio.Emails.update(...)` and cancel it with `sendio.Emails.cancel(email_id)`.
85
+
86
+ ## Domains
87
+
88
+ ```py
89
+ domain = sendio.Domains.create({"name": "yourdomain.com"})
90
+ print(domain["records"]) # DNS records to add at your DNS provider
91
+
92
+ sendio.Domains.verify(domain["id"])
93
+ print(sendio.Domains.get(domain["id"])["status"])
94
+
95
+ for d in sendio.Domains.list()["data"]:
96
+ print(d["name"], d["status"])
97
+ ```
98
+
99
+ ### Receiving email on a domain
100
+
101
+ Pass `capabilities` to also accept inbound email. The create response then
102
+ includes a `Receiving` MX record to publish.
103
+
104
+ ```py
105
+ domain = sendio.Domains.create(
106
+ {"name": "yourdomain.com", "capabilities": {"receiving": "enabled"}}
107
+ )
108
+
109
+ # Stop accepting email for the domain; you can then remove its MX record.
110
+ sendio.Domains.update({"id": domain["id"], "capabilities": {"receiving": "disabled"}})
111
+
112
+ # Turn it back on. This asks for the domain's MX record; publish it to start receiving email.
113
+ sendio.Domains.update({"id": domain["id"], "capabilities": {"receiving": "enabled"}})
114
+ ```
115
+
116
+ Sending can't be turned off: `{"sending": "disabled"}` is rejected with a 422.
117
+
118
+ ## Mailboxes
119
+
120
+ Mailboxes are real email accounts with IMAP/SMTP login. Create one on a verified
121
+ domain (an address that already exists raises a 409); `credentials` comes back
122
+ once, when IMAP/SMTP is ready. The password is also an API key for that mailbox,
123
+ so store it like one.
124
+
125
+ ```py
126
+ mailbox = sendio.Mailboxes.create({"address": "ada@yourdomain.com"})
127
+ print(mailbox["credentials"]["smtp"]) # {"host": ..., "port": 587, "security": "starttls"}
128
+
129
+ for m in sendio.Mailboxes.list()["data"]: # every mailbox, not paginated
130
+ print(m["address"], m["imap_smtp"], m["health"])
131
+
132
+ sendio.Mailboxes.update({"id": mailbox["id"], "warmup_enabled": False})
133
+ creds = sendio.Mailboxes.reset_password(mailbox["id"]) # the old password stops working
134
+ sendio.Mailboxes.remove(mailbox["id"]) # also removes an agent inbox on it
135
+ ```
136
+
137
+ Pass `local_part` + `domain_id` instead of `address`, or `username` (with
138
+ optional `org` and `display_name`) for a free mailbox without a domain.
139
+
140
+ The SDK also covers API keys, contacts, segments, topics, broadcasts, templates,
141
+ webhooks (including signature verification), suppressions, logs, mailboxes and more. See
142
+ [`examples/`](examples) for a script per resource.
143
+
144
+ ## Async
145
+
146
+ Every method has an `_async` twin once the `async` extra is installed:
147
+
148
+ ```py
149
+ import asyncio
150
+ import sendio
151
+
152
+ sendio.api_key = "mx_your_api_key"
153
+
154
+ async def main() -> None:
155
+ email = await sendio.Emails.send_async({
156
+ "from": "Acme <onboarding@yourdomain.com>",
157
+ "to": ["delivered@send.io"],
158
+ "subject": "Hello",
159
+ "html": "<p>Sent asynchronously</p>",
160
+ })
161
+ print(email["id"])
162
+
163
+ asyncio.run(main())
164
+ ```
165
+
166
+ To customise the async client (for example its timeout):
167
+
168
+ ```py
169
+ sendio.default_async_http_client = sendio.HTTPXClient(timeout=60)
170
+ ```
171
+
172
+ A custom synchronous client can be plugged in the same way through
173
+ `sendio.default_http_client` (see `examples/with_custom_http_client.py`).
174
+
175
+ ## Configuration
176
+
177
+ | Setting | Environment variable | Module attribute | Default |
178
+ |----------|----------------------|-------------------|-----------------------|
179
+ | API key | `SENDIO_API_KEY` | `sendio.api_key` | none |
180
+ | Base URL | `SENDIO_BASE_URL` | `sendio.api_url` | `https://api.send.io` |
181
+
182
+ Environment variables are read when `sendio` is imported; module attributes can be
183
+ changed at any time. Point the SDK at a different endpoint (a staging environment or a
184
+ local mock server for tests) with:
185
+
186
+ ```bash
187
+ export SENDIO_BASE_URL=http://localhost:8080
188
+ ```
189
+
190
+ or in code:
191
+
192
+ ```py
193
+ sendio.api_url = "http://localhost:8080"
194
+ ```
195
+
196
+ Every request carries a `User-Agent: sendio-python:<version>` header.
197
+
198
+ ## Error handling
199
+
200
+ API errors raise a subclass of `sendio.exceptions.SendioError`:
201
+
202
+ ```py
203
+ import sendio
204
+ from sendio.exceptions import SendioError, RateLimitError
205
+
206
+ try:
207
+ sendio.Emails.send({
208
+ "from": "Acme <onboarding@yourdomain.com>",
209
+ "to": ["delivered@send.io"],
210
+ "subject": "Hello",
211
+ "html": "<p>Hi</p>",
212
+ })
213
+ except RateLimitError as e:
214
+ print("slow down, retry after", e.headers.get("retry-after"))
215
+ except SendioError as e:
216
+ print(e.code, e.error_type, e.message, e.suggested_action)
217
+ ```
218
+
219
+ Specific types include `MissingApiKeyError`, `InvalidApiKeyError`, `ValidationError`,
220
+ `MissingRequiredFieldsError`, `RateLimitError` and `ApplicationError`. Response headers
221
+ (request id, rate limit info) are available on `e.headers` and, for successful calls,
222
+ on `response["http_headers"]`.
223
+
224
+ ## Development
225
+
226
+ ```bash
227
+ pip install -r requirements.txt pytest pytest-asyncio
228
+ pytest tests
229
+ ```
230
+
231
+ `examples/smoke.py` is a live check against a real send.io environment (it sends one
232
+ email); it is not part of the unit tests.
233
+
234
+ ## License
235
+
236
+ MIT. See [LICENSE.md](LICENSE.md) and [NOTICE](NOTICE).
@@ -0,0 +1,2 @@
1
+ requests>=2.31.0
2
+ typing_extensions>=4.4.0