sendly 4.0.0__tar.gz → 4.1.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.
- {sendly-4.0.0 → sendly-4.1.0}/CHANGELOG.md +11 -0
- {sendly-4.0.0 → sendly-4.1.0}/PKG-INFO +84 -1
- {sendly-4.0.0 → sendly-4.1.0}/README.md +83 -0
- {sendly-4.0.0 → sendly-4.1.0}/pyproject.toml +1 -1
- {sendly-4.0.0 → sendly-4.1.0}/sendly/__init__.py +33 -1
- {sendly-4.0.0 → sendly-4.1.0}/sendly/client.py +3 -0
- {sendly-4.0.0 → sendly-4.1.0}/sendly/resources/__init__.py +3 -0
- sendly-4.1.0/sendly/resources/calls.py +360 -0
- {sendly-4.0.0 → sendly-4.1.0}/sendly/types.py +228 -0
- {sendly-4.0.0 → sendly-4.1.0}/sendly/utils/http.py +1 -1
- sendly-4.1.0/tests/test_calls.py +1047 -0
- {sendly-4.0.0 → sendly-4.1.0}/tests/test_messages.py +23 -1
- {sendly-4.0.0 → sendly-4.1.0}/.github/header.svg +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/.gitignore +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/examples/async_example.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/examples/error_handling.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/examples/send_sms.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/sendly/errors.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/sendly/py.typed +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/sendly/resources/account.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/sendly/resources/business_upgrade.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/sendly/resources/campaigns.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/sendly/resources/contacts.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/sendly/resources/conversations.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/sendly/resources/drafts.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/sendly/resources/enterprise.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/sendly/resources/labels.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/sendly/resources/links.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/sendly/resources/media.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/sendly/resources/messages.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/sendly/resources/numbers.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/sendly/resources/rcs.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/sendly/resources/rules.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/sendly/resources/templates.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/sendly/resources/tendlc.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/sendly/resources/verify.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/sendly/resources/webhooks.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/sendly/resources/whatsapp.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/sendly/utils/__init__.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/sendly/utils/validation.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/sendly/webhooks.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/tests/conftest.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/tests/test_account.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/tests/test_async_client.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/tests/test_client.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/tests/test_errors.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/tests/test_http_body_kwarg.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/tests/test_idempotency.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/tests/test_messages.py.bak +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/tests/test_messages_batch.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/tests/test_messages_schedule.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/tests/test_numbers.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/tests/test_rcs.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/tests/test_rcs_registration.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/tests/test_tendlc.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/tests/test_validation.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/tests/test_webhooks.py +0 -0
- {sendly-4.0.0 → sendly-4.1.0}/tests/test_whatsapp.py +0 -0
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# sendly (Python)
|
|
2
2
|
|
|
3
|
+
## 4.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- **Voice calls: `client.calls`.** `create(to, agent_id, from_=..., context=..., metadata=...)` places a phone call that one of your workspace's AI agents handles; `list(...)`, `get(id)`, `hangup(id)` and `recording(id)` follow it, end it early and fetch the recording. Sync and async clients. Reads need the `calls:read` scope, writes `calls:write` and a live API key; until voice is enabled for your workspace the routes answer 404 `voice_not_enabled`.
|
|
8
|
+
- `voice_enabled` and `voice_mode` on owned numbers.
|
|
9
|
+
|
|
10
|
+
### Patch Changes
|
|
11
|
+
|
|
12
|
+
- `MessageStatus` gains `RECEIVED` and `UNDELIVERED`. `messages.list()` returns inbound rows by default and their status is `received`, which the strict enum rejected, so listing failed for any workspace with inbound traffic. Any status this version does not know now maps to `MessageStatus.UNKNOWN` instead of raising.
|
|
13
|
+
|
|
3
14
|
## 4.0.0
|
|
4
15
|
|
|
5
16
|
**Upgrading from 3.40.0:** that release already contained the breaking changes below, published by mistake as a minor version. 4.0.0 carries them under the correct major. Relative to 3.40.0, the only new changes are under **Security**.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sendly
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.1.0
|
|
4
4
|
Summary: Official Sendly Python SDK for SMS messaging
|
|
5
5
|
Project-URL: Homepage, https://sendly.live
|
|
6
6
|
Project-URL: Documentation, https://sendly.live/docs
|
|
@@ -913,6 +913,67 @@ Writes accept an `idempotency_key` like the other write methods; `submit()` and
|
|
|
913
913
|
`request_launch()` replays return the original response without notifying the
|
|
914
914
|
reviewers again.
|
|
915
915
|
|
|
916
|
+
## Voice Calls
|
|
917
|
+
|
|
918
|
+
Place phone calls that one of your AI agents handles, follow them while they
|
|
919
|
+
ring and after they end, end them early, and download recordings. Requires an
|
|
920
|
+
API key with the `calls:read` / `calls:write` scopes; placing and ending calls
|
|
921
|
+
needs a live key. Voice is enabled workspace by workspace: until it is enabled
|
|
922
|
+
for yours, every call method raises `SendlyError` with code `voice_not_enabled`
|
|
923
|
+
(HTTP 404).
|
|
924
|
+
|
|
925
|
+
Calls are prepaid from your balance per started minute: 2 credits a minute
|
|
926
|
+
outbound plus 8 a minute while an AI agent is on the line (10 credits a minute
|
|
927
|
+
for an agent-handled outbound call); unanswered calls cost nothing. The number
|
|
928
|
+
you call from must be voice-enabled in the dashboard (Calls, then Settings),
|
|
929
|
+
with an emergency address registered; `client.numbers.list()` shows each
|
|
930
|
+
number's `voice_enabled` and `voice_mode`. Destinations are US and Canada.
|
|
931
|
+
|
|
932
|
+
```python
|
|
933
|
+
# Pick a voice-enabled number (optional when exactly one is voice-enabled)
|
|
934
|
+
voice_numbers = [n for n in client.numbers.list().numbers if n.voice_enabled]
|
|
935
|
+
|
|
936
|
+
# Place a call; the agent speaks first, using the context you pass
|
|
937
|
+
call = client.calls.create(
|
|
938
|
+
to='+15555550123',
|
|
939
|
+
agent_id='3c4d5e6f-7081-4293-a4b5-c6d7e8f90a1b',
|
|
940
|
+
from_=voice_numbers[0].phone_number,
|
|
941
|
+
context='You are calling Jordan to confirm the 3pm appointment on Tuesday.',
|
|
942
|
+
metadata={'crmId': 'lead_8812'},
|
|
943
|
+
)
|
|
944
|
+
print(call.id, call.status) # ... 'ringing'
|
|
945
|
+
|
|
946
|
+
# Follow it: status moves ringing -> active -> completed (or no_answer, busy, ...)
|
|
947
|
+
call = client.calls.get(call.id)
|
|
948
|
+
print(call.status, call.duration_secs, call.credits_charged, call.hangup_class)
|
|
949
|
+
for line in call.transcript or []: # agent calls only; None on other calls
|
|
950
|
+
print(f'[{line.at_ms} ms] {line.speaker}: {line.text}')
|
|
951
|
+
|
|
952
|
+
# List calls, newest first, with filters and paging
|
|
953
|
+
page = client.calls.list(status='completed', direction='outbound', limit=20)
|
|
954
|
+
for c in page.data:
|
|
955
|
+
print(c.id, c.to, c.duration_secs, c.credits_charged)
|
|
956
|
+
if page.pagination.has_more:
|
|
957
|
+
page = client.calls.list(status='completed', direction='outbound', limit=20, offset=20)
|
|
958
|
+
|
|
959
|
+
# End a call early (idempotent: an ended call is returned unchanged)
|
|
960
|
+
client.calls.hangup(call.id)
|
|
961
|
+
|
|
962
|
+
# Fetch the recording; the signed URL is valid for 5 minutes
|
|
963
|
+
recording = client.calls.recording(call.id)
|
|
964
|
+
if recording.status == 'ready':
|
|
965
|
+
print(recording.url, recording.expires_at, recording.content_type) # audio/ogg
|
|
966
|
+
```
|
|
967
|
+
|
|
968
|
+
Refusals come back as `SendlyError` with a code that says what to do:
|
|
969
|
+
`insufficient_credits` (raised as `InsufficientCreditsError`; top up),
|
|
970
|
+
`e911_required` (register an emergency address for the number), `lines_busy`
|
|
971
|
+
(retry with backoff), `daily_call_limit` (try again tomorrow),
|
|
972
|
+
`agent_not_found` / `agent_disabled`, `from_number_required` / `no_voice_number`,
|
|
973
|
+
`destination_not_supported`, `live_key_required`. Webhooks `call.started`,
|
|
974
|
+
`call.completed` and `call.recording.ready` carry the same call object in
|
|
975
|
+
snake_case, including `billing` and your `metadata`.
|
|
976
|
+
|
|
916
977
|
## Error Handling
|
|
917
978
|
|
|
918
979
|
The SDK provides typed exception classes:
|
|
@@ -1207,6 +1268,28 @@ Submit the agent and its brand for review by Sendly, then the carrier network. `
|
|
|
1207
1268
|
|
|
1208
1269
|
Ask for the launch review once the agent is in `testing` and has been tried on an invited device.
|
|
1209
1270
|
|
|
1271
|
+
### `client.calls`
|
|
1272
|
+
|
|
1273
|
+
#### `create(to, agent_id, from_=None, context=None, metadata=None, idempotency_key=None) -> Call`
|
|
1274
|
+
|
|
1275
|
+
Place a phone call handled by an AI agent (live key, `calls:write`). Returns the call while it is `ringing`; `from_` is required when more than one number is voice-enabled.
|
|
1276
|
+
|
|
1277
|
+
#### `list(limit=None, offset=None, status=None, direction=None, kind=None, agent_id=None, to=None, from_=None) -> CallListResponse`
|
|
1278
|
+
|
|
1279
|
+
List calls newest first; `pagination.has_more` says whether another page exists.
|
|
1280
|
+
|
|
1281
|
+
#### `get(id) -> Call`
|
|
1282
|
+
|
|
1283
|
+
Fetch one call. Agent-handled calls carry `transcript`; on other calls it is `None`.
|
|
1284
|
+
|
|
1285
|
+
#### `hangup(id, idempotency_key=None) -> Call`
|
|
1286
|
+
|
|
1287
|
+
End a call (live key, `calls:write`). `ringing` becomes `cancelled`, `active` becomes `completed`; an ended call is returned unchanged.
|
|
1288
|
+
|
|
1289
|
+
#### `recording(id) -> CallRecording`
|
|
1290
|
+
|
|
1291
|
+
Where to download the recording. `url` and `expires_at` are set only while `status` is `ready`; the link is valid for 5 minutes.
|
|
1292
|
+
|
|
1210
1293
|
## Enterprise
|
|
1211
1294
|
|
|
1212
1295
|
The Enterprise API lets you programmatically manage workspaces, verification, credits, and API keys for multi-tenant platforms. Requires an enterprise master key (`sk_live_v1_master_*`).
|
|
@@ -878,6 +878,67 @@ Writes accept an `idempotency_key` like the other write methods; `submit()` and
|
|
|
878
878
|
`request_launch()` replays return the original response without notifying the
|
|
879
879
|
reviewers again.
|
|
880
880
|
|
|
881
|
+
## Voice Calls
|
|
882
|
+
|
|
883
|
+
Place phone calls that one of your AI agents handles, follow them while they
|
|
884
|
+
ring and after they end, end them early, and download recordings. Requires an
|
|
885
|
+
API key with the `calls:read` / `calls:write` scopes; placing and ending calls
|
|
886
|
+
needs a live key. Voice is enabled workspace by workspace: until it is enabled
|
|
887
|
+
for yours, every call method raises `SendlyError` with code `voice_not_enabled`
|
|
888
|
+
(HTTP 404).
|
|
889
|
+
|
|
890
|
+
Calls are prepaid from your balance per started minute: 2 credits a minute
|
|
891
|
+
outbound plus 8 a minute while an AI agent is on the line (10 credits a minute
|
|
892
|
+
for an agent-handled outbound call); unanswered calls cost nothing. The number
|
|
893
|
+
you call from must be voice-enabled in the dashboard (Calls, then Settings),
|
|
894
|
+
with an emergency address registered; `client.numbers.list()` shows each
|
|
895
|
+
number's `voice_enabled` and `voice_mode`. Destinations are US and Canada.
|
|
896
|
+
|
|
897
|
+
```python
|
|
898
|
+
# Pick a voice-enabled number (optional when exactly one is voice-enabled)
|
|
899
|
+
voice_numbers = [n for n in client.numbers.list().numbers if n.voice_enabled]
|
|
900
|
+
|
|
901
|
+
# Place a call; the agent speaks first, using the context you pass
|
|
902
|
+
call = client.calls.create(
|
|
903
|
+
to='+15555550123',
|
|
904
|
+
agent_id='3c4d5e6f-7081-4293-a4b5-c6d7e8f90a1b',
|
|
905
|
+
from_=voice_numbers[0].phone_number,
|
|
906
|
+
context='You are calling Jordan to confirm the 3pm appointment on Tuesday.',
|
|
907
|
+
metadata={'crmId': 'lead_8812'},
|
|
908
|
+
)
|
|
909
|
+
print(call.id, call.status) # ... 'ringing'
|
|
910
|
+
|
|
911
|
+
# Follow it: status moves ringing -> active -> completed (or no_answer, busy, ...)
|
|
912
|
+
call = client.calls.get(call.id)
|
|
913
|
+
print(call.status, call.duration_secs, call.credits_charged, call.hangup_class)
|
|
914
|
+
for line in call.transcript or []: # agent calls only; None on other calls
|
|
915
|
+
print(f'[{line.at_ms} ms] {line.speaker}: {line.text}')
|
|
916
|
+
|
|
917
|
+
# List calls, newest first, with filters and paging
|
|
918
|
+
page = client.calls.list(status='completed', direction='outbound', limit=20)
|
|
919
|
+
for c in page.data:
|
|
920
|
+
print(c.id, c.to, c.duration_secs, c.credits_charged)
|
|
921
|
+
if page.pagination.has_more:
|
|
922
|
+
page = client.calls.list(status='completed', direction='outbound', limit=20, offset=20)
|
|
923
|
+
|
|
924
|
+
# End a call early (idempotent: an ended call is returned unchanged)
|
|
925
|
+
client.calls.hangup(call.id)
|
|
926
|
+
|
|
927
|
+
# Fetch the recording; the signed URL is valid for 5 minutes
|
|
928
|
+
recording = client.calls.recording(call.id)
|
|
929
|
+
if recording.status == 'ready':
|
|
930
|
+
print(recording.url, recording.expires_at, recording.content_type) # audio/ogg
|
|
931
|
+
```
|
|
932
|
+
|
|
933
|
+
Refusals come back as `SendlyError` with a code that says what to do:
|
|
934
|
+
`insufficient_credits` (raised as `InsufficientCreditsError`; top up),
|
|
935
|
+
`e911_required` (register an emergency address for the number), `lines_busy`
|
|
936
|
+
(retry with backoff), `daily_call_limit` (try again tomorrow),
|
|
937
|
+
`agent_not_found` / `agent_disabled`, `from_number_required` / `no_voice_number`,
|
|
938
|
+
`destination_not_supported`, `live_key_required`. Webhooks `call.started`,
|
|
939
|
+
`call.completed` and `call.recording.ready` carry the same call object in
|
|
940
|
+
snake_case, including `billing` and your `metadata`.
|
|
941
|
+
|
|
881
942
|
## Error Handling
|
|
882
943
|
|
|
883
944
|
The SDK provides typed exception classes:
|
|
@@ -1172,6 +1233,28 @@ Submit the agent and its brand for review by Sendly, then the carrier network. `
|
|
|
1172
1233
|
|
|
1173
1234
|
Ask for the launch review once the agent is in `testing` and has been tried on an invited device.
|
|
1174
1235
|
|
|
1236
|
+
### `client.calls`
|
|
1237
|
+
|
|
1238
|
+
#### `create(to, agent_id, from_=None, context=None, metadata=None, idempotency_key=None) -> Call`
|
|
1239
|
+
|
|
1240
|
+
Place a phone call handled by an AI agent (live key, `calls:write`). Returns the call while it is `ringing`; `from_` is required when more than one number is voice-enabled.
|
|
1241
|
+
|
|
1242
|
+
#### `list(limit=None, offset=None, status=None, direction=None, kind=None, agent_id=None, to=None, from_=None) -> CallListResponse`
|
|
1243
|
+
|
|
1244
|
+
List calls newest first; `pagination.has_more` says whether another page exists.
|
|
1245
|
+
|
|
1246
|
+
#### `get(id) -> Call`
|
|
1247
|
+
|
|
1248
|
+
Fetch one call. Agent-handled calls carry `transcript`; on other calls it is `None`.
|
|
1249
|
+
|
|
1250
|
+
#### `hangup(id, idempotency_key=None) -> Call`
|
|
1251
|
+
|
|
1252
|
+
End a call (live key, `calls:write`). `ringing` becomes `cancelled`, `active` becomes `completed`; an ended call is returned unchanged.
|
|
1253
|
+
|
|
1254
|
+
#### `recording(id) -> CallRecording`
|
|
1255
|
+
|
|
1256
|
+
Where to download the recording. `url` and `expires_at` are set only while `status` is `ready`; the link is valid for 5 minutes.
|
|
1257
|
+
|
|
1175
1258
|
## Enterprise
|
|
1176
1259
|
|
|
1177
1260
|
The Enterprise API lets you programmatically manage workspaces, verification, credits, and API keys for multi-tenant platforms. Requires an enterprise master key (`sk_live_v1_master_*`).
|
|
@@ -29,7 +29,7 @@ Async Example:
|
|
|
29
29
|
>>> asyncio.run(main())
|
|
30
30
|
"""
|
|
31
31
|
|
|
32
|
-
__version__ = "4.
|
|
32
|
+
__version__ = "4.1.0"
|
|
33
33
|
|
|
34
34
|
# Main clients
|
|
35
35
|
from .client import AsyncSendly, Sendly
|
|
@@ -51,6 +51,7 @@ from .resources.business_upgrade import (
|
|
|
51
51
|
AsyncBusinessUpgradeResource,
|
|
52
52
|
BusinessUpgradeResource,
|
|
53
53
|
)
|
|
54
|
+
from .resources.calls import AsyncCallsResource, CallsResource
|
|
54
55
|
from .resources.links import AsyncLinksResource, LinksResource
|
|
55
56
|
from .resources.media import AsyncMediaResource, MediaResource
|
|
56
57
|
from .resources.numbers import AsyncNumbersResource, NumbersResource
|
|
@@ -194,6 +195,20 @@ from .types import (
|
|
|
194
195
|
RcsTestDeviceInput,
|
|
195
196
|
RcsTestDeviceListResponse,
|
|
196
197
|
RcsTesting,
|
|
198
|
+
# Voice call types
|
|
199
|
+
Call,
|
|
200
|
+
CallBilling,
|
|
201
|
+
CallDirection,
|
|
202
|
+
CallHandledBy,
|
|
203
|
+
CallKind,
|
|
204
|
+
CallListResponse,
|
|
205
|
+
CallPagination,
|
|
206
|
+
CallRecording,
|
|
207
|
+
CallRecordingStatus,
|
|
208
|
+
CallStatus,
|
|
209
|
+
CallTranscriptLine,
|
|
210
|
+
CreateCallRequest,
|
|
211
|
+
ListCallsOptions,
|
|
197
212
|
OptInPage,
|
|
198
213
|
PricingTier,
|
|
199
214
|
QuotaSettings,
|
|
@@ -406,6 +421,20 @@ __all__ = [
|
|
|
406
421
|
"RcsTestDeviceInput",
|
|
407
422
|
"RcsTestDeviceListResponse",
|
|
408
423
|
"RcsTesting",
|
|
424
|
+
# Voice call types
|
|
425
|
+
"Call",
|
|
426
|
+
"CallBilling",
|
|
427
|
+
"CallDirection",
|
|
428
|
+
"CallHandledBy",
|
|
429
|
+
"CallKind",
|
|
430
|
+
"CallListResponse",
|
|
431
|
+
"CallPagination",
|
|
432
|
+
"CallRecording",
|
|
433
|
+
"CallRecordingStatus",
|
|
434
|
+
"CallStatus",
|
|
435
|
+
"CallTranscriptLine",
|
|
436
|
+
"CreateCallRequest",
|
|
437
|
+
"ListCallsOptions",
|
|
409
438
|
# Links resources
|
|
410
439
|
"LinksResource",
|
|
411
440
|
"AsyncLinksResource",
|
|
@@ -427,6 +456,9 @@ __all__ = [
|
|
|
427
456
|
# RCS resources
|
|
428
457
|
"RcsResource",
|
|
429
458
|
"AsyncRcsResource",
|
|
459
|
+
# Voice call resources
|
|
460
|
+
"CallsResource",
|
|
461
|
+
"AsyncCallsResource",
|
|
430
462
|
# Errors
|
|
431
463
|
"SendlyError",
|
|
432
464
|
"AuthenticationError",
|
|
@@ -11,6 +11,7 @@ from .resources.business_upgrade import (
|
|
|
11
11
|
AsyncBusinessUpgradeResource,
|
|
12
12
|
BusinessUpgradeResource,
|
|
13
13
|
)
|
|
14
|
+
from .resources.calls import AsyncCallsResource, CallsResource
|
|
14
15
|
from .resources.campaigns import AsyncCampaignsResource, CampaignsResource
|
|
15
16
|
from .resources.contacts import AsyncContactsResource, ContactsResource
|
|
16
17
|
from .resources.conversations import AsyncConversationsResource, ConversationsResource
|
|
@@ -132,6 +133,7 @@ class Sendly:
|
|
|
132
133
|
self.business_upgrade = BusinessUpgradeResource(self._http)
|
|
133
134
|
self.whatsapp = WhatsAppResource(self._http)
|
|
134
135
|
self.rcs = RcsResource(self._http)
|
|
136
|
+
self.calls = CallsResource(self._http)
|
|
135
137
|
|
|
136
138
|
def __enter__(self) -> "Sendly":
|
|
137
139
|
return self
|
|
@@ -275,6 +277,7 @@ class AsyncSendly:
|
|
|
275
277
|
self.business_upgrade = AsyncBusinessUpgradeResource(self._http)
|
|
276
278
|
self.whatsapp = AsyncWhatsAppResource(self._http)
|
|
277
279
|
self.rcs = AsyncRcsResource(self._http)
|
|
280
|
+
self.calls = AsyncCallsResource(self._http)
|
|
278
281
|
|
|
279
282
|
async def __aenter__(self) -> "AsyncSendly":
|
|
280
283
|
return self
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Sendly SDK Resources"""
|
|
2
2
|
|
|
3
|
+
from .calls import AsyncCallsResource, CallsResource
|
|
3
4
|
from .campaigns import AsyncCampaignsResource, CampaignsResource
|
|
4
5
|
from .contacts import AsyncContactsResource, ContactsResource
|
|
5
6
|
from .conversations import AsyncConversationsResource, ConversationsResource
|
|
@@ -17,6 +18,8 @@ from .verify import AsyncVerifyResource, VerifyResource
|
|
|
17
18
|
from .whatsapp import AsyncWhatsAppResource, WhatsAppResource
|
|
18
19
|
|
|
19
20
|
__all__ = [
|
|
21
|
+
"CallsResource",
|
|
22
|
+
"AsyncCallsResource",
|
|
20
23
|
"CampaignsResource",
|
|
21
24
|
"AsyncCampaignsResource",
|
|
22
25
|
"ContactsResource",
|