sendora 2.4.0__tar.gz → 2.5.1__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.
Files changed (40) hide show
  1. {sendora-2.4.0 → sendora-2.5.1}/CHANGELOG.md +12 -0
  2. {sendora-2.4.0 → sendora-2.5.1}/PKG-INFO +18 -13
  3. {sendora-2.4.0 → sendora-2.5.1}/README.md +17 -12
  4. {sendora-2.4.0 → sendora-2.5.1}/src/sendora/__init__.py +2 -0
  5. {sendora-2.4.0 → sendora-2.5.1}/src/sendora/_version.py +1 -1
  6. {sendora-2.4.0 → sendora-2.5.1}/src/sendora/_webhooks.py +2 -0
  7. {sendora-2.4.0 → sendora-2.5.1}/src/sendora/types.py +21 -2
  8. {sendora-2.4.0 → sendora-2.5.1}/.gitignore +0 -0
  9. {sendora-2.4.0 → sendora-2.5.1}/LICENSE +0 -0
  10. {sendora-2.4.0 → sendora-2.5.1}/pyproject.toml +0 -0
  11. {sendora-2.4.0 → sendora-2.5.1}/src/sendora/_account.py +0 -0
  12. {sendora-2.4.0 → sendora-2.5.1}/src/sendora/_answers.py +0 -0
  13. {sendora-2.4.0 → sendora-2.5.1}/src/sendora/_client.py +0 -0
  14. {sendora-2.4.0 → sendora-2.5.1}/src/sendora/_errors.py +0 -0
  15. {sendora-2.4.0 → sendora-2.5.1}/src/sendora/_json.py +0 -0
  16. {sendora-2.4.0 → sendora-2.5.1}/src/sendora/_keys.py +0 -0
  17. {sendora-2.4.0 → sendora-2.5.1}/src/sendora/_model.py +0 -0
  18. {sendora-2.4.0 → sendora-2.5.1}/src/sendora/_options.py +0 -0
  19. {sendora-2.4.0 → sendora-2.5.1}/src/sendora/_pages.py +0 -0
  20. {sendora-2.4.0 → sendora-2.5.1}/src/sendora/_retry.py +0 -0
  21. {sendora-2.4.0 → sendora-2.5.1}/src/sendora/_transport.py +0 -0
  22. {sendora-2.4.0 → sendora-2.5.1}/src/sendora/_wire.py +0 -0
  23. {sendora-2.4.0 → sendora-2.5.1}/src/sendora/py.typed +0 -0
  24. {sendora-2.4.0 → sendora-2.5.1}/src/sendora/resources/__init__.py +0 -0
  25. {sendora-2.4.0 → sendora-2.5.1}/src/sendora/resources/broadcasts.py +0 -0
  26. {sendora-2.4.0 → sendora-2.5.1}/src/sendora/resources/domains.py +0 -0
  27. {sendora-2.4.0 → sendora-2.5.1}/src/sendora/resources/email.py +0 -0
  28. {sendora-2.4.0 → sendora-2.5.1}/src/sendora/resources/erasures.py +0 -0
  29. {sendora-2.4.0 → sendora-2.5.1}/src/sendora/resources/inbound.py +0 -0
  30. {sendora-2.4.0 → sendora-2.5.1}/src/sendora/resources/inbound_domains.py +0 -0
  31. {sendora-2.4.0 → sendora-2.5.1}/src/sendora/resources/messages.py +0 -0
  32. {sendora-2.4.0 → sendora-2.5.1}/src/sendora/resources/server.py +0 -0
  33. {sendora-2.4.0 → sendora-2.5.1}/src/sendora/resources/servers.py +0 -0
  34. {sendora-2.4.0 → sendora-2.5.1}/src/sendora/resources/streams.py +0 -0
  35. {sendora-2.4.0 → sendora-2.5.1}/src/sendora/resources/suppressions.py +0 -0
  36. {sendora-2.4.0 → sendora-2.5.1}/src/sendora/resources/tokens.py +0 -0
  37. {sendora-2.4.0 → sendora-2.5.1}/src/sendora/resources/tracking_domains.py +0 -0
  38. {sendora-2.4.0 → sendora-2.5.1}/src/sendora/resources/tracking_opt_outs.py +0 -0
  39. {sendora-2.4.0 → sendora-2.5.1}/src/sendora/resources/webhooks.py +0 -0
  40. {sendora-2.4.0 → sendora-2.5.1}/src/sendora/skills/sendora/SKILL.md +0 -0
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.5.1
4
+
5
+ - The README no longer says that tracking is not open to accounts. It says
6
+ that an administrator switches tracking on for a stream in the dashboard.
7
+ No call changes.
8
+
9
+ ## 2.5.0
10
+
11
+ - A new event, `TrackingOptedOutEvent`, for `tracking_opted_out`: a recipient
12
+ asked not to be tracked, through the link that `{{ tracking_opt_out_url }}`
13
+ puts in your mail. `webhooks.create` without `events` takes it.
14
+
3
15
  ## 2.4.0
4
16
 
5
17
  - `sendora.tracking_opt_outs` manages a server's not-tracked list: `list`,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sendora
3
- Version: 2.4.0
3
+ Version: 2.5.1
4
4
  Summary: The Python client for Sendora's email API.
5
5
  Project-URL: Homepage, https://sendora.se/docs
6
6
  License-Expression: MIT
@@ -148,8 +148,8 @@ list of them), `from_` and `to` as an aware `datetime` or ISO 8601,
148
148
 
149
149
  Where mail is tracked, each recipient has `tracked`, `opens`, `clicks`,
150
150
  `first_open_at` and `first_human_open_at`. `messages.get` adds `tracking`, and
151
- its timeline holds `Open` and `Click` events with their labels. Tracking is
152
- not open to accounts yet.
151
+ its timeline holds `Open` and `Click` events with their labels. An
152
+ administrator switches tracking on for a stream in the dashboard.
153
153
 
154
154
  ```python
155
155
  from datetime import UTC, datetime
@@ -250,8 +250,7 @@ sendora.suppressions.delete(address="anna@example.com")
250
250
 
251
251
  Addresses the server never tracks, on any of its streams. Mail to one still
252
252
  goes out, untracked, and its opens and clicks of mail already sent are not
253
- recorded. Tracking is not open to accounts yet, so the list changes nothing
254
- until it is.
253
+ recorded.
255
254
 
256
255
  | Call | Answers |
257
256
  | ------------------------------------------------ | -------------------------------------------------------- |
@@ -262,8 +261,14 @@ until it is.
262
261
 
263
262
  An entry's `source` is `manual`, or `recipient` if the recipient asked for it.
264
263
  Remove a recipient's own entry only after new consent. An entry's `address` is
265
- `None` once an erasure has left only a keyed hash of it. `delete` still finds
266
- that entry by its address, and nothing removes it without the address.
264
+ `None` when only a keyed hash of it is left, after an erasure or when the
265
+ recipient asked from mail that had left the log. `delete` still finds that entry
266
+ by its address, and nothing removes it without the address.
267
+
268
+ To let a recipient ask, write `{{ tracking_opt_out_url }}` in the text or the
269
+ HTML of a message on any stream. Each recipient then gets a copy with their own
270
+ link to a page in Swedish and English. Its button lists the address with the
271
+ `source` `recipient`, and sends the `tracking_opted_out` event.
267
272
 
268
273
  ```python
269
274
  sendora.tracking_opt_outs.add(address="anna@example.com")
@@ -335,7 +340,9 @@ Events: `delivered`, `bounced`, `deferred`, `spam_complaint` and
335
340
  `details` from the receiver, or for `unsubscribed` the `source` (`link` or
336
341
  `one_click`); `opened` and `clicked` about a tracked copy, sent only to a
337
342
  webhook that names them, with the same fields and `details` that say whether
338
- a machine did it and, for `clicked`, give the `link`; `cap_warning` and `cap_reached` about usage, with `scope`,
343
+ a machine did it and, for `clicked`, give the `link`; `tracking_opted_out`
344
+ about a recipient who asked not to be tracked, with the same fields and no
345
+ `details`; `cap_warning` and `cap_reached` about usage, with `scope`,
339
346
  `cap`, `used` and the period; `inbound` about a message received on an
340
347
  inbound stream, with `inbound_message_id`, `envelope_recipient`, the
341
348
  sizes, the `authentication` verdicts and, for a webhook with
@@ -343,8 +350,7 @@ sizes, the `authentication` verdicts and, for a webhook with
343
350
  `to`, `subject`, `text`, `html`, `attachments` and the rest.
344
351
  `inbound_cap_warning` and `inbound_cap_reached` say that received mail
345
352
  nears or reaches the account's monthly cap, with `cap`, `used` and the
346
- period. Every event carries `id`, the delivery id, and `attempt`. Tracking is
347
- not open to accounts yet, so `opened` and `clicked` are not sent until it is.
353
+ period. Every event carries `id`, the delivery id, and `attempt`.
348
354
 
349
355
  A failed delivery is retried with growing delays for about a day, then it
350
356
  is `dead` until you replay it. Deliveries arrive at least once and not
@@ -469,9 +475,8 @@ print(domain.dkim.type, domain.dkim.host, domain.dkim.value)
469
475
 
470
476
  A tracking domain is the host that tracked links and the open pixel use: a
471
477
  subdomain of a verified sending domain, with one CNAME record to add. CAA
472
- records above it must let Let's Encrypt issue its certificate. Tracking is
473
- not open to accounts yet, so a host carries no mail until it is. A host
474
- whose records are not right within 7 days goes.
478
+ records above it must let Let's Encrypt issue its certificate. A host whose
479
+ records are not right within 7 days goes.
475
480
 
476
481
  | Call | Answers |
477
482
  | ----------------------------------------------------- | --------------------------------------------------------------------- |
@@ -126,8 +126,8 @@ list of them), `from_` and `to` as an aware `datetime` or ISO 8601,
126
126
 
127
127
  Where mail is tracked, each recipient has `tracked`, `opens`, `clicks`,
128
128
  `first_open_at` and `first_human_open_at`. `messages.get` adds `tracking`, and
129
- its timeline holds `Open` and `Click` events with their labels. Tracking is
130
- not open to accounts yet.
129
+ its timeline holds `Open` and `Click` events with their labels. An
130
+ administrator switches tracking on for a stream in the dashboard.
131
131
 
132
132
  ```python
133
133
  from datetime import UTC, datetime
@@ -228,8 +228,7 @@ sendora.suppressions.delete(address="anna@example.com")
228
228
 
229
229
  Addresses the server never tracks, on any of its streams. Mail to one still
230
230
  goes out, untracked, and its opens and clicks of mail already sent are not
231
- recorded. Tracking is not open to accounts yet, so the list changes nothing
232
- until it is.
231
+ recorded.
233
232
 
234
233
  | Call | Answers |
235
234
  | ------------------------------------------------ | -------------------------------------------------------- |
@@ -240,8 +239,14 @@ until it is.
240
239
 
241
240
  An entry's `source` is `manual`, or `recipient` if the recipient asked for it.
242
241
  Remove a recipient's own entry only after new consent. An entry's `address` is
243
- `None` once an erasure has left only a keyed hash of it. `delete` still finds
244
- that entry by its address, and nothing removes it without the address.
242
+ `None` when only a keyed hash of it is left, after an erasure or when the
243
+ recipient asked from mail that had left the log. `delete` still finds that entry
244
+ by its address, and nothing removes it without the address.
245
+
246
+ To let a recipient ask, write `{{ tracking_opt_out_url }}` in the text or the
247
+ HTML of a message on any stream. Each recipient then gets a copy with their own
248
+ link to a page in Swedish and English. Its button lists the address with the
249
+ `source` `recipient`, and sends the `tracking_opted_out` event.
245
250
 
246
251
  ```python
247
252
  sendora.tracking_opt_outs.add(address="anna@example.com")
@@ -313,7 +318,9 @@ Events: `delivered`, `bounced`, `deferred`, `spam_complaint` and
313
318
  `details` from the receiver, or for `unsubscribed` the `source` (`link` or
314
319
  `one_click`); `opened` and `clicked` about a tracked copy, sent only to a
315
320
  webhook that names them, with the same fields and `details` that say whether
316
- a machine did it and, for `clicked`, give the `link`; `cap_warning` and `cap_reached` about usage, with `scope`,
321
+ a machine did it and, for `clicked`, give the `link`; `tracking_opted_out`
322
+ about a recipient who asked not to be tracked, with the same fields and no
323
+ `details`; `cap_warning` and `cap_reached` about usage, with `scope`,
317
324
  `cap`, `used` and the period; `inbound` about a message received on an
318
325
  inbound stream, with `inbound_message_id`, `envelope_recipient`, the
319
326
  sizes, the `authentication` verdicts and, for a webhook with
@@ -321,8 +328,7 @@ sizes, the `authentication` verdicts and, for a webhook with
321
328
  `to`, `subject`, `text`, `html`, `attachments` and the rest.
322
329
  `inbound_cap_warning` and `inbound_cap_reached` say that received mail
323
330
  nears or reaches the account's monthly cap, with `cap`, `used` and the
324
- period. Every event carries `id`, the delivery id, and `attempt`. Tracking is
325
- not open to accounts yet, so `opened` and `clicked` are not sent until it is.
331
+ period. Every event carries `id`, the delivery id, and `attempt`.
326
332
 
327
333
  A failed delivery is retried with growing delays for about a day, then it
328
334
  is `dead` until you replay it. Deliveries arrive at least once and not
@@ -447,9 +453,8 @@ print(domain.dkim.type, domain.dkim.host, domain.dkim.value)
447
453
 
448
454
  A tracking domain is the host that tracked links and the open pixel use: a
449
455
  subdomain of a verified sending domain, with one CNAME record to add. CAA
450
- records above it must let Let's Encrypt issue its certificate. Tracking is
451
- not open to accounts yet, so a host carries no mail until it is. A host
452
- whose records are not right within 7 days goes.
456
+ records above it must let Let's Encrypt issue its certificate. A host whose
457
+ records are not right within 7 days goes.
453
458
 
454
459
  | Call | Answers |
455
460
  | ----------------------------------------------------- | --------------------------------------------------------------------- |
@@ -42,6 +42,7 @@ from .types import (
42
42
  OpenedEvent,
43
43
  SendoraErrorCode,
44
44
  SpamComplaintEvent,
45
+ TrackingOptedOutEvent,
45
46
  UnknownEvent,
46
47
  UnsubscribedEvent,
47
48
  WebhookEvent,
@@ -79,6 +80,7 @@ __all__ = [
79
80
  "SendoraError",
80
81
  "SendoraErrorCode",
81
82
  "SpamComplaintEvent",
83
+ "TrackingOptedOutEvent",
82
84
  "UnknownEvent",
83
85
  "UnprocessableEntityError",
84
86
  "UnsubscribedEvent",
@@ -1,3 +1,3 @@
1
1
  """The package's version: the build reads it, and every request names it."""
2
2
 
3
- __version__ = "2.4.0"
3
+ __version__ = "2.5.1"
@@ -24,6 +24,7 @@ from .types import (
24
24
  InboundEventContent,
25
25
  OpenedEvent,
26
26
  SpamComplaintEvent,
27
+ TrackingOptedOutEvent,
27
28
  UnknownEvent,
28
29
  UnsubscribedEvent,
29
30
  WebhookEvent,
@@ -173,6 +174,7 @@ _EVENTS: dict[str, Callable[[dict[str, object]], WebhookEvent]] = {
173
174
  "unsubscribed": lambda payload: decode(UnsubscribedEvent, payload),
174
175
  "opened": lambda payload: decode(OpenedEvent, payload),
175
176
  "clicked": lambda payload: decode(ClickedEvent, payload),
177
+ "tracking_opted_out": lambda payload: decode(TrackingOptedOutEvent, payload),
176
178
  "cap_warning": lambda payload: decode(CapWarningEvent, payload),
177
179
  "cap_reached": lambda payload: decode(CapReachedEvent, payload),
178
180
  "inbound": _inbound,
@@ -146,6 +146,7 @@ __all__ = [
146
146
  "TrackingOptOut",
147
147
  "TrackingOptOutPage",
148
148
  "TrackingOptOutSource",
149
+ "TrackingOptedOutEvent",
149
150
  "TrackingReason",
150
151
  "TrackingSkipped",
151
152
  "TxtRecord",
@@ -1057,8 +1058,9 @@ class TrackingOptOut(Answer):
1057
1058
  """An address that the server never tracks.
1058
1059
 
1059
1060
  Attributes:
1060
- address: The address, lower-cased. ``None`` once an erasure has left
1061
- only a keyed hash.
1061
+ address: The address, lower-cased. ``None`` when only a keyed hash is
1062
+ left: after an erasure, or when the recipient asked from mail that
1063
+ had left the log.
1062
1064
  source: ``manual`` if added through the API or the dashboard,
1063
1065
  ``recipient`` if the recipient asked for it. Remove a recipient's
1064
1066
  own entry only after new consent.
@@ -1236,6 +1238,7 @@ WebhookEventName = Literal[
1236
1238
  "unsubscribed",
1237
1239
  "opened",
1238
1240
  "clicked",
1241
+ "tracking_opted_out",
1239
1242
  "cap_warning",
1240
1243
  "cap_reached",
1241
1244
  "inbound",
@@ -2210,6 +2213,21 @@ class ClickedEvent(MessageEventFields):
2210
2213
  details: ClickedDetails
2211
2214
 
2212
2215
 
2216
+ @model
2217
+ class TrackingOptedOutEvent(MessageEventFields):
2218
+ """The recipient asked not to be tracked.
2219
+
2220
+ They used the page that ``{{ tracking_opt_out_url }}`` links to, so the
2221
+ address is on the server's not-tracked list. A webhook that leaves out
2222
+ ``events`` gets this event.
2223
+
2224
+ Attributes:
2225
+ event: ``tracking_opted_out``.
2226
+ """
2227
+
2228
+ event: Literal["tracking_opted_out"]
2229
+
2230
+
2213
2231
  @model
2214
2232
  class CapEventFields(Answer):
2215
2233
  """What both usage events carry.
@@ -2445,6 +2463,7 @@ WebhookEvent = (
2445
2463
  | UnsubscribedEvent
2446
2464
  | OpenedEvent
2447
2465
  | ClickedEvent
2466
+ | TrackingOptedOutEvent
2448
2467
  | CapWarningEvent
2449
2468
  | CapReachedEvent
2450
2469
  | InboundEvent
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes