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.
- sendio-1.0.0/LICENSE.md +23 -0
- sendio-1.0.0/MANIFEST.in +5 -0
- sendio-1.0.0/NOTICE +2 -0
- sendio-1.0.0/PKG-INFO +281 -0
- sendio-1.0.0/README.md +236 -0
- sendio-1.0.0/requirements.txt +2 -0
- sendio-1.0.0/sendio/__init__.py +302 -0
- sendio-1.0.0/sendio/_base_response.py +16 -0
- sendio-1.0.0/sendio/api_keys/__init__.py +0 -0
- sendio-1.0.0/sendio/api_keys/_api_key.py +22 -0
- sendio-1.0.0/sendio/api_keys/_api_keys.py +301 -0
- sendio-1.0.0/sendio/api_keys/py.typed +0 -0
- sendio-1.0.0/sendio/async_request.py +179 -0
- sendio-1.0.0/sendio/audiences/__init__.py +0 -0
- sendio-1.0.0/sendio/audiences/_audience.py +16 -0
- sendio-1.0.0/sendio/audiences/_audiences.py +285 -0
- sendio-1.0.0/sendio/audiences/py.typed +0 -0
- sendio-1.0.0/sendio/automations/__init__.py +47 -0
- sendio-1.0.0/sendio/automations/_automation.py +365 -0
- sendio-1.0.0/sendio/automations/_automation_template.py +236 -0
- sendio-1.0.0/sendio/automations/_automations.py +961 -0
- sendio-1.0.0/sendio/broadcasts/__init__.py +0 -0
- sendio-1.0.0/sendio/broadcasts/_broadcast.py +121 -0
- sendio-1.0.0/sendio/broadcasts/_broadcast_recipient.py +80 -0
- sendio-1.0.0/sendio/broadcasts/_broadcast_version.py +56 -0
- sendio-1.0.0/sendio/broadcasts/_broadcasts.py +1304 -0
- sendio-1.0.0/sendio/broadcasts/_clicked_link.py +21 -0
- sendio-1.0.0/sendio/broadcasts/py.typed +0 -0
- sendio-1.0.0/sendio/contact_properties/__init__.py +0 -0
- sendio-1.0.0/sendio/contact_properties/_contact_properties.py +362 -0
- sendio-1.0.0/sendio/contact_properties/_contact_property.py +43 -0
- sendio-1.0.0/sendio/contacts/__init__.py +4 -0
- sendio-1.0.0/sendio/contacts/_contact.py +45 -0
- sendio-1.0.0/sendio/contacts/_contact_topic.py +49 -0
- sendio-1.0.0/sendio/contacts/_contacts.py +567 -0
- sendio-1.0.0/sendio/contacts/_topics.py +257 -0
- sendio-1.0.0/sendio/contacts/imports/__init__.py +4 -0
- sendio-1.0.0/sendio/contacts/imports/_contact_import.py +44 -0
- sendio-1.0.0/sendio/contacts/imports/_contact_imports.py +243 -0
- sendio-1.0.0/sendio/contacts/py.typed +0 -0
- sendio-1.0.0/sendio/contacts/segments/__init__.py +0 -0
- sendio-1.0.0/sendio/contacts/segments/_contact_segment.py +16 -0
- sendio-1.0.0/sendio/contacts/segments/_contact_segments.py +332 -0
- sendio-1.0.0/sendio/contacts/segments/py.typed +0 -0
- sendio-1.0.0/sendio/domains/__init__.py +0 -0
- sendio-1.0.0/sendio/domains/_domain.py +77 -0
- sendio-1.0.0/sendio/domains/_domains.py +416 -0
- sendio-1.0.0/sendio/domains/_record.py +41 -0
- sendio-1.0.0/sendio/domains/py.typed +0 -0
- sendio-1.0.0/sendio/emails/__init__.py +0 -0
- sendio-1.0.0/sendio/emails/_attachment.py +52 -0
- sendio-1.0.0/sendio/emails/_attachments.py +163 -0
- sendio-1.0.0/sendio/emails/_batch.py +128 -0
- sendio-1.0.0/sendio/emails/_email.py +78 -0
- sendio-1.0.0/sendio/emails/_emails.py +795 -0
- sendio-1.0.0/sendio/emails/_received_email.py +280 -0
- sendio-1.0.0/sendio/emails/_receiving.py +338 -0
- sendio-1.0.0/sendio/emails/_tag.py +18 -0
- sendio-1.0.0/sendio/emails/py.typed +0 -0
- sendio-1.0.0/sendio/events/__init__.py +11 -0
- sendio-1.0.0/sendio/events/_event.py +88 -0
- sendio-1.0.0/sendio/events/_events.py +451 -0
- sendio-1.0.0/sendio/exceptions.py +322 -0
- sendio-1.0.0/sendio/http_client.py +22 -0
- sendio-1.0.0/sendio/http_client_async.py +22 -0
- sendio-1.0.0/sendio/http_client_httpx.py +47 -0
- sendio-1.0.0/sendio/http_client_requests.py +48 -0
- sendio-1.0.0/sendio/logs/__init__.py +4 -0
- sendio-1.0.0/sendio/logs/_log.py +38 -0
- sendio-1.0.0/sendio/logs/_logs.py +193 -0
- sendio-1.0.0/sendio/mailboxes/__init__.py +14 -0
- sendio-1.0.0/sendio/mailboxes/_mailbox.py +131 -0
- sendio-1.0.0/sendio/mailboxes/_mailboxes.py +332 -0
- sendio-1.0.0/sendio/oauth_grants/__init__.py +4 -0
- sendio-1.0.0/sendio/oauth_grants/_oauth_grant.py +45 -0
- sendio-1.0.0/sendio/oauth_grants/_oauth_grants.py +165 -0
- sendio-1.0.0/sendio/oauth_grants/py.typed +0 -0
- sendio-1.0.0/sendio/pagination_helper.py +34 -0
- sendio-1.0.0/sendio/py.typed +0 -0
- sendio-1.0.0/sendio/request.py +164 -0
- sendio-1.0.0/sendio/response.py +18 -0
- sendio-1.0.0/sendio/segments/__init__.py +0 -0
- sendio-1.0.0/sendio/segments/_segment.py +16 -0
- sendio-1.0.0/sendio/segments/_segments.py +328 -0
- sendio-1.0.0/sendio/segments/py.typed +0 -0
- sendio-1.0.0/sendio/suppressions/__init__.py +14 -0
- sendio-1.0.0/sendio/suppressions/_suppression.py +55 -0
- sendio-1.0.0/sendio/suppressions/_suppressions.py +259 -0
- sendio-1.0.0/sendio/suppressions/batch/__init__.py +4 -0
- sendio-1.0.0/sendio/suppressions/batch/_suppressions_batch.py +187 -0
- sendio-1.0.0/sendio/suppressions/py.typed +0 -0
- sendio-1.0.0/sendio/templates/__init__.py +1 -0
- sendio-1.0.0/sendio/templates/_template.py +133 -0
- sendio-1.0.0/sendio/templates/_templates.py +465 -0
- sendio-1.0.0/sendio/topics/__init__.py +0 -0
- sendio-1.0.0/sendio/topics/_topic.py +24 -0
- sendio-1.0.0/sendio/topics/_topics.py +329 -0
- sendio-1.0.0/sendio/usage/__init__.py +19 -0
- sendio-1.0.0/sendio/usage/_usage.py +331 -0
- sendio-1.0.0/sendio/version.py +8 -0
- sendio-1.0.0/sendio/webhooks/__init__.py +0 -0
- sendio-1.0.0/sendio/webhooks/_webhook.py +128 -0
- sendio-1.0.0/sendio/webhooks/_webhook_event.py +643 -0
- sendio-1.0.0/sendio/webhooks/_webhooks.py +830 -0
- sendio-1.0.0/sendio.egg-info/PKG-INFO +281 -0
- sendio-1.0.0/sendio.egg-info/SOURCES.txt +110 -0
- sendio-1.0.0/sendio.egg-info/dependency_links.txt +1 -0
- sendio-1.0.0/sendio.egg-info/not-zip-safe +1 -0
- sendio-1.0.0/sendio.egg-info/requires.txt +5 -0
- sendio-1.0.0/sendio.egg-info/top_level.txt +1 -0
- sendio-1.0.0/setup.cfg +4 -0
- sendio-1.0.0/setup.py +53 -0
sendio-1.0.0/LICENSE.md
ADDED
|
@@ -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.
|
sendio-1.0.0/MANIFEST.in
ADDED
sendio-1.0.0/NOTICE
ADDED
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
|
+
[](https://pypi.org/project/sendio/)
|
|
49
|
+
[](https://pypi.org/project/sendio)
|
|
50
|
+
[](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
|
+
[](https://pypi.org/project/sendio/)
|
|
4
|
+
[](https://pypi.org/project/sendio)
|
|
5
|
+
[](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).
|