Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 1 addition & 1 deletion docs/hub/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
| [`internal`](./internal.md) | 1 |
| [`logs`](./logs.md) | 9 |
| [`membership`](./membership.md) | 25 |
| [`notifications`](./notifications.md) | 68 |
| [`notifications`](./notifications.md) | 117 |
| [`payments`](./payments.md) | 16 |
| [`scheduler`](./scheduler.md) | 8 |
| [`webhooks`](./webhooks.md) | 8 |
42 changes: 39 additions & 3 deletions docs/hub/notifications.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,41 @@ Access with `norbix.hub.notifications`.
| `preview_email_notification` | `GET` | `/{version}/notifications/email/preview` | `optional` |
| `get_email_campaign_message` | `GET` | `/{version}/notifications/emails/campaigns/{campaignId}/messages/{id}` | `project` |
| `get_email_campaign_messages` | `GET` | `/{version}/notifications/emails/campaigns/{campaignId}/messages` | `project` |
| `disable_sms` | `GET` | `/{version}/notifications/sms/disable` | `project` |
| `enable_sms` | `GET` | `/{version}/notifications/sms/enable` | `project` |
| `get_sms_disable_dependencies` | `GET` | `/{version}/notifications/sms/disable-dependencies` | `project` |
| `get_sms_settings` | `GET` | `/{version}/notifications/sms/settings` | `project` |
| `get_sms_integrations` | `GET` | `/{version}/notifications/sms/integrations` | `project` |
| `save_sms_integration` | `POST` | `/{version}/notifications/sms/integrations` | `project` |
| `get_sms_integration` | `GET` | `/{version}/notifications/sms/integrations/{id}` | `project` |
| `enable_sms_integration` | `PUT` | `/{version}/notifications/sms/integrations/{Id}/enable` | `project` |
| `disable_sms_integration` | `PUT` | `/{version}/notifications/sms/integrations/{Id}/disable` | `project` |
| `set_sms_integration_as_default` | `PUT` | `/{version}/notifications/sms/integrations/{Id}/default` | `project` |
| `delete_sms_integration` | `DELETE` | `/{version}/notifications/sms/integrations/{Id}` | `project` |
| `test_sms_integration` | `POST` | `/{version}/notifications/sms/integrations/test` | `project` |
| `confirm_sms_integration_human_delivery` | `POST` | `/{version}/notifications/sms/integrations/confirm-human-delivery` | `project` |
| `get_sms_templates` | `GET` | `/{version}/notifications/sms/templates` | `project` |
| `create_sms_template` | `POST` | `/{version}/notifications/sms/templates` | `project` |
| `update_sms_template` | `PUT` | `/{version}/notifications/sms/templates` | `project` |
| `get_sms_template` | `GET` | `/{version}/notifications/sms/templates/{id}` | `project` |
| `delete_sms_template` | `DELETE` | `/{version}/notifications/sms/templates/{Id}` | `project` |
| `archive_sms_template` | `PUT` | `/{version}/notifications/sms/templates/{Id}/archive` | `project` |
| `un_archive_sms_template` | `PUT` | `/{version}/notifications/sms/templates/{Id}/unarchive` | `project` |
| `clone_sms_template` | `POST` | `/{version}/notifications/sms/templates/{Id}/clone` | `project` |
| `get_sms_message_content_tokens` | `GET` | `/{version}/notifications/sms/templates/{id}/tokens` | `project` |
| `render_sms` | `POST` | `/{version}/notifications/sms/templates/render` | `project` |
| `get_sms_campaigns` | `GET` | `/{version}/notifications/sms/campaigns` | `project` |
| `create_sms_campaign` | `POST` | `/{version}/notifications/sms/campaigns` | `project` |
| `get_sms_campaign` | `GET` | `/{version}/notifications/sms/campaigns/{id}` | `project` |
| `delete_sms_campaign` | `DELETE` | `/{version}/notifications/sms/campaigns/{id}` | `project` |
| `stop_sms_campaign` | `POST` | `/{version}/notifications/sms/campaigns/{Id}/stop` | `project` |
| `get_sms_campaign_batches` | `GET` | `/{version}/notifications/sms/campaigns/{id}/batches` | `project` |
| `get_sms_campaign_batch_notifications` | `GET` | `/{version}/notifications/sms/campaigns/{id}/batches/{batchId}` | `project` |
| `get_sms_campaign_batch_notification` | `GET` | `/{version}/notifications/sms/campaigns/{id}/batches/{batchId}/{notificationId}` | `project` |
| `get_sms_campaign_statistics` | `GET` | `/{version}/notifications/sms/campaigns/{id}/stats` | `project` |
| `get_sms_campaign_messages` | `GET` | `/{version}/notifications/sms/campaigns/{campaignId}/messages` | `project` |
| `get_sms_campaign_message` | `GET` | `/{version}/notifications/sms/campaigns/{campaignId}/messages/{notificationId}` | `project` |
| `preview_sms_notification` | `GET` | `/{version}/notifications/sms/preview` | `optional` |
| `disable_push` | `GET` | `/{version}/notifications/push/disable` | `project` |
| `enable_push` | `GET` | `/{version}/notifications/push/enable` | `project` |
| `archive_push_template` | `PUT` | `/{version}/notifications/push/templates/{Id}/archive` | `project` |
Expand Down Expand Up @@ -87,6 +122,7 @@ Access with `norbix.hub.notifications`.
| `get_push_campaign_message` | `GET` | `/{version}/notifications/push/campaigns/{campaignId}/messages/{id}` | `project` |
| `preview_push_notification` | `GET` | `/{version}/notifications/push/preview` | `optional` |

Every push method above has an async twin on `client.hub.notifications` when
the client is an `AsyncNorbix`. See [push.md](./push.md) for how to pick a
campaign audience and a push provider.
Every method above has an async twin on `client.hub.notifications` when the
client is an `AsyncNorbix`. See [push.md](./push.md) for how to pick a push
campaign audience and a push provider, and [sms.md](./sms.md) for the same two
choices on an SMS campaign.
108 changes: 108 additions & 0 deletions docs/hub/sms.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# SMS — choosing an audience and a provider

[← Back to the notifications reference](./notifications.md)

`notifications.md` lists every SMS method with its verb, path and scope. Two of
them take a body whose shape the server picks from a value inside that body,
which the signature cannot show. This page covers those two, plus the three
calls whose required values are not obvious from the route.

Everything here is asserted in `tests/hub/test_sms.py`.

## Choosing who a campaign goes to

`create_sms_campaign` reads the audience from `deliveryType` and then the one
settings object that matches it. The request is flat (no `campaign` wrapper),
and the settings object repeats the audience name in `recipientsSourceType`:

| audience | `deliveryType` | settings key | own fields |
|---|---|---|---|
| everyone in the project subscribed to SMS | `AllUsers` | `allUsers` | `rolesNames`, `userTags` (both optional filters) |
| a named list of project members | `SpecifiedUsers` | `specifiedUsers` | `recipients` (member ids) |
| rows of a database collection | `Collection` | `collection` | `schemaName`, `fields` (the record fields that hold the recipient), `fieldType` (`User` or `Email`), optional `roleNames`, `languages` |
| raw phone numbers | `PhoneNumbers` | `phoneNumbers` | `phoneNumbers` (international format, `+370…`) |

Every settings object also takes `campaignTime` (Unix seconds, UTC — set a
future time so the campaign can be reviewed in the dashboard first), and the
optional `mappedTokens` and `respectTimeZoneSettings`. The request itself takes
`templateId` (required), and the optional `language` and `databaseIntegrationId`.
Note the spelling: `rolesNames` on `allUsers`, but `roleNames` on `collection` —
the gateway names them differently.

```python
client.hub.notifications.create_sms_campaign(
templateId="tpl_123",
deliveryType="PhoneNumbers",
phoneNumbers={
"recipientsSourceType": "PhoneNumbers",
"phoneNumbers": ["+37060000000"],
"campaignTime": 1_900_000_000,
},
)
```

Send `deliveryType` as the name, not a number — the server reads it as a
string. The generated enum also lists `AccountUsers`; the create request has no
settings object for it, so the gateway refuses it.

A scheduled campaign can be cancelled with `stop_sms_campaign(id=...)`; it posts
to `…/campaigns/{id}/stop` with no body.

## Choosing an SMS provider

`save_sms_integration` works the same way, with `integration["provider"]`:

| provider | `provider` value | own fields |
|---|---|---|
| Fake (sandbox, never sends) | `Fake` | none |
| Twilio | `Twilio` | `accountSid`, `fromPhoneNumber`, `authToken` |
| Vonage | `Vonage` | `apiKey`, `fromSender`, `apiSecret` |
| Plivo | `Plivo` | `authId`, `fromPhoneNumber`, `authToken` |
| Telnyx | `Telnyx` | `messagingProfileId`, `fromPhoneNumber`, `apiKey` |
| Bird | `Bird` | `originator`, `region`, `apiKey` |
| Telesign | `Telesign` | `customerId`, `fromSender`, `apiKey` |
| Sinch | `Sinch` | `servicePlanId`, `fromPhoneNumber`, `apiKey`, `apiSecret` |

`integrationName` and `isEnabled` are optional; send `integrationId` to update
an existing one. The Fake provider takes no field at all — this is the whole
request, and the server builds the rest (one Fake per project):

```python
client.hub.notifications.save_sms_integration(integration={"provider": "Fake"})
```

## Rendering a template without sending

`render_sms` runs the Razor template and returns the bound text, or the list of
tokens it could not resolve. Tokens are Razor only (`@Model.FirstName`):

```python
rendered = client.hub.notifications.render_sms(
code="Hi @Model.FirstName",
tokens=[{"key": "FirstName", "value": "Ada"}],
isForPreview=True,
)
```

## Reading one message of a campaign

`get_sms_campaign_message` takes the campaign id and the notification id in the
route; the batch id is required as well but is not part of the route, so it is
sent as a query value:

```python
message = client.hub.notifications.get_sms_campaign_message(
campaign_id="camp_1",
id="notif_1",
campaignBatchId="batch_1",
)
```

Get the ids from `get_sms_campaigns`, `get_sms_campaign_batches` and
`get_sms_campaign_messages`.

## Preview with a signed link

`preview_sms_notification(hash=...)` opens with the signed link alone — no API
key and no sign-in are needed (scope `optional`). See the README section
"Preview a notification with its signed link".
76 changes: 64 additions & 12 deletions src/norbix_python/hub/notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -1308,6 +1308,19 @@ def create_sms_campaign(self, *, timeout: float | None = None, bearer_token: str
bearer_token=bearer_token,
)

def stop_sms_campaign(self, id: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any:
"""POST /{version}/notifications/sms/campaigns/{Id}/stop"""
return self._transport.send(
target="hub",
path="/{version}/notifications/sms/campaigns/{Id}/stop",
method="POST",
path_params={"Id": id},
request=request,
scope="project",
timeout=timeout,
bearer_token=bearer_token,
)

def get_sms_campaign_messages(self, campaign_id: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any:
"""GET /{version}/notifications/sms/campaigns/{campaignId}/messages"""
return self._transport.send(
Expand All @@ -1322,12 +1335,12 @@ def get_sms_campaign_messages(self, campaign_id: str, *, timeout: float | None =
)

def get_sms_campaign_message(self, campaign_id: str, id: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any:
"""GET /{version}/notifications/sms/campaigns/{campaignId}/messages/{id}"""
"""GET /{version}/notifications/sms/campaigns/{campaignId}/messages/{notificationId}"""
return self._transport.send(
target="hub",
path="/{version}/notifications/sms/campaigns/{campaignId}/messages/{id}",
path="/{version}/notifications/sms/campaigns/{campaignId}/messages/{notificationId}",
method="GET",
path_params={"campaignId": campaign_id, "id": id},
path_params={"campaignId": campaign_id, "notificationId": id},
request=request,
scope="project",
timeout=timeout,
Expand Down Expand Up @@ -1425,6 +1438,19 @@ def disable_sms(self, *, timeout: float | None = None, bearer_token: str | None
bearer_token=bearer_token,
)

def get_sms_disable_dependencies(self, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any:
"""GET /{version}/notifications/sms/disable-dependencies"""
return self._transport.send(
target="hub",
path="/{version}/notifications/sms/disable-dependencies",
method="GET",
path_params={},
request=request,
scope="project",
timeout=timeout,
bearer_token=bearer_token,
)

def enable_sms(self, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any:
"""GET /{version}/notifications/sms/enable"""
return self._transport.send(
Expand Down Expand Up @@ -1624,11 +1650,11 @@ def update_sms_template(self, *, timeout: float | None = None, bearer_token: str
bearer_token=bearer_token,
)

def sms_razor_syntax_check(self, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any:
"""POST /{version}/notifications/sms/templates/razor-syntax-check"""
def render_sms(self, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any:
"""POST /{version}/notifications/sms/templates/render"""
return self._transport.send(
target="hub",
path="/{version}/notifications/sms/templates/razor-syntax-check",
path="/{version}/notifications/sms/templates/render",
method="POST",
path_params={},
request=request,
Expand Down Expand Up @@ -3019,6 +3045,19 @@ async def create_sms_campaign(self, *, timeout: float | None = None, bearer_toke
bearer_token=bearer_token,
)

async def stop_sms_campaign(self, id: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any:
"""POST /{version}/notifications/sms/campaigns/{Id}/stop"""
return await self._transport.send(
target="hub",
path="/{version}/notifications/sms/campaigns/{Id}/stop",
method="POST",
path_params={"Id": id},
request=request,
scope="project",
timeout=timeout,
bearer_token=bearer_token,
)

async def get_sms_campaign_messages(self, campaign_id: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any:
"""GET /{version}/notifications/sms/campaigns/{campaignId}/messages"""
return await self._transport.send(
Expand All @@ -3033,12 +3072,12 @@ async def get_sms_campaign_messages(self, campaign_id: str, *, timeout: float |
)

async def get_sms_campaign_message(self, campaign_id: str, id: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any:
"""GET /{version}/notifications/sms/campaigns/{campaignId}/messages/{id}"""
"""GET /{version}/notifications/sms/campaigns/{campaignId}/messages/{notificationId}"""
return await self._transport.send(
target="hub",
path="/{version}/notifications/sms/campaigns/{campaignId}/messages/{id}",
path="/{version}/notifications/sms/campaigns/{campaignId}/messages/{notificationId}",
method="GET",
path_params={"campaignId": campaign_id, "id": id},
path_params={"campaignId": campaign_id, "notificationId": id},
request=request,
scope="project",
timeout=timeout,
Expand Down Expand Up @@ -3136,6 +3175,19 @@ async def disable_sms(self, *, timeout: float | None = None, bearer_token: str |
bearer_token=bearer_token,
)

async def get_sms_disable_dependencies(self, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any:
"""GET /{version}/notifications/sms/disable-dependencies"""
return await self._transport.send(
target="hub",
path="/{version}/notifications/sms/disable-dependencies",
method="GET",
path_params={},
request=request,
scope="project",
timeout=timeout,
bearer_token=bearer_token,
)

async def enable_sms(self, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any:
"""GET /{version}/notifications/sms/enable"""
return await self._transport.send(
Expand Down Expand Up @@ -3335,11 +3387,11 @@ async def update_sms_template(self, *, timeout: float | None = None, bearer_toke
bearer_token=bearer_token,
)

async def sms_razor_syntax_check(self, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any:
"""POST /{version}/notifications/sms/templates/razor-syntax-check"""
async def render_sms(self, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any:
"""POST /{version}/notifications/sms/templates/render"""
return await self._transport.send(
target="hub",
path="/{version}/notifications/sms/templates/razor-syntax-check",
path="/{version}/notifications/sms/templates/render",
method="POST",
path_params={},
request=request,
Expand Down
Loading
Loading