nextmail 0.0.2__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.
Potentially problematic release.
This version of nextmail might be problematic. Click here for more details.
- nextmail-0.0.2/.gitignore +7 -0
- nextmail-0.0.2/CHANGELOG.md +58 -0
- nextmail-0.0.2/PKG-INFO +297 -0
- nextmail-0.0.2/README.md +265 -0
- nextmail-0.0.2/pyproject.toml +144 -0
- nextmail-0.0.2/src/nextmail/__init__.py +510 -0
- nextmail-0.0.2/src/nextmail/_async/__init__.py +0 -0
- nextmail-0.0.2/src/nextmail/_async/client.py +467 -0
- nextmail-0.0.2/src/nextmail/_async/pagination.py +104 -0
- nextmail-0.0.2/src/nextmail/_async/resources/__init__.py +0 -0
- nextmail-0.0.2/src/nextmail/_async/resources/account.py +454 -0
- nextmail-0.0.2/src/nextmail/_async/resources/addresses.py +89 -0
- nextmail-0.0.2/src/nextmail/_async/resources/analytics.py +102 -0
- nextmail-0.0.2/src/nextmail/_async/resources/app_host.py +72 -0
- nextmail-0.0.2/src/nextmail/_async/resources/audiences.py +374 -0
- nextmail-0.0.2/src/nextmail/_async/resources/automations.py +567 -0
- nextmail-0.0.2/src/nextmail/_async/resources/billing.py +283 -0
- nextmail-0.0.2/src/nextmail/_async/resources/bookings.py +426 -0
- nextmail-0.0.2/src/nextmail/_async/resources/branding.py +90 -0
- nextmail-0.0.2/src/nextmail/_async/resources/broadcasts.py +418 -0
- nextmail-0.0.2/src/nextmail/_async/resources/calendar.py +541 -0
- nextmail-0.0.2/src/nextmail/_async/resources/chats.py +136 -0
- nextmail-0.0.2/src/nextmail/_async/resources/connected_mailboxes.py +127 -0
- nextmail-0.0.2/src/nextmail/_async/resources/connectors.py +370 -0
- nextmail-0.0.2/src/nextmail/_async/resources/contacts.py +856 -0
- nextmail-0.0.2/src/nextmail/_async/resources/dns_connections.py +60 -0
- nextmail-0.0.2/src/nextmail/_async/resources/domains.py +755 -0
- nextmail-0.0.2/src/nextmail/_async/resources/drafts.py +154 -0
- nextmail-0.0.2/src/nextmail/_async/resources/emails.py +442 -0
- nextmail-0.0.2/src/nextmail/_async/resources/encryption.py +68 -0
- nextmail-0.0.2/src/nextmail/_async/resources/events.py +87 -0
- nextmail-0.0.2/src/nextmail/_async/resources/exports.py +84 -0
- nextmail-0.0.2/src/nextmail/_async/resources/files.py +326 -0
- nextmail-0.0.2/src/nextmail/_async/resources/follow_ups.py +85 -0
- nextmail-0.0.2/src/nextmail/_async/resources/forms.py +473 -0
- nextmail-0.0.2/src/nextmail/_async/resources/imports.py +349 -0
- nextmail-0.0.2/src/nextmail/_async/resources/keys.py +512 -0
- nextmail-0.0.2/src/nextmail/_async/resources/knowledge.py +550 -0
- nextmail-0.0.2/src/nextmail/_async/resources/labels.py +172 -0
- nextmail-0.0.2/src/nextmail/_async/resources/languages.py +32 -0
- nextmail-0.0.2/src/nextmail/_async/resources/me.py +224 -0
- nextmail-0.0.2/src/nextmail/_async/resources/members.py +290 -0
- nextmail-0.0.2/src/nextmail/_async/resources/out_of_office.py +69 -0
- nextmail-0.0.2/src/nextmail/_async/resources/provider_imports.py +142 -0
- nextmail-0.0.2/src/nextmail/_async/resources/roles.py +156 -0
- nextmail-0.0.2/src/nextmail/_async/resources/rules.py +259 -0
- nextmail-0.0.2/src/nextmail/_async/resources/security.py +124 -0
- nextmail-0.0.2/src/nextmail/_async/resources/segments.py +115 -0
- nextmail-0.0.2/src/nextmail/_async/resources/senders.py +45 -0
- nextmail-0.0.2/src/nextmail/_async/resources/sending.py +74 -0
- nextmail-0.0.2/src/nextmail/_async/resources/settings.py +48 -0
- nextmail-0.0.2/src/nextmail/_async/resources/signatures.py +174 -0
- nextmail-0.0.2/src/nextmail/_async/resources/subscriptions.py +195 -0
- nextmail-0.0.2/src/nextmail/_async/resources/support.py +31 -0
- nextmail-0.0.2/src/nextmail/_async/resources/suppressions.py +203 -0
- nextmail-0.0.2/src/nextmail/_async/resources/tasks.py +439 -0
- nextmail-0.0.2/src/nextmail/_async/resources/temp_mail.py +230 -0
- nextmail-0.0.2/src/nextmail/_async/resources/templates.py +625 -0
- nextmail-0.0.2/src/nextmail/_async/resources/threads.py +711 -0
- nextmail-0.0.2/src/nextmail/_async/resources/tools.py +138 -0
- nextmail-0.0.2/src/nextmail/_async/resources/tracking.py +289 -0
- nextmail-0.0.2/src/nextmail/_async/resources/webhooks.py +559 -0
- nextmail-0.0.2/src/nextmail/_async/resources/workspaces.py +94 -0
- nextmail-0.0.2/src/nextmail/_async/transport.py +252 -0
- nextmail-0.0.2/src/nextmail/_build.py +1 -0
- nextmail-0.0.2/src/nextmail/_core/__init__.py +0 -0
- nextmail-0.0.2/src/nextmail/_core/credentials.py +65 -0
- nextmail-0.0.2/src/nextmail/_core/crop_query.py +59 -0
- nextmail-0.0.2/src/nextmail/_core/environment.py +34 -0
- nextmail-0.0.2/src/nextmail/_core/errors.py +138 -0
- nextmail-0.0.2/src/nextmail/_core/hosts.py +234 -0
- nextmail-0.0.2/src/nextmail/_core/log_query.py +37 -0
- nextmail-0.0.2/src/nextmail/_core/namespace.py +32 -0
- nextmail-0.0.2/src/nextmail/_core/request_path.py +39 -0
- nextmail-0.0.2/src/nextmail/_core/response.py +201 -0
- nextmail-0.0.2/src/nextmail/_core/retry.py +50 -0
- nextmail-0.0.2/src/nextmail/_core/sentinel.py +16 -0
- nextmail-0.0.2/src/nextmail/_core/update_notice.py +89 -0
- nextmail-0.0.2/src/nextmail/_core/uploads.py +22 -0
- nextmail-0.0.2/src/nextmail/_core/wire.py +136 -0
- nextmail-0.0.2/src/nextmail/_default.py +76 -0
- nextmail-0.0.2/src/nextmail/_helpers/__init__.py +0 -0
- nextmail-0.0.2/src/nextmail/_helpers/languages.py +59 -0
- nextmail-0.0.2/src/nextmail/_helpers/messages.py +13 -0
- nextmail-0.0.2/src/nextmail/_helpers/webhooks.py +115 -0
- nextmail-0.0.2/src/nextmail/_sync/__init__.py +0 -0
- nextmail-0.0.2/src/nextmail/_sync/client.py +467 -0
- nextmail-0.0.2/src/nextmail/_sync/pagination.py +104 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/__init__.py +0 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/account.py +454 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/addresses.py +87 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/analytics.py +102 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/app_host.py +72 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/audiences.py +374 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/automations.py +567 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/billing.py +283 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/bookings.py +426 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/branding.py +90 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/broadcasts.py +418 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/calendar.py +541 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/chats.py +136 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/connected_mailboxes.py +127 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/connectors.py +370 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/contacts.py +856 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/dns_connections.py +60 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/domains.py +755 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/drafts.py +154 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/emails.py +442 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/encryption.py +68 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/events.py +87 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/exports.py +84 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/files.py +326 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/follow_ups.py +85 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/forms.py +473 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/imports.py +349 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/keys.py +512 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/knowledge.py +550 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/labels.py +172 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/languages.py +32 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/me.py +224 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/members.py +290 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/out_of_office.py +69 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/provider_imports.py +142 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/roles.py +156 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/rules.py +259 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/security.py +124 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/segments.py +115 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/senders.py +45 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/sending.py +74 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/settings.py +48 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/signatures.py +174 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/subscriptions.py +195 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/support.py +31 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/suppressions.py +203 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/tasks.py +439 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/temp_mail.py +230 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/templates.py +625 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/threads.py +711 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/tools.py +138 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/tracking.py +289 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/webhooks.py +559 -0
- nextmail-0.0.2/src/nextmail/_sync/resources/workspaces.py +94 -0
- nextmail-0.0.2/src/nextmail/_sync/transport.py +258 -0
- nextmail-0.0.2/src/nextmail/_version.py +1 -0
- nextmail-0.0.2/src/nextmail/constants/__init__.py +4242 -0
- nextmail-0.0.2/src/nextmail/constants/client.py +78 -0
- nextmail-0.0.2/src/nextmail/py.typed +0 -0
- nextmail-0.0.2/src/nextmail/types/__init__.py +3092 -0
- nextmail-0.0.2/src/nextmail/types/__init__.pyi +10619 -0
- nextmail-0.0.2/src/nextmail/types/client.py +25 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.0.2
|
|
4
|
+
|
|
5
|
+
- API keys begin `nm_live_` or `nm_test_`, and temporary inbox tokens `nm_inbox_`. The client
|
|
6
|
+
refuses a key of any other shape, and a key created before this release no longer works: create a
|
|
7
|
+
new one in the app or with `keys.create`.
|
|
8
|
+
- Images up to 100 MB go in on `account.set_photo`, `contacts.set_photo`,
|
|
9
|
+
`domains.set_address_photo`, `bookings.upload_background`, `branding.upload_image` and
|
|
10
|
+
`templates.upload_image`. Each also takes `crop=`, the part of the image to keep as an
|
|
11
|
+
`ImageCrop` of fractions, and waits as long as `files.upload` does.
|
|
12
|
+
|
|
13
|
+
## 0.0.1
|
|
14
|
+
|
|
15
|
+
The first release of the Python package. It has a method for every one of the 684 methods of the
|
|
16
|
+
TypeScript SDK, in 53 namespaces, each under its TypeScript name in snake_case, and a parity check
|
|
17
|
+
in the package's own build proves that every one sends the same request as its TypeScript twin.
|
|
18
|
+
|
|
19
|
+
- Two clients with the same methods and arguments: `Nextmail`, which blocks, and `AsyncNextmail`,
|
|
20
|
+
which is awaited and runs on asyncio and on trio. Each has an attribute for every namespace, from
|
|
21
|
+
`emails`, `threads` and `templates` to `automations`, `bookings` and `connected_mailboxes`.
|
|
22
|
+
- A request body is a dict whose keys keep the API's camelCase names, the options of a call are
|
|
23
|
+
snake_case keyword arguments such as `idempotency_key=` and `limit=`, and a response comes back as
|
|
24
|
+
a dict. Every body, result and webhook payload has a `TypedDict` in `nextmail.types`, so a type
|
|
25
|
+
checker catches a misspelt key.
|
|
26
|
+
- A client takes an API key or an OAuth access token, or reads `NEXTMAIL_API_KEY`,
|
|
27
|
+
`NEXTMAIL_ACCESS_TOKEN` and `NEXTMAIL_BASE_URL`. `access_token=` also takes a function that
|
|
28
|
+
returns the token, called before every request, which may be async on `AsyncNextmail`.
|
|
29
|
+
`base_url`, `timeout`, `max_retries`, `headers`, `user_agent` and `http_client`, your own `httpx`
|
|
30
|
+
client, set the rest. Every method also takes `timeout=` for one call, and `api_key=`, or
|
|
31
|
+
`inbox_token=` on `temp_mail`, to act with another credential.
|
|
32
|
+
- Every workspace is the same kind, the one that holds the free address included. That address
|
|
33
|
+
sends only the mail a person writes in the app, so a send from it with an API key or an access
|
|
34
|
+
token is refused with `from_address_forbidden`.
|
|
35
|
+
- `from nextmail import nextmail` is a ready-made client, built from the environment the first time
|
|
36
|
+
it is touched, and `init(...)` configures it once at startup. `create_temp_mail()` and
|
|
37
|
+
`create_async_temp_mail()` open disposable inboxes with no credential.
|
|
38
|
+
- Every paginated resource has `list` for one page, `list_all` for every item as a list, and
|
|
39
|
+
`iterate`, which yields each item and fetches the next page only when it gets there.
|
|
40
|
+
- An API refusal raises `NextmailApiError`, with `status`, `type`, `code`, `param`, `doc_url`,
|
|
41
|
+
`request_id`, `retry_after_seconds` and `fields`, and checks such as `is_validation`,
|
|
42
|
+
`is_not_found`, `is_rate_limited`, `is_step_up_required` and `is_retryable`. No response at all
|
|
43
|
+
raises `NextmailNetworkError`, with `is_timeout` when the deadline passed. Both derive from
|
|
44
|
+
`NextmailError`, and a mistake in the call itself raises `ValueError` before anything is sent.
|
|
45
|
+
- Reads, sends and every write that is safe to repeat are retried on 408, 500, 502, 503 and 504 with
|
|
46
|
+
backoff, and on a 429 only when its `Retry-After` is a minute or less. A send carries an
|
|
47
|
+
idempotency key that its retries reuse, so a retry never sends a second message. `timeout` bounds
|
|
48
|
+
a whole attempt, the response body included, and uploads wait at least ten minutes.
|
|
49
|
+
- `verify_webhook_signature` checks a delivery in constant time, refuses one more than five minutes
|
|
50
|
+
old and returns the parsed event, or raises `WebhookVerificationError`.
|
|
51
|
+
- The helpers of the TypeScript SDK are here too, such as `to_base64` and `resolve_language`, and
|
|
52
|
+
every value set it exports is a constant in `nextmail.constants`, such as `WEBHOOK_EVENTS` and
|
|
53
|
+
`API_SCOPES`.
|
|
54
|
+
- `client.raw.request()` reaches any endpoint with the client's credential, base URL, timeout and
|
|
55
|
+
retries, and a credential is never sent over plain HTTP, except to a server on this machine.
|
|
56
|
+
- When a newer version is on PyPI, the client says so once, on a terminal only.
|
|
57
|
+
`disable_update_notice=True` or `NEXTMAIL_DISABLE_UPDATE_NOTICE=1` turns that off.
|
|
58
|
+
- It needs Python 3.10 or newer and depends only on `httpx`, `anyio` and `typing-extensions`.
|
nextmail-0.0.2/PKG-INFO
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nextmail
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: The official Python SDK for the Nextmail API. Send email and broadcasts, work with threads, drafts, labels, templates, rules, webhooks, tracking, contacts, audiences, sign-up forms, domains, suppressions, files, API keys, roles, members, mailbox imports and disposable inboxes from code.
|
|
5
|
+
Project-URL: Homepage, https://nextmail.sh
|
|
6
|
+
Project-URL: Documentation, https://nextmail.sh/docs/python
|
|
7
|
+
Project-URL: Reference, https://nextmail.sh/docs/python/reference/methods
|
|
8
|
+
Project-URL: Changelog, https://nextmail.sh/docs/python/changelog
|
|
9
|
+
Project-URL: Support, https://nextmail.sh/contact
|
|
10
|
+
Author: Nextmail
|
|
11
|
+
License-Expression: LicenseRef-Proprietary
|
|
12
|
+
Keywords: api,api client,email,email api,email templates,inbox,mailbox,nextmail,sdk,send email,temp mail,transactional email,webhooks
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Framework :: AsyncIO
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
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: Programming Language :: Python :: 3.14
|
|
24
|
+
Classifier: Topic :: Communications :: Email
|
|
25
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
26
|
+
Classifier: Typing :: Typed
|
|
27
|
+
Requires-Python: >=3.10
|
|
28
|
+
Requires-Dist: anyio>=3.7
|
|
29
|
+
Requires-Dist: httpx<1,>=0.27
|
|
30
|
+
Requires-Dist: typing-extensions>=4.12
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
<div align='center'>
|
|
34
|
+
<a href='https://nextmail.sh'>
|
|
35
|
+
<img
|
|
36
|
+
src='https://nextmail.sh/logo.svg'
|
|
37
|
+
alt='Nextmail Logo'
|
|
38
|
+
width='180'
|
|
39
|
+
/>
|
|
40
|
+
</a>
|
|
41
|
+
|
|
42
|
+
<br />
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<p align='center'>
|
|
46
|
+
Email you can build on. Send mail, read the mailbox and automate a workspace from code.
|
|
47
|
+
</p>
|
|
48
|
+
|
|
49
|
+
<p align='center'>
|
|
50
|
+
<a href='https://nextmail.sh'>
|
|
51
|
+
<b>
|
|
52
|
+
Website
|
|
53
|
+
</b>
|
|
54
|
+
</a>
|
|
55
|
+
•
|
|
56
|
+
<a href='https://nextmail.sh/docs/python'>
|
|
57
|
+
<b>
|
|
58
|
+
Documentation
|
|
59
|
+
</b>
|
|
60
|
+
</a>
|
|
61
|
+
•
|
|
62
|
+
<a href='https://nextmail.sh/docs/python/reference/methods'>
|
|
63
|
+
<b>
|
|
64
|
+
Every method
|
|
65
|
+
</b>
|
|
66
|
+
</a>
|
|
67
|
+
•
|
|
68
|
+
<a href='https://nextmail.sh/docs/api/reference'>
|
|
69
|
+
<b>
|
|
70
|
+
API Reference
|
|
71
|
+
</b>
|
|
72
|
+
</a>
|
|
73
|
+
</p>
|
|
74
|
+
|
|
75
|
+
<br />
|
|
76
|
+
|
|
77
|
+
## Intro to the Python Package
|
|
78
|
+
|
|
79
|
+
The official Python client for the Nextmail API. It has one method for every method of the TypeScript SDK, under the same names in snake_case, typed end to end. There is a synchronous client and an asynchronous one with the same methods, it runs on Python 3.10 and newer, and it depends only on `httpx`, `anyio` and `typing-extensions`.
|
|
80
|
+
|
|
81
|
+
It carries a workspace API key or an OAuth access token, so it belongs on a server or in a tool that runs on your own machine. The one exception is disposable inboxes, which need no credential.
|
|
82
|
+
|
|
83
|
+
### Installing
|
|
84
|
+
```bash
|
|
85
|
+
pip install nextmail
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Or `uv add nextmail`, or `poetry add nextmail`.
|
|
89
|
+
|
|
90
|
+
### Using
|
|
91
|
+
```python
|
|
92
|
+
from nextmail import Nextmail
|
|
93
|
+
|
|
94
|
+
client = Nextmail('nm_live_...')
|
|
95
|
+
|
|
96
|
+
sent = client.emails.send(
|
|
97
|
+
{
|
|
98
|
+
'from': 'Acme Billing <billing@acme.com>',
|
|
99
|
+
'to': 'ada@example.com',
|
|
100
|
+
'subject': 'Your September invoice',
|
|
101
|
+
'html': '<p>Your invoice is attached.</p>',
|
|
102
|
+
'attachments': [
|
|
103
|
+
{'filename': 'invoice.pdf', 'content': pdf_bytes, 'contentType': 'application/pdf'}
|
|
104
|
+
],
|
|
105
|
+
}
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
print(sent['id'], sent['status'])
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Request bodies are plain dictionaries with the API's own field names, so `'from'`, `'scheduledAt'` and `'replyTo'` read exactly as they do in the API reference. Responses are dictionaries too. Every body and response has a `TypedDict` in `nextmail.types`, so your editor completes the keys and a type checker catches a misspelt one.
|
|
112
|
+
|
|
113
|
+
Create a key in Nextmail under Settings, API keys. It is shown once, and it belongs in an environment variable rather than in code. `Nextmail()` with no key reads `NEXTMAIL_API_KEY`:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from nextmail import Nextmail
|
|
117
|
+
|
|
118
|
+
client = Nextmail()
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Build the client once, in a module of its own, and import it everywhere else. It keeps one connection pool, is safe to share between threads, and closes with `client.close()` or a `with` block.
|
|
122
|
+
|
|
123
|
+
Or skip even that. The package ships a ready made `nextmail` client that reads `NEXTMAIL_API_KEY` the first time it is touched:
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from nextmail import nextmail
|
|
127
|
+
|
|
128
|
+
nextmail.emails.send({'from': sender, 'to': recipient, 'subject': subject, 'text': text})
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Every send carries an idempotency key, generated once per call and reused by its retries, so a retried request replays the original message rather than sending a second one. Pass your own with `idempotency_key=` to make that hold across processes and restarts.
|
|
132
|
+
|
|
133
|
+
### Async
|
|
134
|
+
```python
|
|
135
|
+
import asyncio
|
|
136
|
+
|
|
137
|
+
from nextmail import AsyncNextmail
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
async def main() -> None:
|
|
141
|
+
async with AsyncNextmail() as client:
|
|
142
|
+
sent = await client.emails.send(
|
|
143
|
+
{'from': sender, 'to': recipient, 'subject': 'Hi', 'text': 'Hello'}
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
async for thread in client.threads.iterate(folder='inbox'):
|
|
147
|
+
print(thread['id'])
|
|
148
|
+
|
|
149
|
+
print(sent['status'])
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
asyncio.run(main())
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`AsyncNextmail` has every method `Nextmail` has, with the same arguments, and runs on asyncio and trio.
|
|
156
|
+
|
|
157
|
+
### Reading the mailbox
|
|
158
|
+
```python
|
|
159
|
+
page = client.threads.list(folder='inbox', limit=25)
|
|
160
|
+
|
|
161
|
+
for thread in client.threads.iterate(folder='inbox', query='invoice'):
|
|
162
|
+
full = client.threads.get(thread['id'])
|
|
163
|
+
|
|
164
|
+
print(full['messageCount'], full['hasUnread'])
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Every paginated resource has `list` for one page, `list_all` for every page at once and `iterate` to stream items and stop whenever you like. A page is `{'items': [...], 'hasMore': ..., 'nextCursor': ...}`, and `list_all` returns one list, apart from `addresses.list_all`, which returns the whole address book.
|
|
168
|
+
|
|
169
|
+
### Errors
|
|
170
|
+
```python
|
|
171
|
+
from nextmail import NextmailApiError, nextmail
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
nextmail.templates.send(
|
|
175
|
+
'order-shipped',
|
|
176
|
+
{
|
|
177
|
+
'from': 'dispatch@acme.com',
|
|
178
|
+
'to': 'ada@example.com',
|
|
179
|
+
'props': {'orderId': 'AC-4192'},
|
|
180
|
+
},
|
|
181
|
+
)
|
|
182
|
+
except NextmailApiError as error:
|
|
183
|
+
if error.is_validation:
|
|
184
|
+
print(error.code, error.param, error.request_id)
|
|
185
|
+
|
|
186
|
+
raise
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
An API refusal is one class, `NextmailApiError`, with `status`, `type`, `code`, `param` and `request_id`, plus `is_validation`, `is_not_found`, `is_rate_limited` and friends to branch on. No response at all is `NextmailNetworkError`, with `is_timeout` when the deadline passed. Both inherit `NextmailError`. An argument the client can tell is wrong before anything is sent, such as a malformed key, raises `ValueError`.
|
|
190
|
+
|
|
191
|
+
### Webhooks
|
|
192
|
+
```python
|
|
193
|
+
import os
|
|
194
|
+
|
|
195
|
+
from fastapi import FastAPI, Request, Response
|
|
196
|
+
from nextmail import verify_webhook_signature
|
|
197
|
+
|
|
198
|
+
app = FastAPI()
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@app.post('/webhooks/nextmail')
|
|
202
|
+
async def webhook(request: Request) -> Response:
|
|
203
|
+
event = verify_webhook_signature(
|
|
204
|
+
payload=await request.body(),
|
|
205
|
+
headers=request.headers,
|
|
206
|
+
secret=os.environ['NEXTMAIL_WEBHOOK_SECRET'],
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
print(event['type'], event['data'])
|
|
210
|
+
|
|
211
|
+
return Response(status_code=204)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
It checks the HMAC in constant time and rejects a delivery more than five minutes old, then returns the parsed event, or raises `WebhookVerificationError`. Pass the raw body as bytes or text: re-serialising it changes the bytes and the signature will not match. The headers can come from any framework, since the lookup ignores case.
|
|
215
|
+
|
|
216
|
+
### Disposable inboxes
|
|
217
|
+
```python
|
|
218
|
+
from nextmail import create_temp_mail
|
|
219
|
+
|
|
220
|
+
temp = create_temp_mail()
|
|
221
|
+
|
|
222
|
+
inbox = temp.create({'ttlMinutes': 60})
|
|
223
|
+
|
|
224
|
+
messages = temp.list_messages(inbox['id'], inbox_token=inbox['token'])
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
`create` needs no credential and is the only call that returns the inbox token, so keep it.
|
|
228
|
+
|
|
229
|
+
### OAuth access tokens
|
|
230
|
+
An app a person connected to Nextmail with OAuth, such as a command line tool or an agent, holds an access token rather than an API key. Pass it as `access_token`:
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
from nextmail import Nextmail
|
|
234
|
+
|
|
235
|
+
client = Nextmail(access_token=session.fresh_access_token)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
`access_token` takes the token itself, or a function that returns it. The function runs before every request, so renew the token there when it is close to expiring and the client never has to be rebuilt. On `AsyncNextmail` the function may also be `async`. Pass `api_key` or `access_token`, not both. `Nextmail()` reads `NEXTMAIL_ACCESS_TOKEN` when you pass neither and `NEXTMAIL_API_KEY` is not set. `me.get()` answers `'object': 'oauth_token'` for a token, with the connected app's `clientId` and `expiresAt`, when the person's approval of the app runs out.
|
|
239
|
+
|
|
240
|
+
A token acts for a person, so before a sensitive change, such as deleting a domain or changing a webhook, it is asked for the same verification code the web app asks for. The request fails with `is_step_up_required`. Ask for a code, check it, then replay the request:
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
from nextmail import NextmailApiError
|
|
244
|
+
|
|
245
|
+
try:
|
|
246
|
+
client.domains.delete(domain_id)
|
|
247
|
+
except NextmailApiError as error:
|
|
248
|
+
if not error.is_step_up_required:
|
|
249
|
+
raise
|
|
250
|
+
|
|
251
|
+
challenge = client.security.begin_step_up()
|
|
252
|
+
|
|
253
|
+
if challenge['method'] == 'email':
|
|
254
|
+
prompt = f'Enter the code we emailed to {challenge.get("sentTo")}: '
|
|
255
|
+
else:
|
|
256
|
+
prompt = 'Enter the code from your authenticator app, or a backup code: '
|
|
257
|
+
|
|
258
|
+
client.security.verify_step_up({'code': input(prompt)})
|
|
259
|
+
client.domains.delete(domain_id)
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
An emailed code works for 10 minutes, and `begin_step_up({'resend': True})` sends a fresh one. Once a code is verified the app is not asked again for 60 minutes. `security.step_up_status()` says whether it is verified right now. API keys are never asked for a code.
|
|
263
|
+
|
|
264
|
+
### Configuring
|
|
265
|
+
Pass keyword arguments when the defaults are not right:
|
|
266
|
+
|
|
267
|
+
```python
|
|
268
|
+
import os
|
|
269
|
+
|
|
270
|
+
import httpx
|
|
271
|
+
from nextmail import Nextmail
|
|
272
|
+
|
|
273
|
+
client = Nextmail(
|
|
274
|
+
os.environ['NEXTMAIL_API_KEY'],
|
|
275
|
+
base_url='https://api.nextmail.sh',
|
|
276
|
+
timeout=30,
|
|
277
|
+
max_retries=2,
|
|
278
|
+
http_client=httpx.Client(proxy='http://proxy.internal:3128', follow_redirects=True),
|
|
279
|
+
headers={'X-Team': 'billing'},
|
|
280
|
+
)
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
The shipped `nextmail` client takes the same arguments through `init(...)`, called once at startup.
|
|
284
|
+
|
|
285
|
+
`base_url` also comes from `NEXTMAIL_BASE_URL`. Use an `https:` origin: the client refuses to send an API key, an access token or an inbox token over plain `http:`, and raises before the request leaves, unless the server is on this machine at `localhost`, a `127.x.x.x` address or `::1`. A `base_url` on `0.0.0.0` raises when the client is built, since that is the address a server listens on: use `127.0.0.1` with the same port.
|
|
286
|
+
|
|
287
|
+
`timeout` is in seconds and bounds the whole attempt, the response body included, and `0` turns it off. Reads are retried on 408 and 5xx with backoff. A 429 is retried only when it carries a `Retry-After`, and any wait longer than a minute raises instead of sleeping. Writes that cannot safely repeat are not retried. Every method outside `temp_mail` takes `api_key=` and `timeout=`, so one process can serve several workspaces with one client. The `temp_mail` methods take `inbox_token=` instead of `api_key=`.
|
|
288
|
+
|
|
289
|
+
An endpoint no method wraps yet is one `client.raw.request()` away, with the client's credential, base URL, timeout and retry policy applied:
|
|
290
|
+
|
|
291
|
+
```python
|
|
292
|
+
result = client.raw.request('/something-new', method='POST', body={'name': 'Invoices'})
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
The path must begin with a single `/`. Anything else, such as `//host/x`, raises before a request is sent, and so does a path whose finished URL leaves the base URL's origin, so the credential it carries never reaches another host.
|
|
296
|
+
|
|
297
|
+
When a newer version is on PyPI the client says so once on a terminal. `NEXTMAIL_DISABLE_UPDATE_NOTICE=1` or `disable_update_notice=True` turns that off.
|
nextmail-0.0.2/README.md
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
<div align='center'>
|
|
2
|
+
<a href='https://nextmail.sh'>
|
|
3
|
+
<img
|
|
4
|
+
src='https://nextmail.sh/logo.svg'
|
|
5
|
+
alt='Nextmail Logo'
|
|
6
|
+
width='180'
|
|
7
|
+
/>
|
|
8
|
+
</a>
|
|
9
|
+
|
|
10
|
+
<br />
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<p align='center'>
|
|
14
|
+
Email you can build on. Send mail, read the mailbox and automate a workspace from code.
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
<p align='center'>
|
|
18
|
+
<a href='https://nextmail.sh'>
|
|
19
|
+
<b>
|
|
20
|
+
Website
|
|
21
|
+
</b>
|
|
22
|
+
</a>
|
|
23
|
+
•
|
|
24
|
+
<a href='https://nextmail.sh/docs/python'>
|
|
25
|
+
<b>
|
|
26
|
+
Documentation
|
|
27
|
+
</b>
|
|
28
|
+
</a>
|
|
29
|
+
•
|
|
30
|
+
<a href='https://nextmail.sh/docs/python/reference/methods'>
|
|
31
|
+
<b>
|
|
32
|
+
Every method
|
|
33
|
+
</b>
|
|
34
|
+
</a>
|
|
35
|
+
•
|
|
36
|
+
<a href='https://nextmail.sh/docs/api/reference'>
|
|
37
|
+
<b>
|
|
38
|
+
API Reference
|
|
39
|
+
</b>
|
|
40
|
+
</a>
|
|
41
|
+
</p>
|
|
42
|
+
|
|
43
|
+
<br />
|
|
44
|
+
|
|
45
|
+
## Intro to the Python Package
|
|
46
|
+
|
|
47
|
+
The official Python client for the Nextmail API. It has one method for every method of the TypeScript SDK, under the same names in snake_case, typed end to end. There is a synchronous client and an asynchronous one with the same methods, it runs on Python 3.10 and newer, and it depends only on `httpx`, `anyio` and `typing-extensions`.
|
|
48
|
+
|
|
49
|
+
It carries a workspace API key or an OAuth access token, so it belongs on a server or in a tool that runs on your own machine. The one exception is disposable inboxes, which need no credential.
|
|
50
|
+
|
|
51
|
+
### Installing
|
|
52
|
+
```bash
|
|
53
|
+
pip install nextmail
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Or `uv add nextmail`, or `poetry add nextmail`.
|
|
57
|
+
|
|
58
|
+
### Using
|
|
59
|
+
```python
|
|
60
|
+
from nextmail import Nextmail
|
|
61
|
+
|
|
62
|
+
client = Nextmail('nm_live_...')
|
|
63
|
+
|
|
64
|
+
sent = client.emails.send(
|
|
65
|
+
{
|
|
66
|
+
'from': 'Acme Billing <billing@acme.com>',
|
|
67
|
+
'to': 'ada@example.com',
|
|
68
|
+
'subject': 'Your September invoice',
|
|
69
|
+
'html': '<p>Your invoice is attached.</p>',
|
|
70
|
+
'attachments': [
|
|
71
|
+
{'filename': 'invoice.pdf', 'content': pdf_bytes, 'contentType': 'application/pdf'}
|
|
72
|
+
],
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
print(sent['id'], sent['status'])
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Request bodies are plain dictionaries with the API's own field names, so `'from'`, `'scheduledAt'` and `'replyTo'` read exactly as they do in the API reference. Responses are dictionaries too. Every body and response has a `TypedDict` in `nextmail.types`, so your editor completes the keys and a type checker catches a misspelt one.
|
|
80
|
+
|
|
81
|
+
Create a key in Nextmail under Settings, API keys. It is shown once, and it belongs in an environment variable rather than in code. `Nextmail()` with no key reads `NEXTMAIL_API_KEY`:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from nextmail import Nextmail
|
|
85
|
+
|
|
86
|
+
client = Nextmail()
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Build the client once, in a module of its own, and import it everywhere else. It keeps one connection pool, is safe to share between threads, and closes with `client.close()` or a `with` block.
|
|
90
|
+
|
|
91
|
+
Or skip even that. The package ships a ready made `nextmail` client that reads `NEXTMAIL_API_KEY` the first time it is touched:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from nextmail import nextmail
|
|
95
|
+
|
|
96
|
+
nextmail.emails.send({'from': sender, 'to': recipient, 'subject': subject, 'text': text})
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Every send carries an idempotency key, generated once per call and reused by its retries, so a retried request replays the original message rather than sending a second one. Pass your own with `idempotency_key=` to make that hold across processes and restarts.
|
|
100
|
+
|
|
101
|
+
### Async
|
|
102
|
+
```python
|
|
103
|
+
import asyncio
|
|
104
|
+
|
|
105
|
+
from nextmail import AsyncNextmail
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
async def main() -> None:
|
|
109
|
+
async with AsyncNextmail() as client:
|
|
110
|
+
sent = await client.emails.send(
|
|
111
|
+
{'from': sender, 'to': recipient, 'subject': 'Hi', 'text': 'Hello'}
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
async for thread in client.threads.iterate(folder='inbox'):
|
|
115
|
+
print(thread['id'])
|
|
116
|
+
|
|
117
|
+
print(sent['status'])
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
asyncio.run(main())
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`AsyncNextmail` has every method `Nextmail` has, with the same arguments, and runs on asyncio and trio.
|
|
124
|
+
|
|
125
|
+
### Reading the mailbox
|
|
126
|
+
```python
|
|
127
|
+
page = client.threads.list(folder='inbox', limit=25)
|
|
128
|
+
|
|
129
|
+
for thread in client.threads.iterate(folder='inbox', query='invoice'):
|
|
130
|
+
full = client.threads.get(thread['id'])
|
|
131
|
+
|
|
132
|
+
print(full['messageCount'], full['hasUnread'])
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Every paginated resource has `list` for one page, `list_all` for every page at once and `iterate` to stream items and stop whenever you like. A page is `{'items': [...], 'hasMore': ..., 'nextCursor': ...}`, and `list_all` returns one list, apart from `addresses.list_all`, which returns the whole address book.
|
|
136
|
+
|
|
137
|
+
### Errors
|
|
138
|
+
```python
|
|
139
|
+
from nextmail import NextmailApiError, nextmail
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
nextmail.templates.send(
|
|
143
|
+
'order-shipped',
|
|
144
|
+
{
|
|
145
|
+
'from': 'dispatch@acme.com',
|
|
146
|
+
'to': 'ada@example.com',
|
|
147
|
+
'props': {'orderId': 'AC-4192'},
|
|
148
|
+
},
|
|
149
|
+
)
|
|
150
|
+
except NextmailApiError as error:
|
|
151
|
+
if error.is_validation:
|
|
152
|
+
print(error.code, error.param, error.request_id)
|
|
153
|
+
|
|
154
|
+
raise
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
An API refusal is one class, `NextmailApiError`, with `status`, `type`, `code`, `param` and `request_id`, plus `is_validation`, `is_not_found`, `is_rate_limited` and friends to branch on. No response at all is `NextmailNetworkError`, with `is_timeout` when the deadline passed. Both inherit `NextmailError`. An argument the client can tell is wrong before anything is sent, such as a malformed key, raises `ValueError`.
|
|
158
|
+
|
|
159
|
+
### Webhooks
|
|
160
|
+
```python
|
|
161
|
+
import os
|
|
162
|
+
|
|
163
|
+
from fastapi import FastAPI, Request, Response
|
|
164
|
+
from nextmail import verify_webhook_signature
|
|
165
|
+
|
|
166
|
+
app = FastAPI()
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@app.post('/webhooks/nextmail')
|
|
170
|
+
async def webhook(request: Request) -> Response:
|
|
171
|
+
event = verify_webhook_signature(
|
|
172
|
+
payload=await request.body(),
|
|
173
|
+
headers=request.headers,
|
|
174
|
+
secret=os.environ['NEXTMAIL_WEBHOOK_SECRET'],
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
print(event['type'], event['data'])
|
|
178
|
+
|
|
179
|
+
return Response(status_code=204)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
It checks the HMAC in constant time and rejects a delivery more than five minutes old, then returns the parsed event, or raises `WebhookVerificationError`. Pass the raw body as bytes or text: re-serialising it changes the bytes and the signature will not match. The headers can come from any framework, since the lookup ignores case.
|
|
183
|
+
|
|
184
|
+
### Disposable inboxes
|
|
185
|
+
```python
|
|
186
|
+
from nextmail import create_temp_mail
|
|
187
|
+
|
|
188
|
+
temp = create_temp_mail()
|
|
189
|
+
|
|
190
|
+
inbox = temp.create({'ttlMinutes': 60})
|
|
191
|
+
|
|
192
|
+
messages = temp.list_messages(inbox['id'], inbox_token=inbox['token'])
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
`create` needs no credential and is the only call that returns the inbox token, so keep it.
|
|
196
|
+
|
|
197
|
+
### OAuth access tokens
|
|
198
|
+
An app a person connected to Nextmail with OAuth, such as a command line tool or an agent, holds an access token rather than an API key. Pass it as `access_token`:
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
from nextmail import Nextmail
|
|
202
|
+
|
|
203
|
+
client = Nextmail(access_token=session.fresh_access_token)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
`access_token` takes the token itself, or a function that returns it. The function runs before every request, so renew the token there when it is close to expiring and the client never has to be rebuilt. On `AsyncNextmail` the function may also be `async`. Pass `api_key` or `access_token`, not both. `Nextmail()` reads `NEXTMAIL_ACCESS_TOKEN` when you pass neither and `NEXTMAIL_API_KEY` is not set. `me.get()` answers `'object': 'oauth_token'` for a token, with the connected app's `clientId` and `expiresAt`, when the person's approval of the app runs out.
|
|
207
|
+
|
|
208
|
+
A token acts for a person, so before a sensitive change, such as deleting a domain or changing a webhook, it is asked for the same verification code the web app asks for. The request fails with `is_step_up_required`. Ask for a code, check it, then replay the request:
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
from nextmail import NextmailApiError
|
|
212
|
+
|
|
213
|
+
try:
|
|
214
|
+
client.domains.delete(domain_id)
|
|
215
|
+
except NextmailApiError as error:
|
|
216
|
+
if not error.is_step_up_required:
|
|
217
|
+
raise
|
|
218
|
+
|
|
219
|
+
challenge = client.security.begin_step_up()
|
|
220
|
+
|
|
221
|
+
if challenge['method'] == 'email':
|
|
222
|
+
prompt = f'Enter the code we emailed to {challenge.get("sentTo")}: '
|
|
223
|
+
else:
|
|
224
|
+
prompt = 'Enter the code from your authenticator app, or a backup code: '
|
|
225
|
+
|
|
226
|
+
client.security.verify_step_up({'code': input(prompt)})
|
|
227
|
+
client.domains.delete(domain_id)
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
An emailed code works for 10 minutes, and `begin_step_up({'resend': True})` sends a fresh one. Once a code is verified the app is not asked again for 60 minutes. `security.step_up_status()` says whether it is verified right now. API keys are never asked for a code.
|
|
231
|
+
|
|
232
|
+
### Configuring
|
|
233
|
+
Pass keyword arguments when the defaults are not right:
|
|
234
|
+
|
|
235
|
+
```python
|
|
236
|
+
import os
|
|
237
|
+
|
|
238
|
+
import httpx
|
|
239
|
+
from nextmail import Nextmail
|
|
240
|
+
|
|
241
|
+
client = Nextmail(
|
|
242
|
+
os.environ['NEXTMAIL_API_KEY'],
|
|
243
|
+
base_url='https://api.nextmail.sh',
|
|
244
|
+
timeout=30,
|
|
245
|
+
max_retries=2,
|
|
246
|
+
http_client=httpx.Client(proxy='http://proxy.internal:3128', follow_redirects=True),
|
|
247
|
+
headers={'X-Team': 'billing'},
|
|
248
|
+
)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
The shipped `nextmail` client takes the same arguments through `init(...)`, called once at startup.
|
|
252
|
+
|
|
253
|
+
`base_url` also comes from `NEXTMAIL_BASE_URL`. Use an `https:` origin: the client refuses to send an API key, an access token or an inbox token over plain `http:`, and raises before the request leaves, unless the server is on this machine at `localhost`, a `127.x.x.x` address or `::1`. A `base_url` on `0.0.0.0` raises when the client is built, since that is the address a server listens on: use `127.0.0.1` with the same port.
|
|
254
|
+
|
|
255
|
+
`timeout` is in seconds and bounds the whole attempt, the response body included, and `0` turns it off. Reads are retried on 408 and 5xx with backoff. A 429 is retried only when it carries a `Retry-After`, and any wait longer than a minute raises instead of sleeping. Writes that cannot safely repeat are not retried. Every method outside `temp_mail` takes `api_key=` and `timeout=`, so one process can serve several workspaces with one client. The `temp_mail` methods take `inbox_token=` instead of `api_key=`.
|
|
256
|
+
|
|
257
|
+
An endpoint no method wraps yet is one `client.raw.request()` away, with the client's credential, base URL, timeout and retry policy applied:
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
result = client.raw.request('/something-new', method='POST', body={'name': 'Invoices'})
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
The path must begin with a single `/`. Anything else, such as `//host/x`, raises before a request is sent, and so does a path whose finished URL leaves the base URL's origin, so the credential it carries never reaches another host.
|
|
264
|
+
|
|
265
|
+
When a newer version is on PyPI the client says so once on a terminal. `NEXTMAIL_DISABLE_UPDATE_NOTICE=1` or `disable_update_notice=True` turns that off.
|