Skip to content

Commit ec588a9

Browse files
Domantasclaude
andauthored
feat(notifications): add Email stop / disable-dependencies / domain-health / preferences-by-link; remove campaign-message methods (#22)
* feat(notifications): [1] remove the campaign-message methods whose gateway route was deleted The gateway removed the duplicate route GET /{version}/notifications/{emails|sms|push}/campaigns/{campaignId}/messages/{notificationId}. get_email_campaign_message, get_sms_campaign_message and get_push_campaign_message (sync and async) are removed with their tests and docs rows. Breaking: hub.notifications.get_email_campaign_message, get_sms_campaign_message and get_push_campaign_message are removed. Use get_email_campaign_batch_notification / get_sms_campaign_batch_notification / get_push_campaign_batch_notification(id=campaign_id, batch_id=..., notification_id=...) instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(notifications): [2] add stop_email_campaign, get_email_disable_dependencies, check_email_integration_domain_health, get_email_preferences_by_link Four Email methods the gateway has and this SDK lacked (sync and async): - hub.notifications.stop_email_campaign POST .../email/campaigns/{Id}/stop - hub.notifications.get_email_disable_dependencies GET .../email/disable-dependencies - hub.notifications.check_email_integration_domain_health POST .../email/integrations/domain-health - hub.email.get_email_preferences_by_link GET /{version}/email/preferences (scope optional: the signed unsubscribe-link token is the key) tests/hub/test_email_routes.py mirrors test_push.py: one case per Email route (50, routes and verbs from the gateway endpoint manifest) through the capture transport, a surface-size guard, the auth/project headers, the no-credentials call for the preferences link (sync and async) and an async-twin check. docs/hub/notifications.md and docs/hub/email.md list them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(email): [3] send one_click_unsubscribe with scope optional so it works without a token The gateway marks POST /{version}/email/one-click-unsubscribe as requiresAuth false: the signed link in the e-mail is the key. With scope "project" a client with no API key raised NORBIX_NOT_AUTHENTICATED. Sync and async now use "optional" like get_email_preferences_by_link and the preview links. Two new tests (sync, async): no credentials -> request sent, no Authorization header (red with "project", green with "optional"). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 03082db commit ec588a9

9 files changed

Lines changed: 655 additions & 156 deletions

File tree

‎docs/hub/email.md‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,13 @@ Access with `norbix.hub.email`.
44

55
| Method | Verb | Path | Scope |
66
| --- | --- | --- | --- |
7-
| `one_click_unsubscribe` | `POST` | `/{version}/email/one-click-unsubscribe` | `project` |
7+
| `one_click_unsubscribe` | `POST` | `/{version}/email/one-click-unsubscribe` | `optional` |
8+
| `get_email_preferences_by_link` | `GET` | `/{version}/email/preferences` | `optional` |
9+
10+
`get_email_preferences_by_link(token=...)` reads the marketing e-mail preferences of the
11+
person a signed unsubscribe link belongs to. The signed token is the key: it works on a
12+
client with no API key or bearer token (scope `optional` — a token is sent only when the
13+
client has one).
14+
15+
Both methods are public links: `one_click_unsubscribe` is also sent without credentials
16+
when the client has none.

‎docs/hub/notifications.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Access with `norbix.hub.notifications`.
77
| `get_user_notification_preferences` | `GET` | `/{version}/notifications/user/preferences` | `project` |
88
| `update_user_notifications_preferences` | `PUT` | `/{version}/notifications/user/preferences` | `project` |
99
| `disable_email` | `GET` | `/{version}/notifications/email/disable` | `project` |
10+
| `get_email_disable_dependencies` | `GET` | `/{version}/notifications/email/disable-dependencies` | `project` |
1011
| `enable_email` | `GET` | `/{version}/notifications/email/enable` | `project` |
1112
| `attach_file_to_template` | `POST` | `/{version}/notifications/email/templates/attachments` | `project` |
1213
| `create_email_template` | `POST` | `/{version}/notifications/email/templates` | `project` |
@@ -24,6 +25,7 @@ Access with `norbix.hub.notifications`.
2425
| `save_email_signature` | `POST` | `/{version}/notifications/email/signatures` | `project` |
2526
| `get_email_settings` | `GET` | `/{version}/notifications/email/settings` | `project` |
2627
| `confirm_email_integration_human_delivery` | `POST` | `/{version}/notifications/email/integrations/confirm-human-delivery` | `project` |
28+
| `check_email_integration_domain_health` | `POST` | `/{version}/notifications/email/integrations/domain-health` | `project` |
2729
| `delete_email_integration` | `DELETE` | `/{version}/notifications/email/integrations/{Id}` | `project` |
2830
| `disable_email_integration` | `PUT` | `/{version}/notifications/email/integrations/{Id}/disable` | `project` |
2931
| `enable_email_integration` | `PUT` | `/{version}/notifications/email/integrations/{Id}/enable` | `project` |
@@ -41,14 +43,14 @@ Access with `norbix.hub.notifications`.
4143
| `save_email_footer` | `POST` | `/{version}/notifications/email/footers` | `project` |
4244
| `create_email_campaign` | `POST` | `/{version}/notifications/email/campaigns` | `project` |
4345
| `delete_email_campaign` | `DELETE` | `/{version}/notifications/email/campaigns/{Id}` | `project` |
46+
| `stop_email_campaign` | `POST` | `/{version}/notifications/email/campaigns/{Id}/stop` | `project` |
4447
| `get_email_campaign` | `GET` | `/{version}/notifications/email/campaigns/{id}` | `project` |
4548
| `get_email_campaigns` | `GET` | `/{version}/notifications/email/campaigns` | `project` |
4649
| `get_email_campaign_batches` | `GET` | `/{version}/notifications/email/campaigns/{id}/batches` | `project` |
4750
| `get_email_campaign_batch_notification` | `GET` | `/{version}/notifications/email/campaigns/{id}/batches/{batchId}/{notificationId}` | `project` |
4851
| `get_email_campaign_batch_notifications` | `GET` | `/{version}/notifications/email/campaigns/{id}/batches/{batchId}` | `project` |
4952
| `get_email_campaign_statistics` | `GET` | `/{version}/notifications/email/campaigns/{id}/stats` | `project` |
5053
| `preview_email_notification` | `GET` | `/{version}/notifications/email/preview` | `optional` |
51-
| `get_email_campaign_message` | `GET` | `/{version}/notifications/emails/campaigns/{campaignId}/messages/{id}` | `project` |
5254
| `get_email_campaign_messages` | `GET` | `/{version}/notifications/emails/campaigns/{campaignId}/messages` | `project` |
5355
| `disable_sms` | `GET` | `/{version}/notifications/sms/disable` | `project` |
5456
| `enable_sms` | `GET` | `/{version}/notifications/sms/enable` | `project` |
@@ -83,7 +85,6 @@ Access with `norbix.hub.notifications`.
8385
| `get_sms_campaign_batch_notification` | `GET` | `/{version}/notifications/sms/campaigns/{id}/batches/{batchId}/{notificationId}` | `project` |
8486
| `get_sms_campaign_statistics` | `GET` | `/{version}/notifications/sms/campaigns/{id}/stats` | `project` |
8587
| `get_sms_campaign_messages` | `GET` | `/{version}/notifications/sms/campaigns/{campaignId}/messages` | `project` |
86-
| `get_sms_campaign_message` | `GET` | `/{version}/notifications/sms/campaigns/{campaignId}/messages/{notificationId}` | `project` |
8788
| `preview_sms_notification` | `GET` | `/{version}/notifications/sms/preview` | `optional` |
8889
| `disable_push` | `GET` | `/{version}/notifications/push/disable` | `project` |
8990
| `enable_push` | `GET` | `/{version}/notifications/push/enable` | `project` |
@@ -119,7 +120,6 @@ Access with `norbix.hub.notifications`.
119120
| `get_push_campaign_batch_notification` | `GET` | `/{version}/notifications/push/campaigns/{id}/batches/{batchId}/{notificationId}` | `project` |
120121
| `get_push_campaign_statistics` | `GET` | `/{version}/notifications/push/campaigns/{id}/stats` | `project` |
121122
| `get_push_campaign_messages` | `GET` | `/{version}/notifications/push/campaigns/{campaignId}/messages` | `project` |
122-
| `get_push_campaign_message` | `GET` | `/{version}/notifications/push/campaigns/{campaignId}/messages/{id}` | `project` |
123123
| `preview_push_notification` | `GET` | `/{version}/notifications/push/preview` | `optional` |
124124

125125
Every method above has an async twin on `client.hub.notifications` when the

‎docs/hub/sms.md‎

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,20 +86,21 @@ rendered = client.hub.notifications.render_sms(
8686

8787
## Reading one message of a campaign
8888

89-
`get_sms_campaign_message` takes the campaign id and the notification id in the
90-
route; the batch id is required as well but is not part of the route, so it is
91-
sent as a query value:
89+
Use `get_sms_campaign_batch_notification` with the campaign id, the batch id
90+
and the notification id (route `…/campaigns/{id}/batches/{batchId}/{notificationId}`):
9291

9392
```python
94-
message = client.hub.notifications.get_sms_campaign_message(
95-
campaign_id="camp_1",
96-
id="notif_1",
97-
campaignBatchId="batch_1",
93+
message = client.hub.notifications.get_sms_campaign_batch_notification(
94+
id="camp_1",
95+
batch_id="batch_1",
96+
notification_id="notif_1",
9897
)
9998
```
10099

101100
Get the ids from `get_sms_campaigns`, `get_sms_campaign_batches` and
102-
`get_sms_campaign_messages`.
101+
`get_sms_campaign_batch_notifications`. The older `get_sms_campaign_message`
102+
(`…/campaigns/{campaignId}/messages/{notificationId}`) was removed together
103+
with its gateway route.
103104

104105
## Preview with a signed link
105106

‎src/norbix_python/hub/email.py‎

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,36 @@ def __init__(self, transport: Transport) -> None:
1010
self._transport = transport
1111

1212
def one_click_unsubscribe(self, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any:
13-
"""POST /{version}/email/one-click-unsubscribe"""
13+
"""POST /{version}/email/one-click-unsubscribe
14+
15+
Public: the signed link in the e-mail is the key, so no credentials are
16+
needed. Auth is still sent when the client has a token.
17+
"""
1418
return self._transport.send(
1519
target="hub",
1620
path="/{version}/email/one-click-unsubscribe",
1721
method="POST",
1822
path_params={},
1923
request=request,
20-
scope="project",
24+
scope="optional",
25+
timeout=timeout,
26+
bearer_token=bearer_token,
27+
)
28+
29+
def get_email_preferences_by_link(self, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any:
30+
"""GET /{version}/email/preferences
31+
32+
Reads the marketing e-mail preferences of the person a signed unsubscribe link
33+
belongs to. Opens with the link alone: pass ``token=...`` and no credentials are
34+
needed. Auth is still sent when the client has a token.
35+
"""
36+
return self._transport.send(
37+
target="hub",
38+
path="/{version}/email/preferences",
39+
method="GET",
40+
path_params={},
41+
request=request,
42+
scope="optional",
2143
timeout=timeout,
2244
bearer_token=bearer_token,
2345
)
@@ -28,14 +50,36 @@ def __init__(self, transport: AsyncTransport) -> None:
2850
self._transport = transport
2951

3052
async def one_click_unsubscribe(self, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any:
31-
"""POST /{version}/email/one-click-unsubscribe"""
53+
"""POST /{version}/email/one-click-unsubscribe
54+
55+
Public: the signed link in the e-mail is the key, so no credentials are
56+
needed. Auth is still sent when the client has a token.
57+
"""
3258
return await self._transport.send(
3359
target="hub",
3460
path="/{version}/email/one-click-unsubscribe",
3561
method="POST",
3662
path_params={},
3763
request=request,
38-
scope="project",
64+
scope="optional",
65+
timeout=timeout,
66+
bearer_token=bearer_token,
67+
)
68+
69+
async def get_email_preferences_by_link(self, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any:
70+
"""GET /{version}/email/preferences
71+
72+
Reads the marketing e-mail preferences of the person a signed unsubscribe link
73+
belongs to. Opens with the link alone: pass ``token=...`` and no credentials are
74+
needed. Auth is still sent when the client has a token.
75+
"""
76+
return await self._transport.send(
77+
target="hub",
78+
path="/{version}/email/preferences",
79+
method="GET",
80+
path_params={},
81+
request=request,
82+
scope="optional",
3983
timeout=timeout,
4084
bearer_token=bearer_token,
4185
)

0 commit comments

Comments
 (0)