Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
b8057b2
refactor(sms): remove deprecated groups and inbound models
marcos-sinch May 28, 2026
69ee1ca
chore(update version): update version to 2.1.0
marcos-sinch May 28, 2026
f1898ec
feat(sms): add list and create groups
marcos-sinch May 28, 2026
4882538
feat(sms): add delete, list_members, update and replace groups
marcos-sinch May 29, 2026
25bd731
feat(sms): fix ruff format
marcos-sinch May 29, 2026
3de66e3
test(sms): add groups unit tests
marcos-sinch Jun 1, 2026
a8053e4
test(sms): add groups e2e tests
marcos-sinch Jun 1, 2026
de86292
test(sms): fix update group excludes none
marcos-sinch Jun 1, 2026
f808e08
feat(sms): fix kwargs in list_members and delete
marcos-sinch Jun 1, 2026
c27ff78
feat(sms): fix group list snippet
marcos-sinch Jun 2, 2026
a567fdc
Merge branch 'v2.1-next' into feature/DEVEXP-1324-sms-api-groups
marcos-sinch Jun 2, 2026
1ce96ac
feat(sms): fix group list snippet
marcos-sinch Jun 3, 2026
b94020d
feat(sms): address PR review comments
marcos-sinch Jun 4, 2026
4794003
feat(sms): address PR review comments
marcos-sinch Jun 4, 2026
e44635b
feat(sms): address PR review comments
marcos-sinch Jun 4, 2026
415bb4e
feat(sms): address PR review comments
marcos-sinch Jun 4, 2026
764a424
feat(sms): change list groups and members print on snippet
marcos-sinch Jun 4, 2026
db0a8ff
feat(sms): missing Groups in apis __init__
marcos-sinch Jun 5, 2026
e912020
feat(sms): delete strong typing in snippets
marcos-sinch Jun 5, 2026
0d15c22
feat(sms): update CHANGELOG.md and MIGRATION_GUIDE.md
marcos-sinch Jun 5, 2026
3fddd99
Merge branch 'v2.1-next' into feature/DEVEXP-1324-sms-api-groups
marcos-sinch Jun 5, 2026
092f023
feat(sms): update MIGRATION_GUIDE.md version
marcos-sinch Jun 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ jobs:
cp sinch-sdk-mockserver/features/numbers/numbers.feature ./tests/e2e/numbers/features/
cp sinch-sdk-mockserver/features/numbers/webhooks.feature ./tests/e2e/numbers/features/
cp sinch-sdk-mockserver/features/sms/delivery-reports.feature ./tests/e2e/sms/features/
cp sinch-sdk-mockserver/features/sms/groups.feature ./tests/e2e/sms/features/
cp sinch-sdk-mockserver/features/sms/groups_servicePlanId.feature ./tests/e2e/sms/features/
cp sinch-sdk-mockserver/features/sms/delivery-reports_servicePlanId.feature ./tests/e2e/sms/features/
cp sinch-sdk-mockserver/features/sms/batches.feature ./tests/e2e/sms/features/
cp sinch-sdk-mockserver/features/sms/batches_servicePlanId.feature ./tests/e2e/sms/features/
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ coverage.xml
.hypothesis/
.pytest_cache/
cover/
lcov.info

# E2E features
*.feature
Expand Down
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,17 @@ All notable changes to the **Sinch Python SDK** are documented in this file.

---

## v2.1.0 –
## v2.1.0 – 2026-06-05

### SDK

- **[dependency]** Set up minimum version for `requests` to `>=2.0.0` (#152).


### SMS

- **[feature]** SMS Groups API: `create`, `list`, `get`, `update`, `replace`, `delete`, and `list_members` operations, with full model, endpoint, and unit test coverage (see [MIGRATION_GUIDE.md](MIGRATION_GUIDE.md#groups-api)).

---

## v2.0.1 – 2026-06-02
Expand Down
39 changes: 37 additions & 2 deletions MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Sinch Python SDK Migration Guide

## 2.0.0
## 2.1.0

This release removes legacy SDK support.

Expand Down Expand Up @@ -207,7 +207,7 @@ The Conversation HTTP API still expects the JSON field **`callback_url`**. In V2

The SMS domain API access remains the same: `sinch.sms.batches` and `sinch.sms.delivery_reports`. However, the underlying models and method signatures have changed.

Note that `sinch.sms.groups` and `sinch.sms.inbounds` are not supported yet and will be available in future minor versions.
Note that `sinch.sms.inbounds` is not supported yet and will be available in a future minor version. `sinch.sms.groups` is now available — see [Groups API](#groups-api) below.

##### Batches API

Expand All @@ -230,6 +230,41 @@ Note that `sinch.sms.groups` and `sinch.sms.inbounds` are not supported yet and
| `get_for_batch()` with `GetSMSDeliveryReportForBatchRequest` | `get()` with `batch_id: str` and optional parameters: `report_type`, `status`, `code`, `client_reference` |
| `get_for_number()` with `GetSMSDeliveryReportForNumberRequest` | `get_for_number()` with `batch_id: str` and `recipient: str` parameters |

##### Groups API

> **Added in v2.1.0.** `sinch.sms.groups` is now fully supported.

###### Replacement models

| Old class | New class |
|-----------|-----------|
| `sinch.domains.sms.models.groups.requests.CreateSMSGroupRequest` | [`sinch.domains.sms.models.v1.internal.GroupRequest`](sinch/domains/sms/models/v1/internal/group_request.py) |
| `sinch.domains.sms.models.groups.requests.ListSMSGroupRequest` | [`sinch.domains.sms.models.v1.internal.ListGroupsRequest`](sinch/domains/sms/models/v1/internal/list_groups_request.py) |
| `sinch.domains.sms.models.groups.requests.GetSMSGroupRequest` | [`sinch.domains.sms.models.v1.internal.GroupIdRequest`](sinch/domains/sms/models/v1/internal/group_id_request.py) |
| `sinch.domains.sms.models.groups.requests.DeleteSMSGroupRequest` | [`sinch.domains.sms.models.v1.internal.GroupIdRequest`](sinch/domains/sms/models/v1/internal/group_id_request.py) |
| `sinch.domains.sms.models.groups.requests.GetSMSGroupPhoneNumbersRequest` | [`sinch.domains.sms.models.v1.internal.GroupIdRequest`](sinch/domains/sms/models/v1/internal/group_id_request.py) |
| `sinch.domains.sms.models.groups.requests.UpdateSMSGroupRequest` | [`sinch.domains.sms.models.v1.internal.UpdateGroupRequest`](sinch/domains/sms/models/v1/internal/update_group_request.py) |
| `sinch.domains.sms.models.groups.requests.ReplaceSMSGroupPhoneNumbersRequest` | [`sinch.domains.sms.models.v1.internal.ReplaceGroupRequest`](sinch/domains/sms/models/v1/internal/replace_group_request.py) |
| `sinch.domains.sms.models.groups.responses.CreateSMSGroupResponse` | [`sinch.domains.sms.models.v1.response.GroupResponse`](sinch/domains/sms/models/v1/response/group_response.py) |
| `sinch.domains.sms.models.groups.responses.GetSMSGroupResponse` | [`sinch.domains.sms.models.v1.response.GroupResponse`](sinch/domains/sms/models/v1/response/group_response.py) |
| `sinch.domains.sms.models.groups.responses.UpdateSMSGroupResponse` | [`sinch.domains.sms.models.v1.response.GroupResponse`](sinch/domains/sms/models/v1/response/group_response.py) |
| `sinch.domains.sms.models.groups.responses.ReplaceSMSGroupResponse` | [`sinch.domains.sms.models.v1.response.GroupResponse`](sinch/domains/sms/models/v1/response/group_response.py) |
| `sinch.domains.sms.models.groups.responses.SinchListSMSGroupResponse` | [`sinch.domains.sms.models.v1.response.ListGroupsResponse`](sinch/domains/sms/models/v1/response/list_groups_response.py) |
| `sinch.domains.sms.models.groups.responses.SinchGetSMSGroupPhoneNumbersResponse` | [`sinch.domains.sms.models.v1.response.ListGroupMembersResponse`](sinch/domains/sms/models/v1/response/list_group_members_response.py) |
| `sinch.domains.sms.models.groups.responses.SinchDeleteSMSGroupResponse` | `None` (method returns `None`) |

###### Replacement APIs

| Old method | New method in `sms.groups` |
|------------|---------------------------|
| `create()` with `CreateSMSGroupRequest` | `create()` with individual parameters: `name`, `members`, `child_groups`, `auto_update` |
| `list()` with `ListSMSGroupRequest` | `list()` with individual parameters: `page`, `page_size`. Returns **`Paginator[GroupResponse]`** |
| `get()` with `GetSMSGroupRequest` | `get()` with `group_id: str` parameter |
| `update()` with `UpdateSMSGroupRequest` | `update()` with `group_id: str` and optional parameters: `add`, `remove`, `name`, `add_from_group`, `remove_from_group`, `auto_update` |
| `replace()` with `ReplaceSMSGroupPhoneNumbersRequest` | `replace()` with `group_id: str` and optional parameters: `name`, `members`, `child_groups`, `auto_update` |
| `delete()` with `DeleteSMSGroupRequest` | `delete()` with `group_id: str` parameter |
| `get_phone_numbers()` / phone number listing | `list_members()` with `group_id: str`. Returns **`Paginator[str]`** |

---

### [`Numbers` (Virtual Numbers)](https://github.com/sinch/sinch-sdk-python/tree/main/sinch/domains/numbers)
Expand Down
26 changes: 26 additions & 0 deletions examples/snippets/sms/groups/create/snippet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""
Sinch Python Snippet

This snippet is available at https://github.com/sinch/sinch-sdk-python/tree/main/examples/snippets
"""

import os

from dotenv import load_dotenv

from sinch import SinchClient

load_dotenv()

sinch_client = SinchClient(
project_id=os.environ.get("SINCH_PROJECT_ID") or "MY_PROJECT_ID",
key_id=os.environ.get("SINCH_KEY_ID") or "MY_KEY_ID",
key_secret=os.environ.get("SINCH_KEY_SECRET") or "MY_KEY_SECRET",
sms_region=os.environ.get("SINCH_SMS_REGION") or "MY_SMS_REGION"
)

response = sinch_client.sms.groups.create(
name="Sinch Python SDK group", members=["+1234567890", "+1987654321"]
)

print(f"Group created:\n{response}")
27 changes: 27 additions & 0 deletions examples/snippets/sms/groups/delete/snippet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""
Sinch Python Snippet

This snippet is available at https://github.com/sinch/sinch-sdk-python/tree/main/examples/snippets
"""

import os

from dotenv import load_dotenv

from sinch import SinchClient

load_dotenv()

sinch_client = SinchClient(
project_id=os.environ.get("SINCH_PROJECT_ID") or "MY_PROJECT_ID",
key_id=os.environ.get("SINCH_KEY_ID") or "MY_KEY_ID",
key_secret=os.environ.get("SINCH_KEY_SECRET") or "MY_KEY_SECRET",
sms_region=os.environ.get("SINCH_SMS_REGION") or "MY_SMS_REGION"
)

# The ID of the group to delete
group_id = "GROUP_ID"

sinch_client.sms.groups.delete(group_id=group_id)

print(f"Group {group_id} deleted")
29 changes: 29 additions & 0 deletions examples/snippets/sms/groups/get/snippet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""
Sinch Python Snippet

This snippet is available at https://github.com/sinch/sinch-sdk-python/tree/main/examples/snippets
"""

import os

from dotenv import load_dotenv

from sinch import SinchClient

load_dotenv()

sinch_client = SinchClient(
project_id=os.environ.get("SINCH_PROJECT_ID") or "MY_PROJECT_ID",
key_id=os.environ.get("SINCH_KEY_ID") or "MY_KEY_ID",
key_secret=os.environ.get("SINCH_KEY_SECRET") or "MY_KEY_SECRET",
sms_region=os.environ.get("SINCH_SMS_REGION") or "MY_SMS_REGION"
)

# The ID of the group to retrieve
GROUP_ID = "GROUP_ID"
Comment thread
JPPortier marked this conversation as resolved.

response = sinch_client.sms.groups.get(
group_id=GROUP_ID
)

print(f"Group details:\n{response}")
26 changes: 26 additions & 0 deletions examples/snippets/sms/groups/list/snippet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""
Sinch Python Snippet

This snippet is available at https://github.com/sinch/sinch-sdk-python/tree/main/examples/snippets
"""

import os

from dotenv import load_dotenv

from sinch import SinchClient

load_dotenv()

sinch_client = SinchClient(
project_id=os.environ.get("SINCH_PROJECT_ID") or "MY_PROJECT_ID",
key_id=os.environ.get("SINCH_KEY_ID") or "MY_KEY_ID",
key_secret=os.environ.get("SINCH_KEY_SECRET") or "MY_KEY_SECRET",
sms_region=os.environ.get("SINCH_SMS_REGION") or "MY_SMS_REGION"
)

groups = sinch_client.sms.groups.list()

print("List of groups:\n")
for group in groups.iterator():
print(group)
30 changes: 30 additions & 0 deletions examples/snippets/sms/groups/list_members/snippet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""
Sinch Python Snippet

This snippet is available at https://github.com/sinch/sinch-sdk-python/tree/main/examples/snippets
"""

import os
from typing import List

from dotenv import load_dotenv

from sinch import SinchClient

load_dotenv()

sinch_client = SinchClient(
project_id=os.environ.get("SINCH_PROJECT_ID") or "MY_PROJECT_ID",
key_id=os.environ.get("SINCH_KEY_ID") or "MY_KEY_ID",
key_secret=os.environ.get("SINCH_KEY_SECRET") or "MY_KEY_SECRET",
sms_region=os.environ.get("SINCH_SMS_REGION") or "MY_SMS_REGION"
)

# The ID of the group to list members for
group_id = "GROUP_ID"

members = sinch_client.sms.groups.list_members(group_id=group_id)

print("List of members:\n")
for member in members.iterator():
print(member)
30 changes: 30 additions & 0 deletions examples/snippets/sms/groups/replace/snippet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""
Sinch Python Snippet

This snippet is available at https://github.com/sinch/sinch-sdk-python/tree/main/examples/snippets
"""

import os

from dotenv import load_dotenv

from sinch import SinchClient

load_dotenv()

sinch_client = SinchClient(
project_id=os.environ.get("SINCH_PROJECT_ID") or "MY_PROJECT_ID",
key_id=os.environ.get("SINCH_KEY_ID") or "MY_KEY_ID",
key_secret=os.environ.get("SINCH_KEY_SECRET") or "MY_KEY_SECRET",
sms_region=os.environ.get("SINCH_SMS_REGION") or "MY_SMS_REGION"
)

# The ID of the group to replace
group_id = "GROUP_ID"

response = sinch_client.sms.groups.replace(
group_id=group_id,
members=["+1234567890", "+1987654321"],
)

print(f"Group replaced:\n{response}")
32 changes: 32 additions & 0 deletions examples/snippets/sms/groups/update/snippet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""
Sinch Python Snippet

This snippet is available at https://github.com/sinch/sinch-sdk-python/tree/main/examples/snippets
"""

import os

from dotenv import load_dotenv

from sinch import SinchClient

load_dotenv()

sinch_client = SinchClient(
project_id=os.environ.get("SINCH_PROJECT_ID") or "MY_PROJECT_ID",
key_id=os.environ.get("SINCH_KEY_ID") or "MY_KEY_ID",
key_secret=os.environ.get("SINCH_KEY_SECRET") or "MY_KEY_SECRET",
sms_region=os.environ.get("SINCH_SMS_REGION") or "MY_SMS_REGION"
)

# The ID of the group to update
group_id = "GROUP_ID"

response = sinch_client.sms.groups.update(
group_id=group_id,
add=["+1234567890"],
remove=["+1987654321"],
name="Renamed Group",
)

print(f"Group updated:\n{response}")
2 changes: 1 addition & 1 deletion sinch/core/adapters/requests_http_transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def send(self, endpoint: HTTPEndpoint) -> HTTPResponse:

self.sinch.configuration.logger.debug(
f"Sync HTTP {request_data.http_method} call with headers:"
f" {request_data.headers} and body: {request_data.request_body} to URL: {request_data.url}"
f" {request_data.headers}, body: {request_data.request_body} and query_params: {request_data.query_params} to URL: {request_data.url}"
)
response = self.http_session.request(
method=request_data.http_method,
Expand Down
2 changes: 2 additions & 0 deletions sinch/domains/sms/api/v1/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from sinch.domains.sms.api.v1.batches_apis import Batches
from sinch.domains.sms.api.v1.delivery_reports_apis import DeliveryReports
from sinch.domains.sms.api.v1.groups_apis import Groups

__all__ = [
"Batches",
"DeliveryReports",
"Groups",
]
Loading
Loading