unitpost 0.3.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {unitpost-0.3.0 → unitpost-0.4.0}/PKG-INFO +42 -4
- {unitpost-0.3.0 → unitpost-0.4.0}/README.md +41 -3
- {unitpost-0.3.0 → unitpost-0.4.0}/pyproject.toml +1 -1
- {unitpost-0.3.0 → unitpost-0.4.0}/src/unitpost/_client.py +6 -0
- {unitpost-0.3.0 → unitpost-0.4.0}/src/unitpost/_generated/models.py +497 -10
- {unitpost-0.3.0 → unitpost-0.4.0}/src/unitpost/_generated/operations.py +467 -19
- {unitpost-0.3.0 → unitpost-0.4.0}/src/unitpost/_http.py +1 -1
- {unitpost-0.3.0 → unitpost-0.4.0}/src/unitpost/resources.py +199 -5
- {unitpost-0.3.0 → unitpost-0.4.0}/tests/test_sdk.py +9 -0
- {unitpost-0.3.0 → unitpost-0.4.0}/.gitignore +0 -0
- {unitpost-0.3.0 → unitpost-0.4.0}/package.json +0 -0
- {unitpost-0.3.0 → unitpost-0.4.0}/src/unitpost/__init__.py +0 -0
- {unitpost-0.3.0 → unitpost-0.4.0}/src/unitpost/_generated/__init__.py +0 -0
- {unitpost-0.3.0 → unitpost-0.4.0}/src/unitpost/_webhooks.py +0 -0
- {unitpost-0.3.0 → unitpost-0.4.0}/tests/test_retry.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: unitpost
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Official Unitpost SDK for Python — send email and manage contacts, segments, campaigns, templates, brand kits, domains, suppressions, webhooks, and usage.
|
|
5
5
|
Project-URL: Homepage, https://www.unitpost.com
|
|
6
6
|
Project-URL: Documentation, https://www.unitpost.com/docs
|
|
@@ -71,6 +71,7 @@ Pass the key to `Unitpost(...)`, or set `UNITPOST_API_KEY`. Create a key at
|
|
|
71
71
|
| `email.campaigns` | Draft, schedule, send, pause/resume |
|
|
72
72
|
| `email.templates` | Template CRUD |
|
|
73
73
|
| `email.domains` | Domains + verify |
|
|
74
|
+
| `sms` | Send, get, list, `brands()`, `numbers()`, contact SMS consent. Available on paid plans: on the free plan `send` returns a `403` with code `limit_reached`, while the reads keep working. |
|
|
74
75
|
| `contacts` | CRUD, import jobs |
|
|
75
76
|
| `contact_fields` | Custom field definitions |
|
|
76
77
|
| `segments` | Segments + membership |
|
|
@@ -98,7 +99,7 @@ complaints are added automatically; you can also add your own:
|
|
|
98
99
|
unitpost.suppressions.create({"email": "bounced@example.com", "reason": "manual"})
|
|
99
100
|
unitpost.suppressions.create({"emails": ["a@example.com", "b@example.com"]})
|
|
100
101
|
|
|
101
|
-
# Look one up by id or email; list
|
|
102
|
+
# Look one up by id or email; list your own; un-suppress.
|
|
102
103
|
unitpost.suppressions.get("bounced@example.com")
|
|
103
104
|
for s in unitpost.suppressions.list_all():
|
|
104
105
|
print(s["email"], s["reason"])
|
|
@@ -127,6 +128,42 @@ Or from the client (Resend-style alias):
|
|
|
127
128
|
event = client.webhooks.verify(payload=raw_body, secret=secret, headers=request.headers)
|
|
128
129
|
```
|
|
129
130
|
|
|
131
|
+
## Receive a text and answer it
|
|
132
|
+
|
|
133
|
+
US toll-free, 10DLC and Canadian numbers take replies (`two_way: True` on
|
|
134
|
+
`sms.numbers()`). Subscribe a webhook to `sms.received`, skip STOP and texts
|
|
135
|
+
Unitpost already answered, read the conversation with `participant`, and answer
|
|
136
|
+
without `from` so the reply leaves from the number they texted.
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
import os
|
|
140
|
+
from unitpost import Unitpost, verify_webhook
|
|
141
|
+
|
|
142
|
+
unitpost = Unitpost() # reads UNITPOST_API_KEY
|
|
143
|
+
|
|
144
|
+
# Your webhook endpoint, subscribed to sms.received. Pass the RAW body.
|
|
145
|
+
def on_webhook(raw_body: bytes, headers: dict) -> None:
|
|
146
|
+
event = verify_webhook(
|
|
147
|
+
payload=raw_body,
|
|
148
|
+
secret=os.environ["UNITPOST_WEBHOOK_SECRET"], # whsec_…
|
|
149
|
+
headers=headers,
|
|
150
|
+
)
|
|
151
|
+
if event["type"] != "sms.received":
|
|
152
|
+
return
|
|
153
|
+
reply = event["data"]
|
|
154
|
+
if reply["keyword"] == "OPT_OUT" or reply["auto_replied"]:
|
|
155
|
+
return # STOP, or already answered
|
|
156
|
+
|
|
157
|
+
# The whole conversation with this person, both directions, newest first.
|
|
158
|
+
# Other filters: direction, status, from_ (sent as `from`), to, contact_id,
|
|
159
|
+
# campaign_id, created_after, created_before.
|
|
160
|
+
history = unitpost.sms.list(participant=reply["from"], limit=20)
|
|
161
|
+
answer = your_agent(history.data["data"], reply["body"] or "")
|
|
162
|
+
|
|
163
|
+
# No "from": the answer goes out from the number they texted.
|
|
164
|
+
unitpost.sms.send({"to": reply["from"], "body": answer})
|
|
165
|
+
```
|
|
166
|
+
|
|
130
167
|
## Idempotency
|
|
131
168
|
|
|
132
169
|
`email.send` and `email.batch` are safe to retry by default — if you don't pass
|
|
@@ -162,7 +199,8 @@ unitpost = Unitpost(max_retries=2, retry_base_delay=0.5, max_retry_delay=20.0)
|
|
|
162
199
|
| | |
|
|
163
200
|
| --- | --- |
|
|
164
201
|
| Docs + API reference | https://www.unitpost.com/docs |
|
|
165
|
-
| Product
|
|
202
|
+
| Product guides | https://www.unitpost.com/guides |
|
|
203
|
+
| SMTP | https://www.unitpost.com/docs/smtp |
|
|
166
204
|
| OpenAPI JSON | https://www.unitpost.com/api/v1/openapi.json |
|
|
167
205
|
| LLM index | https://www.unitpost.com/llms.txt |
|
|
168
206
|
| Dashboard / API keys | https://www.unitpost.com |
|
|
@@ -170,7 +208,7 @@ unitpost = Unitpost(max_retries=2, retry_base_delay=0.5, max_retry_delay=20.0)
|
|
|
170
208
|
| Template gallery | https://www.unitpost.com/templates/gallery |
|
|
171
209
|
| Playground | https://www.unitpost.com/playground |
|
|
172
210
|
| Migration Assistant | https://www.unitpost.com/migration |
|
|
173
|
-
| MCP + Agent Skills | https://www.unitpost.com/
|
|
211
|
+
| MCP + Agent Skills | https://www.unitpost.com/docs/ai/mcp · https://www.unitpost.com/docs/ai/skills |
|
|
174
212
|
| Support | support@unitpost.com |
|
|
175
213
|
|
|
176
214
|
## License
|
|
@@ -48,6 +48,7 @@ Pass the key to `Unitpost(...)`, or set `UNITPOST_API_KEY`. Create a key at
|
|
|
48
48
|
| `email.campaigns` | Draft, schedule, send, pause/resume |
|
|
49
49
|
| `email.templates` | Template CRUD |
|
|
50
50
|
| `email.domains` | Domains + verify |
|
|
51
|
+
| `sms` | Send, get, list, `brands()`, `numbers()`, contact SMS consent. Available on paid plans: on the free plan `send` returns a `403` with code `limit_reached`, while the reads keep working. |
|
|
51
52
|
| `contacts` | CRUD, import jobs |
|
|
52
53
|
| `contact_fields` | Custom field definitions |
|
|
53
54
|
| `segments` | Segments + membership |
|
|
@@ -75,7 +76,7 @@ complaints are added automatically; you can also add your own:
|
|
|
75
76
|
unitpost.suppressions.create({"email": "bounced@example.com", "reason": "manual"})
|
|
76
77
|
unitpost.suppressions.create({"emails": ["a@example.com", "b@example.com"]})
|
|
77
78
|
|
|
78
|
-
# Look one up by id or email; list
|
|
79
|
+
# Look one up by id or email; list your own; un-suppress.
|
|
79
80
|
unitpost.suppressions.get("bounced@example.com")
|
|
80
81
|
for s in unitpost.suppressions.list_all():
|
|
81
82
|
print(s["email"], s["reason"])
|
|
@@ -104,6 +105,42 @@ Or from the client (Resend-style alias):
|
|
|
104
105
|
event = client.webhooks.verify(payload=raw_body, secret=secret, headers=request.headers)
|
|
105
106
|
```
|
|
106
107
|
|
|
108
|
+
## Receive a text and answer it
|
|
109
|
+
|
|
110
|
+
US toll-free, 10DLC and Canadian numbers take replies (`two_way: True` on
|
|
111
|
+
`sms.numbers()`). Subscribe a webhook to `sms.received`, skip STOP and texts
|
|
112
|
+
Unitpost already answered, read the conversation with `participant`, and answer
|
|
113
|
+
without `from` so the reply leaves from the number they texted.
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
import os
|
|
117
|
+
from unitpost import Unitpost, verify_webhook
|
|
118
|
+
|
|
119
|
+
unitpost = Unitpost() # reads UNITPOST_API_KEY
|
|
120
|
+
|
|
121
|
+
# Your webhook endpoint, subscribed to sms.received. Pass the RAW body.
|
|
122
|
+
def on_webhook(raw_body: bytes, headers: dict) -> None:
|
|
123
|
+
event = verify_webhook(
|
|
124
|
+
payload=raw_body,
|
|
125
|
+
secret=os.environ["UNITPOST_WEBHOOK_SECRET"], # whsec_…
|
|
126
|
+
headers=headers,
|
|
127
|
+
)
|
|
128
|
+
if event["type"] != "sms.received":
|
|
129
|
+
return
|
|
130
|
+
reply = event["data"]
|
|
131
|
+
if reply["keyword"] == "OPT_OUT" or reply["auto_replied"]:
|
|
132
|
+
return # STOP, or already answered
|
|
133
|
+
|
|
134
|
+
# The whole conversation with this person, both directions, newest first.
|
|
135
|
+
# Other filters: direction, status, from_ (sent as `from`), to, contact_id,
|
|
136
|
+
# campaign_id, created_after, created_before.
|
|
137
|
+
history = unitpost.sms.list(participant=reply["from"], limit=20)
|
|
138
|
+
answer = your_agent(history.data["data"], reply["body"] or "")
|
|
139
|
+
|
|
140
|
+
# No "from": the answer goes out from the number they texted.
|
|
141
|
+
unitpost.sms.send({"to": reply["from"], "body": answer})
|
|
142
|
+
```
|
|
143
|
+
|
|
107
144
|
## Idempotency
|
|
108
145
|
|
|
109
146
|
`email.send` and `email.batch` are safe to retry by default — if you don't pass
|
|
@@ -139,7 +176,8 @@ unitpost = Unitpost(max_retries=2, retry_base_delay=0.5, max_retry_delay=20.0)
|
|
|
139
176
|
| | |
|
|
140
177
|
| --- | --- |
|
|
141
178
|
| Docs + API reference | https://www.unitpost.com/docs |
|
|
142
|
-
| Product
|
|
179
|
+
| Product guides | https://www.unitpost.com/guides |
|
|
180
|
+
| SMTP | https://www.unitpost.com/docs/smtp |
|
|
143
181
|
| OpenAPI JSON | https://www.unitpost.com/api/v1/openapi.json |
|
|
144
182
|
| LLM index | https://www.unitpost.com/llms.txt |
|
|
145
183
|
| Dashboard / API keys | https://www.unitpost.com |
|
|
@@ -147,7 +185,7 @@ unitpost = Unitpost(max_retries=2, retry_base_delay=0.5, max_retry_delay=20.0)
|
|
|
147
185
|
| Template gallery | https://www.unitpost.com/templates/gallery |
|
|
148
186
|
| Playground | https://www.unitpost.com/playground |
|
|
149
187
|
| Migration Assistant | https://www.unitpost.com/migration |
|
|
150
|
-
| MCP + Agent Skills | https://www.unitpost.com/
|
|
188
|
+
| MCP + Agent Skills | https://www.unitpost.com/docs/ai/mcp · https://www.unitpost.com/docs/ai/skills |
|
|
151
189
|
| Support | support@unitpost.com |
|
|
152
190
|
|
|
153
191
|
## License
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "unitpost"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
description = "Official Unitpost SDK for Python — send email and manage contacts, segments, campaigns, templates, brand kits, domains, suppressions, webhooks, and usage."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -29,7 +29,9 @@ from .resources import (
|
|
|
29
29
|
Contacts,
|
|
30
30
|
Email,
|
|
31
31
|
Segments,
|
|
32
|
+
Sms,
|
|
32
33
|
Suppressions,
|
|
34
|
+
Jobs,
|
|
33
35
|
Usage,
|
|
34
36
|
Webhooks,
|
|
35
37
|
)
|
|
@@ -62,6 +64,9 @@ class Unitpost:
|
|
|
62
64
|
)
|
|
63
65
|
self._http = http
|
|
64
66
|
self.email = Email(http)
|
|
67
|
+
# SMS channel. Available on paid plans: on the free plan `send` returns
|
|
68
|
+
# a 403 with code `limit_reached`; reads keep working.
|
|
69
|
+
self.sms = Sms(http)
|
|
65
70
|
self.contacts = Contacts(http)
|
|
66
71
|
self.contact_fields = ContactFields(http)
|
|
67
72
|
self.segments = Segments(http)
|
|
@@ -71,6 +76,7 @@ class Unitpost:
|
|
|
71
76
|
self.suppressions = Suppressions(http)
|
|
72
77
|
# NOTE (pre-launch): `events` is intentionally absent while the
|
|
73
78
|
# custom-events surface is behind the launch gate.
|
|
79
|
+
self.jobs = Jobs(http)
|
|
74
80
|
self.usage = Usage(http)
|
|
75
81
|
|
|
76
82
|
def close(self) -> None:
|