mailgent-sdk 0.6.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.
- mailgent_sdk-0.6.2/LICENSE +21 -0
- mailgent_sdk-0.6.2/PKG-INFO +249 -0
- mailgent_sdk-0.6.2/README.md +213 -0
- mailgent_sdk-0.6.2/mailgent/__init__.py +41 -0
- mailgent_sdk-0.6.2/mailgent/_errors.py +10 -0
- mailgent_sdk-0.6.2/mailgent/_http.py +104 -0
- mailgent_sdk-0.6.2/mailgent/client.py +77 -0
- mailgent_sdk-0.6.2/mailgent/platform_client.py +59 -0
- mailgent_sdk-0.6.2/mailgent/resources/__init__.py +0 -0
- mailgent_sdk-0.6.2/mailgent/resources/calendar.py +93 -0
- mailgent_sdk-0.6.2/mailgent/resources/did.py +24 -0
- mailgent_sdk-0.6.2/mailgent/resources/identity.py +18 -0
- mailgent_sdk-0.6.2/mailgent/resources/logs.py +47 -0
- mailgent_sdk-0.6.2/mailgent/resources/mail.py +138 -0
- mailgent_sdk-0.6.2/mailgent/resources/payments.py +174 -0
- mailgent_sdk-0.6.2/mailgent/resources/platform_identities.py +129 -0
- mailgent_sdk-0.6.2/mailgent/resources/vault.py +134 -0
- mailgent_sdk-0.6.2/mailgent/types.py +696 -0
- mailgent_sdk-0.6.2/mailgent/webhook.py +67 -0
- mailgent_sdk-0.6.2/mailgent_sdk.egg-info/PKG-INFO +249 -0
- mailgent_sdk-0.6.2/mailgent_sdk.egg-info/SOURCES.txt +33 -0
- mailgent_sdk-0.6.2/mailgent_sdk.egg-info/dependency_links.txt +1 -0
- mailgent_sdk-0.6.2/mailgent_sdk.egg-info/requires.txt +8 -0
- mailgent_sdk-0.6.2/mailgent_sdk.egg-info/top_level.txt +1 -0
- mailgent_sdk-0.6.2/pyproject.toml +54 -0
- mailgent_sdk-0.6.2/setup.cfg +4 -0
- mailgent_sdk-0.6.2/tests/test_calendar.py +91 -0
- mailgent_sdk-0.6.2/tests/test_client.py +52 -0
- mailgent_sdk-0.6.2/tests/test_http.py +75 -0
- mailgent_sdk-0.6.2/tests/test_mail_rules.py +79 -0
- mailgent_sdk-0.6.2/tests/test_payments.py +340 -0
- mailgent_sdk-0.6.2/tests/test_platform.py +115 -0
- mailgent_sdk-0.6.2/tests/test_types.py +85 -0
- mailgent_sdk-0.6.2/tests/test_vault.py +261 -0
- mailgent_sdk-0.6.2/tests/test_webhook.py +58 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Loomal
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mailgent-sdk
|
|
3
|
+
Version: 0.6.2
|
|
4
|
+
Summary: Official Python SDK for the Mailgent API — identity, mail, vault, calendar, and buyer x402 payments for AI agents
|
|
5
|
+
Author: Mailgent
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://mailgent.dev
|
|
8
|
+
Project-URL: Documentation, https://docs.mailgent.dev
|
|
9
|
+
Project-URL: Repository, https://github.com/mailgent-dev/mailgent-python
|
|
10
|
+
Project-URL: Issues, https://github.com/mailgent-dev/mailgent-python/issues
|
|
11
|
+
Keywords: mailgent,ai,agent,email,mcp,did,vault,sdk,x402,usdc,payments
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: httpx>=0.27
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
32
|
+
Requires-Dist: respx>=0.21; extra == "dev"
|
|
33
|
+
Requires-Dist: build; extra == "dev"
|
|
34
|
+
Requires-Dist: twine; extra == "dev"
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
# Mailgent Python SDK
|
|
38
|
+
|
|
39
|
+
The official Python SDK for the [Mailgent API](https://mailgent.dev) -- identity, mail, vault, calendar, and **Mailgent Pay** for AI agents.
|
|
40
|
+
|
|
41
|
+
[](https://pypi.org/project/mailgent-sdk/)
|
|
42
|
+
[](https://pypi.org/project/mailgent-sdk/)
|
|
43
|
+
[](https://opensource.org/licenses/MIT)
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install mailgent-sdk
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
> The distribution is `mailgent-sdk` on PyPI, but the import name is `mailgent`.
|
|
52
|
+
|
|
53
|
+
## Buyer payments (x402)
|
|
54
|
+
|
|
55
|
+
Pay any x402-protected URL from your project's wallet. Mailgent drives the
|
|
56
|
+
full handshake — discover the 402 challenge, enforce mandate caps, sign
|
|
57
|
+
EIP-3009, retry, and record — then returns a discriminated result.
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
result = client.payments.pay(url="https://api.example.com")
|
|
61
|
+
if result["ok"]:
|
|
62
|
+
print(result["txHash"], result["cost"]["amountUsdc"])
|
|
63
|
+
else:
|
|
64
|
+
print(result["code"], result["hint"])
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Spend policy lives at `client.payments.mandates`. Requires the
|
|
68
|
+
`payments:spend` scope on the API key. See the
|
|
69
|
+
[full payments guide](https://docs.mailgent.dev/payments).
|
|
70
|
+
|
|
71
|
+
### Webhook signature verification
|
|
72
|
+
|
|
73
|
+
Mailgent sends `X-Mailgent-Signature: sha256=<hex>` (HMAC-SHA256 of the raw
|
|
74
|
+
request body) along with `X-Mailgent-Event` / `X-Mailgent-Idempotency-Key`.
|
|
75
|
+
The signing secret is generated by Mailgent when you register an endpoint
|
|
76
|
+
in the Console and shown to you exactly once.
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
import os
|
|
80
|
+
from fastapi import FastAPI, HTTPException, Request
|
|
81
|
+
from mailgent.webhook import verify_webhook
|
|
82
|
+
|
|
83
|
+
app = FastAPI()
|
|
84
|
+
|
|
85
|
+
@app.post("/webhooks/mailgent")
|
|
86
|
+
async def webhook(request: Request):
|
|
87
|
+
raw = await request.body()
|
|
88
|
+
ok = verify_webhook(
|
|
89
|
+
raw,
|
|
90
|
+
request.headers.get("x-mailgent-signature"),
|
|
91
|
+
os.environ["MAILGENT_WEBHOOK_SECRET"],
|
|
92
|
+
)
|
|
93
|
+
if not ok:
|
|
94
|
+
raise HTTPException(400, "invalid signature")
|
|
95
|
+
# de-dupe on x-mailgent-idempotency-key, then handle the event.
|
|
96
|
+
# Today the only event type is `payment.received`.
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Quick start
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from mailgent import Mailgent
|
|
103
|
+
|
|
104
|
+
client = Mailgent(api_key="loid-...")
|
|
105
|
+
|
|
106
|
+
me = client.identity.whoami()
|
|
107
|
+
print(me.email)
|
|
108
|
+
|
|
109
|
+
client.mail.send(
|
|
110
|
+
to=["colleague@example.com"],
|
|
111
|
+
subject="Hello from my agent",
|
|
112
|
+
text="Sent via the Mailgent Python SDK.",
|
|
113
|
+
)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Async usage
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from mailgent import AsyncMailgent
|
|
120
|
+
|
|
121
|
+
async with AsyncMailgent(api_key="loid-...") as client:
|
|
122
|
+
me = await client.identity.whoami()
|
|
123
|
+
await client.mail.send(
|
|
124
|
+
to=["colleague@example.com"],
|
|
125
|
+
subject="Hello",
|
|
126
|
+
text="Sent asynchronously.",
|
|
127
|
+
)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Authentication
|
|
131
|
+
|
|
132
|
+
Pass your API key directly, or set the `MAILGENT_API_KEY` environment variable:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
# Explicit
|
|
136
|
+
client = Mailgent(api_key="loid-...")
|
|
137
|
+
|
|
138
|
+
# From environment
|
|
139
|
+
import os
|
|
140
|
+
os.environ["MAILGENT_API_KEY"] = "loid-..."
|
|
141
|
+
client = Mailgent()
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Both `Mailgent` and `AsyncMailgent` support context managers for automatic resource cleanup:
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
with Mailgent() as client:
|
|
148
|
+
me = client.identity.whoami()
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Usage
|
|
152
|
+
|
|
153
|
+
### Identity
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
me = client.identity.whoami()
|
|
157
|
+
print(me.email, me.display_name)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Vault
|
|
161
|
+
|
|
162
|
+
The vault is password-manager-style encrypted secret storage (AES-256-GCM at rest). Use `client.vault.store()` for arbitrary types, or the typed helpers below.
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
# Simple API key
|
|
166
|
+
client.vault.store_api_key("stripe", "sk_live_...")
|
|
167
|
+
|
|
168
|
+
# OAuth-style client credentials (client id + secret)
|
|
169
|
+
client.vault.store_api_key("twitter", {
|
|
170
|
+
"clientId": "abc123",
|
|
171
|
+
"secret": "def456",
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
# Credit card (encrypted at rest — this is a secret vault, not a payment processor)
|
|
175
|
+
client.vault.store_card("personal-visa", {
|
|
176
|
+
"cardholder": "Jane Doe",
|
|
177
|
+
"number": "4242 4242 4242 4242",
|
|
178
|
+
"expMonth": "12",
|
|
179
|
+
"expYear": "2029",
|
|
180
|
+
"cvc": "123",
|
|
181
|
+
"zip": "94103",
|
|
182
|
+
}, metadata={"brand": "Visa"})
|
|
183
|
+
|
|
184
|
+
# Shipping address
|
|
185
|
+
client.vault.store_shipping_address("home", {
|
|
186
|
+
"name": "Autonomous Agent",
|
|
187
|
+
"line1": "1 Demo Way",
|
|
188
|
+
"city": "San Francisco",
|
|
189
|
+
"state": "CA",
|
|
190
|
+
"postcode": "94103",
|
|
191
|
+
"country": "US",
|
|
192
|
+
})
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Supported credential types: `LOGIN`, `API_KEY`, `OAUTH`, `TOTP`, `SSH_KEY`, `DATABASE`, `SMTP`, `AWS`, `CERTIFICATE`, `CARD`, `SHIPPING_ADDRESS`, `CUSTOM`.
|
|
196
|
+
|
|
197
|
+
### More resources
|
|
198
|
+
|
|
199
|
+
The SDK also exposes `client.mail`, `client.calendar`, `client.logs`, and `client.did`. See the full reference at **[docs.mailgent.dev](https://docs.mailgent.dev)** for request/response shapes, pagination, and end-to-end examples.
|
|
200
|
+
|
|
201
|
+
## Error handling
|
|
202
|
+
|
|
203
|
+
All API errors raise `MailgentApiError` with structured fields:
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
from mailgent import MailgentApiError
|
|
207
|
+
|
|
208
|
+
try:
|
|
209
|
+
client.mail.send(to=["a@b.com"], subject="Hi", text="Hello")
|
|
210
|
+
except MailgentApiError as e:
|
|
211
|
+
print(e.status) # HTTP status code
|
|
212
|
+
print(e.code) # Error code string
|
|
213
|
+
print(e.message) # Human-readable message
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Types
|
|
217
|
+
|
|
218
|
+
The SDK returns typed dataclasses, not raw dictionaries. API responses are automatically converted from camelCase to snake_case.
|
|
219
|
+
|
|
220
|
+
| Type | Description |
|
|
221
|
+
|------|-------------|
|
|
222
|
+
| `IdentityResponse` | Agent identity details |
|
|
223
|
+
| `MessageResponse` | Email message |
|
|
224
|
+
| `ThreadResponse` | Thread summary |
|
|
225
|
+
| `ThreadDetailResponse` | Thread with messages |
|
|
226
|
+
| `CredentialMetadata` | Vault credential metadata |
|
|
227
|
+
| `CredentialWithData` | Credential with decrypted data |
|
|
228
|
+
| `ActivityLog` | Single activity log entry |
|
|
229
|
+
| `LogsStats` | Aggregated log statistics |
|
|
230
|
+
| `TotpResponse` | Generated TOTP code |
|
|
231
|
+
| `DidDocument` | DID document |
|
|
232
|
+
|
|
233
|
+
> **Note:** The `from` field in message responses is exposed as `from_addrs` since `from` is a reserved keyword in Python.
|
|
234
|
+
|
|
235
|
+
## Requirements
|
|
236
|
+
|
|
237
|
+
- Python 3.9+
|
|
238
|
+
- [`httpx`](https://www.python-httpx.org/) (installed automatically)
|
|
239
|
+
|
|
240
|
+
## Links
|
|
241
|
+
|
|
242
|
+
- [Documentation](https://docs.mailgent.dev)
|
|
243
|
+
- [Console](https://console.mailgent.dev)
|
|
244
|
+
- [Website](https://mailgent.dev)
|
|
245
|
+
- [PyPI](https://pypi.org/project/mailgent-sdk/)
|
|
246
|
+
|
|
247
|
+
## License
|
|
248
|
+
|
|
249
|
+
MIT
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# Mailgent Python SDK
|
|
2
|
+
|
|
3
|
+
The official Python SDK for the [Mailgent API](https://mailgent.dev) -- identity, mail, vault, calendar, and **Mailgent Pay** for AI agents.
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/mailgent-sdk/)
|
|
6
|
+
[](https://pypi.org/project/mailgent-sdk/)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install mailgent-sdk
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
> The distribution is `mailgent-sdk` on PyPI, but the import name is `mailgent`.
|
|
16
|
+
|
|
17
|
+
## Buyer payments (x402)
|
|
18
|
+
|
|
19
|
+
Pay any x402-protected URL from your project's wallet. Mailgent drives the
|
|
20
|
+
full handshake — discover the 402 challenge, enforce mandate caps, sign
|
|
21
|
+
EIP-3009, retry, and record — then returns a discriminated result.
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
result = client.payments.pay(url="https://api.example.com")
|
|
25
|
+
if result["ok"]:
|
|
26
|
+
print(result["txHash"], result["cost"]["amountUsdc"])
|
|
27
|
+
else:
|
|
28
|
+
print(result["code"], result["hint"])
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Spend policy lives at `client.payments.mandates`. Requires the
|
|
32
|
+
`payments:spend` scope on the API key. See the
|
|
33
|
+
[full payments guide](https://docs.mailgent.dev/payments).
|
|
34
|
+
|
|
35
|
+
### Webhook signature verification
|
|
36
|
+
|
|
37
|
+
Mailgent sends `X-Mailgent-Signature: sha256=<hex>` (HMAC-SHA256 of the raw
|
|
38
|
+
request body) along with `X-Mailgent-Event` / `X-Mailgent-Idempotency-Key`.
|
|
39
|
+
The signing secret is generated by Mailgent when you register an endpoint
|
|
40
|
+
in the Console and shown to you exactly once.
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
import os
|
|
44
|
+
from fastapi import FastAPI, HTTPException, Request
|
|
45
|
+
from mailgent.webhook import verify_webhook
|
|
46
|
+
|
|
47
|
+
app = FastAPI()
|
|
48
|
+
|
|
49
|
+
@app.post("/webhooks/mailgent")
|
|
50
|
+
async def webhook(request: Request):
|
|
51
|
+
raw = await request.body()
|
|
52
|
+
ok = verify_webhook(
|
|
53
|
+
raw,
|
|
54
|
+
request.headers.get("x-mailgent-signature"),
|
|
55
|
+
os.environ["MAILGENT_WEBHOOK_SECRET"],
|
|
56
|
+
)
|
|
57
|
+
if not ok:
|
|
58
|
+
raise HTTPException(400, "invalid signature")
|
|
59
|
+
# de-dupe on x-mailgent-idempotency-key, then handle the event.
|
|
60
|
+
# Today the only event type is `payment.received`.
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Quick start
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from mailgent import Mailgent
|
|
67
|
+
|
|
68
|
+
client = Mailgent(api_key="loid-...")
|
|
69
|
+
|
|
70
|
+
me = client.identity.whoami()
|
|
71
|
+
print(me.email)
|
|
72
|
+
|
|
73
|
+
client.mail.send(
|
|
74
|
+
to=["colleague@example.com"],
|
|
75
|
+
subject="Hello from my agent",
|
|
76
|
+
text="Sent via the Mailgent Python SDK.",
|
|
77
|
+
)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Async usage
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from mailgent import AsyncMailgent
|
|
84
|
+
|
|
85
|
+
async with AsyncMailgent(api_key="loid-...") as client:
|
|
86
|
+
me = await client.identity.whoami()
|
|
87
|
+
await client.mail.send(
|
|
88
|
+
to=["colleague@example.com"],
|
|
89
|
+
subject="Hello",
|
|
90
|
+
text="Sent asynchronously.",
|
|
91
|
+
)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Authentication
|
|
95
|
+
|
|
96
|
+
Pass your API key directly, or set the `MAILGENT_API_KEY` environment variable:
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
# Explicit
|
|
100
|
+
client = Mailgent(api_key="loid-...")
|
|
101
|
+
|
|
102
|
+
# From environment
|
|
103
|
+
import os
|
|
104
|
+
os.environ["MAILGENT_API_KEY"] = "loid-..."
|
|
105
|
+
client = Mailgent()
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Both `Mailgent` and `AsyncMailgent` support context managers for automatic resource cleanup:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
with Mailgent() as client:
|
|
112
|
+
me = client.identity.whoami()
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Usage
|
|
116
|
+
|
|
117
|
+
### Identity
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
me = client.identity.whoami()
|
|
121
|
+
print(me.email, me.display_name)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Vault
|
|
125
|
+
|
|
126
|
+
The vault is password-manager-style encrypted secret storage (AES-256-GCM at rest). Use `client.vault.store()` for arbitrary types, or the typed helpers below.
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
# Simple API key
|
|
130
|
+
client.vault.store_api_key("stripe", "sk_live_...")
|
|
131
|
+
|
|
132
|
+
# OAuth-style client credentials (client id + secret)
|
|
133
|
+
client.vault.store_api_key("twitter", {
|
|
134
|
+
"clientId": "abc123",
|
|
135
|
+
"secret": "def456",
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
# Credit card (encrypted at rest — this is a secret vault, not a payment processor)
|
|
139
|
+
client.vault.store_card("personal-visa", {
|
|
140
|
+
"cardholder": "Jane Doe",
|
|
141
|
+
"number": "4242 4242 4242 4242",
|
|
142
|
+
"expMonth": "12",
|
|
143
|
+
"expYear": "2029",
|
|
144
|
+
"cvc": "123",
|
|
145
|
+
"zip": "94103",
|
|
146
|
+
}, metadata={"brand": "Visa"})
|
|
147
|
+
|
|
148
|
+
# Shipping address
|
|
149
|
+
client.vault.store_shipping_address("home", {
|
|
150
|
+
"name": "Autonomous Agent",
|
|
151
|
+
"line1": "1 Demo Way",
|
|
152
|
+
"city": "San Francisco",
|
|
153
|
+
"state": "CA",
|
|
154
|
+
"postcode": "94103",
|
|
155
|
+
"country": "US",
|
|
156
|
+
})
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Supported credential types: `LOGIN`, `API_KEY`, `OAUTH`, `TOTP`, `SSH_KEY`, `DATABASE`, `SMTP`, `AWS`, `CERTIFICATE`, `CARD`, `SHIPPING_ADDRESS`, `CUSTOM`.
|
|
160
|
+
|
|
161
|
+
### More resources
|
|
162
|
+
|
|
163
|
+
The SDK also exposes `client.mail`, `client.calendar`, `client.logs`, and `client.did`. See the full reference at **[docs.mailgent.dev](https://docs.mailgent.dev)** for request/response shapes, pagination, and end-to-end examples.
|
|
164
|
+
|
|
165
|
+
## Error handling
|
|
166
|
+
|
|
167
|
+
All API errors raise `MailgentApiError` with structured fields:
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
from mailgent import MailgentApiError
|
|
171
|
+
|
|
172
|
+
try:
|
|
173
|
+
client.mail.send(to=["a@b.com"], subject="Hi", text="Hello")
|
|
174
|
+
except MailgentApiError as e:
|
|
175
|
+
print(e.status) # HTTP status code
|
|
176
|
+
print(e.code) # Error code string
|
|
177
|
+
print(e.message) # Human-readable message
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Types
|
|
181
|
+
|
|
182
|
+
The SDK returns typed dataclasses, not raw dictionaries. API responses are automatically converted from camelCase to snake_case.
|
|
183
|
+
|
|
184
|
+
| Type | Description |
|
|
185
|
+
|------|-------------|
|
|
186
|
+
| `IdentityResponse` | Agent identity details |
|
|
187
|
+
| `MessageResponse` | Email message |
|
|
188
|
+
| `ThreadResponse` | Thread summary |
|
|
189
|
+
| `ThreadDetailResponse` | Thread with messages |
|
|
190
|
+
| `CredentialMetadata` | Vault credential metadata |
|
|
191
|
+
| `CredentialWithData` | Credential with decrypted data |
|
|
192
|
+
| `ActivityLog` | Single activity log entry |
|
|
193
|
+
| `LogsStats` | Aggregated log statistics |
|
|
194
|
+
| `TotpResponse` | Generated TOTP code |
|
|
195
|
+
| `DidDocument` | DID document |
|
|
196
|
+
|
|
197
|
+
> **Note:** The `from` field in message responses is exposed as `from_addrs` since `from` is a reserved keyword in Python.
|
|
198
|
+
|
|
199
|
+
## Requirements
|
|
200
|
+
|
|
201
|
+
- Python 3.9+
|
|
202
|
+
- [`httpx`](https://www.python-httpx.org/) (installed automatically)
|
|
203
|
+
|
|
204
|
+
## Links
|
|
205
|
+
|
|
206
|
+
- [Documentation](https://docs.mailgent.dev)
|
|
207
|
+
- [Console](https://console.mailgent.dev)
|
|
208
|
+
- [Website](https://mailgent.dev)
|
|
209
|
+
- [PyPI](https://pypi.org/project/mailgent-sdk/)
|
|
210
|
+
|
|
211
|
+
## License
|
|
212
|
+
|
|
213
|
+
MIT
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from mailgent.client import Mailgent, AsyncMailgent
|
|
2
|
+
from mailgent.platform_client import MailgentPlatform, AsyncMailgentPlatform
|
|
3
|
+
from mailgent.types import (
|
|
4
|
+
MessageResponse, ThreadResponse, ThreadDetailResponse,
|
|
5
|
+
VaultCredentialType,
|
|
6
|
+
ApiKeySecretData, ApiKeyClientPairData,
|
|
7
|
+
CardData, CardMetadata, ShippingAddressData,
|
|
8
|
+
CredentialMetadata, CredentialWithData, IdentityResponse,
|
|
9
|
+
IdentitySummary, IdentityDetail, CreateIdentityResponse, RotateKeyResponse,
|
|
10
|
+
CalendarEvent,
|
|
11
|
+
ActivityLog, LogsStats, TotpResponse, TotpBackupResponse, DidDocument,
|
|
12
|
+
PaymentEndpointSummary, PaymentSummary, PaymentReceiptBody,
|
|
13
|
+
PaymentReceipt, PaymentDetail,
|
|
14
|
+
PAYMENT_ERROR_CODES, PaymentErrorCode,
|
|
15
|
+
PaymentsPayParams, PaymentsPaySuccess, PaymentsPayFailure, PaymentsPayResponse,
|
|
16
|
+
PaymentActivityIn, PaymentActivityOut, PaymentActivityRow, PaymentActivityList,
|
|
17
|
+
Mandate, MandateCreateParams, MandateList,
|
|
18
|
+
)
|
|
19
|
+
from mailgent._errors import MailgentApiError
|
|
20
|
+
|
|
21
|
+
__version__ = "0.6.2"
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"Mailgent", "AsyncMailgent",
|
|
25
|
+
"MailgentPlatform", "AsyncMailgentPlatform",
|
|
26
|
+
"MailgentApiError",
|
|
27
|
+
"MessageResponse", "ThreadResponse", "ThreadDetailResponse",
|
|
28
|
+
"VaultCredentialType",
|
|
29
|
+
"ApiKeySecretData", "ApiKeyClientPairData",
|
|
30
|
+
"CardData", "CardMetadata", "ShippingAddressData",
|
|
31
|
+
"CredentialMetadata", "CredentialWithData", "IdentityResponse",
|
|
32
|
+
"IdentitySummary", "IdentityDetail", "CreateIdentityResponse", "RotateKeyResponse",
|
|
33
|
+
"CalendarEvent",
|
|
34
|
+
"ActivityLog", "LogsStats", "TotpResponse", "DidDocument",
|
|
35
|
+
"PaymentEndpointSummary", "PaymentSummary", "PaymentReceiptBody",
|
|
36
|
+
"PaymentReceipt", "PaymentDetail",
|
|
37
|
+
"PAYMENT_ERROR_CODES", "PaymentErrorCode",
|
|
38
|
+
"PaymentsPayParams", "PaymentsPaySuccess", "PaymentsPayFailure", "PaymentsPayResponse",
|
|
39
|
+
"PaymentActivityIn", "PaymentActivityOut", "PaymentActivityRow", "PaymentActivityList",
|
|
40
|
+
"Mandate", "MandateCreateParams", "MandateList",
|
|
41
|
+
]
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
class MailgentApiError(Exception):
|
|
2
|
+
"""Raised when the Mailgent API returns a non-2xx response."""
|
|
3
|
+
|
|
4
|
+
def __init__(self, status: int, code: str, message: str) -> None:
|
|
5
|
+
super().__init__(message)
|
|
6
|
+
self.status = status
|
|
7
|
+
self.code = code
|
|
8
|
+
|
|
9
|
+
def __repr__(self) -> str:
|
|
10
|
+
return f"MailgentApiError(status={self.status}, code={self.code!r}, message={self.args[0]!r})"
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from mailgent._errors import MailgentApiError
|
|
8
|
+
|
|
9
|
+
DEFAULT_BASE_URL = "https://api.mailgent.dev"
|
|
10
|
+
DEFAULT_TIMEOUT = 30.0
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _build_headers(api_key: str) -> dict[str, str]:
|
|
14
|
+
return {
|
|
15
|
+
"Authorization": f"Bearer {api_key}",
|
|
16
|
+
"Content-Type": "application/json",
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _handle_response(response: httpx.Response) -> Any:
|
|
21
|
+
if response.status_code == 204:
|
|
22
|
+
return None
|
|
23
|
+
|
|
24
|
+
data = response.json() if response.content else {}
|
|
25
|
+
|
|
26
|
+
if not response.is_success:
|
|
27
|
+
raise MailgentApiError(
|
|
28
|
+
status=response.status_code,
|
|
29
|
+
code=data.get("error", "unknown_error"),
|
|
30
|
+
message=data.get("message", f"Request failed with status {response.status_code}"),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
return data
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class SyncHttpClient:
|
|
37
|
+
def __init__(self, base_url: str, api_key: str, timeout: float = DEFAULT_TIMEOUT) -> None:
|
|
38
|
+
self._client = httpx.Client(
|
|
39
|
+
base_url=base_url.rstrip("/"),
|
|
40
|
+
headers=_build_headers(api_key),
|
|
41
|
+
timeout=timeout,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def get(self, path: str, params: Optional[dict[str, Any]] = None) -> Any:
|
|
45
|
+
return _handle_response(self._client.get(path, params=params))
|
|
46
|
+
|
|
47
|
+
def post(self, path: str, json: Optional[dict[str, Any]] = None) -> Any:
|
|
48
|
+
return _handle_response(self._client.post(path, json=json))
|
|
49
|
+
|
|
50
|
+
def post_unchecked(self, path: str, json: Optional[dict[str, Any]] = None) -> Any:
|
|
51
|
+
"""POST that does not raise on 4xx/5xx — returns the parsed JSON body
|
|
52
|
+
verbatim. Use for endpoints that encode success/failure in the body
|
|
53
|
+
(e.g. ``/v0/payments/pay`` returns ``{"ok": ...}`` with status 200,
|
|
54
|
+
402, or 503)."""
|
|
55
|
+
response = self._client.post(path, json=json)
|
|
56
|
+
if response.status_code == 204:
|
|
57
|
+
return None
|
|
58
|
+
return response.json() if response.content else {}
|
|
59
|
+
|
|
60
|
+
def put(self, path: str, json: Optional[dict[str, Any]] = None) -> Any:
|
|
61
|
+
return _handle_response(self._client.put(path, json=json))
|
|
62
|
+
|
|
63
|
+
def patch(self, path: str, json: Optional[dict[str, Any]] = None) -> Any:
|
|
64
|
+
return _handle_response(self._client.patch(path, json=json))
|
|
65
|
+
|
|
66
|
+
def delete(self, path: str) -> Any:
|
|
67
|
+
return _handle_response(self._client.delete(path))
|
|
68
|
+
|
|
69
|
+
def close(self) -> None:
|
|
70
|
+
self._client.close()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class AsyncHttpClient:
|
|
74
|
+
def __init__(self, base_url: str, api_key: str, timeout: float = DEFAULT_TIMEOUT) -> None:
|
|
75
|
+
self._client = httpx.AsyncClient(
|
|
76
|
+
base_url=base_url.rstrip("/"),
|
|
77
|
+
headers=_build_headers(api_key),
|
|
78
|
+
timeout=timeout,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
async def get(self, path: str, params: Optional[dict[str, Any]] = None) -> Any:
|
|
82
|
+
return _handle_response(await self._client.get(path, params=params))
|
|
83
|
+
|
|
84
|
+
async def post(self, path: str, json: Optional[dict[str, Any]] = None) -> Any:
|
|
85
|
+
return _handle_response(await self._client.post(path, json=json))
|
|
86
|
+
|
|
87
|
+
async def post_unchecked(self, path: str, json: Optional[dict[str, Any]] = None) -> Any:
|
|
88
|
+
"""Async sibling of :meth:`SyncHttpClient.post_unchecked`."""
|
|
89
|
+
response = await self._client.post(path, json=json)
|
|
90
|
+
if response.status_code == 204:
|
|
91
|
+
return None
|
|
92
|
+
return response.json() if response.content else {}
|
|
93
|
+
|
|
94
|
+
async def put(self, path: str, json: Optional[dict[str, Any]] = None) -> Any:
|
|
95
|
+
return _handle_response(await self._client.put(path, json=json))
|
|
96
|
+
|
|
97
|
+
async def patch(self, path: str, json: Optional[dict[str, Any]] = None) -> Any:
|
|
98
|
+
return _handle_response(await self._client.patch(path, json=json))
|
|
99
|
+
|
|
100
|
+
async def delete(self, path: str) -> Any:
|
|
101
|
+
return _handle_response(await self._client.delete(path))
|
|
102
|
+
|
|
103
|
+
async def close(self) -> None:
|
|
104
|
+
await self._client.aclose()
|