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.
Files changed (35) hide show
  1. mailgent_sdk-0.6.2/LICENSE +21 -0
  2. mailgent_sdk-0.6.2/PKG-INFO +249 -0
  3. mailgent_sdk-0.6.2/README.md +213 -0
  4. mailgent_sdk-0.6.2/mailgent/__init__.py +41 -0
  5. mailgent_sdk-0.6.2/mailgent/_errors.py +10 -0
  6. mailgent_sdk-0.6.2/mailgent/_http.py +104 -0
  7. mailgent_sdk-0.6.2/mailgent/client.py +77 -0
  8. mailgent_sdk-0.6.2/mailgent/platform_client.py +59 -0
  9. mailgent_sdk-0.6.2/mailgent/resources/__init__.py +0 -0
  10. mailgent_sdk-0.6.2/mailgent/resources/calendar.py +93 -0
  11. mailgent_sdk-0.6.2/mailgent/resources/did.py +24 -0
  12. mailgent_sdk-0.6.2/mailgent/resources/identity.py +18 -0
  13. mailgent_sdk-0.6.2/mailgent/resources/logs.py +47 -0
  14. mailgent_sdk-0.6.2/mailgent/resources/mail.py +138 -0
  15. mailgent_sdk-0.6.2/mailgent/resources/payments.py +174 -0
  16. mailgent_sdk-0.6.2/mailgent/resources/platform_identities.py +129 -0
  17. mailgent_sdk-0.6.2/mailgent/resources/vault.py +134 -0
  18. mailgent_sdk-0.6.2/mailgent/types.py +696 -0
  19. mailgent_sdk-0.6.2/mailgent/webhook.py +67 -0
  20. mailgent_sdk-0.6.2/mailgent_sdk.egg-info/PKG-INFO +249 -0
  21. mailgent_sdk-0.6.2/mailgent_sdk.egg-info/SOURCES.txt +33 -0
  22. mailgent_sdk-0.6.2/mailgent_sdk.egg-info/dependency_links.txt +1 -0
  23. mailgent_sdk-0.6.2/mailgent_sdk.egg-info/requires.txt +8 -0
  24. mailgent_sdk-0.6.2/mailgent_sdk.egg-info/top_level.txt +1 -0
  25. mailgent_sdk-0.6.2/pyproject.toml +54 -0
  26. mailgent_sdk-0.6.2/setup.cfg +4 -0
  27. mailgent_sdk-0.6.2/tests/test_calendar.py +91 -0
  28. mailgent_sdk-0.6.2/tests/test_client.py +52 -0
  29. mailgent_sdk-0.6.2/tests/test_http.py +75 -0
  30. mailgent_sdk-0.6.2/tests/test_mail_rules.py +79 -0
  31. mailgent_sdk-0.6.2/tests/test_payments.py +340 -0
  32. mailgent_sdk-0.6.2/tests/test_platform.py +115 -0
  33. mailgent_sdk-0.6.2/tests/test_types.py +85 -0
  34. mailgent_sdk-0.6.2/tests/test_vault.py +261 -0
  35. 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
+ [![PyPI version](https://img.shields.io/pypi/v/mailgent-sdk.svg)](https://pypi.org/project/mailgent-sdk/)
42
+ [![Python 3.9+](https://img.shields.io/pypi/pyversions/mailgent-sdk.svg)](https://pypi.org/project/mailgent-sdk/)
43
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](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
+ [![PyPI version](https://img.shields.io/pypi/v/mailgent-sdk.svg)](https://pypi.org/project/mailgent-sdk/)
6
+ [![Python 3.9+](https://img.shields.io/pypi/pyversions/mailgent-sdk.svg)](https://pypi.org/project/mailgent-sdk/)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](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()