sendora 2.2.0__tar.gz → 2.3.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.
Files changed (39) hide show
  1. {sendora-2.2.0 → sendora-2.3.0}/CHANGELOG.md +7 -0
  2. {sendora-2.2.0 → sendora-2.3.0}/PKG-INFO +6 -3
  3. {sendora-2.2.0 → sendora-2.3.0}/README.md +5 -2
  4. {sendora-2.2.0 → sendora-2.3.0}/src/sendora/__init__.py +4 -0
  5. {sendora-2.2.0 → sendora-2.3.0}/src/sendora/_version.py +1 -1
  6. {sendora-2.2.0 → sendora-2.3.0}/src/sendora/_webhooks.py +4 -0
  7. {sendora-2.2.0 → sendora-2.3.0}/src/sendora/resources/webhooks.py +4 -2
  8. {sendora-2.2.0 → sendora-2.3.0}/src/sendora/types.py +89 -1
  9. {sendora-2.2.0 → sendora-2.3.0}/.gitignore +0 -0
  10. {sendora-2.2.0 → sendora-2.3.0}/LICENSE +0 -0
  11. {sendora-2.2.0 → sendora-2.3.0}/pyproject.toml +0 -0
  12. {sendora-2.2.0 → sendora-2.3.0}/src/sendora/_account.py +0 -0
  13. {sendora-2.2.0 → sendora-2.3.0}/src/sendora/_answers.py +0 -0
  14. {sendora-2.2.0 → sendora-2.3.0}/src/sendora/_client.py +0 -0
  15. {sendora-2.2.0 → sendora-2.3.0}/src/sendora/_errors.py +0 -0
  16. {sendora-2.2.0 → sendora-2.3.0}/src/sendora/_json.py +0 -0
  17. {sendora-2.2.0 → sendora-2.3.0}/src/sendora/_keys.py +0 -0
  18. {sendora-2.2.0 → sendora-2.3.0}/src/sendora/_model.py +0 -0
  19. {sendora-2.2.0 → sendora-2.3.0}/src/sendora/_options.py +0 -0
  20. {sendora-2.2.0 → sendora-2.3.0}/src/sendora/_pages.py +0 -0
  21. {sendora-2.2.0 → sendora-2.3.0}/src/sendora/_retry.py +0 -0
  22. {sendora-2.2.0 → sendora-2.3.0}/src/sendora/_transport.py +0 -0
  23. {sendora-2.2.0 → sendora-2.3.0}/src/sendora/_wire.py +0 -0
  24. {sendora-2.2.0 → sendora-2.3.0}/src/sendora/py.typed +0 -0
  25. {sendora-2.2.0 → sendora-2.3.0}/src/sendora/resources/__init__.py +0 -0
  26. {sendora-2.2.0 → sendora-2.3.0}/src/sendora/resources/broadcasts.py +0 -0
  27. {sendora-2.2.0 → sendora-2.3.0}/src/sendora/resources/domains.py +0 -0
  28. {sendora-2.2.0 → sendora-2.3.0}/src/sendora/resources/email.py +0 -0
  29. {sendora-2.2.0 → sendora-2.3.0}/src/sendora/resources/erasures.py +0 -0
  30. {sendora-2.2.0 → sendora-2.3.0}/src/sendora/resources/inbound.py +0 -0
  31. {sendora-2.2.0 → sendora-2.3.0}/src/sendora/resources/inbound_domains.py +0 -0
  32. {sendora-2.2.0 → sendora-2.3.0}/src/sendora/resources/messages.py +0 -0
  33. {sendora-2.2.0 → sendora-2.3.0}/src/sendora/resources/server.py +0 -0
  34. {sendora-2.2.0 → sendora-2.3.0}/src/sendora/resources/servers.py +0 -0
  35. {sendora-2.2.0 → sendora-2.3.0}/src/sendora/resources/streams.py +0 -0
  36. {sendora-2.2.0 → sendora-2.3.0}/src/sendora/resources/suppressions.py +0 -0
  37. {sendora-2.2.0 → sendora-2.3.0}/src/sendora/resources/tokens.py +0 -0
  38. {sendora-2.2.0 → sendora-2.3.0}/src/sendora/resources/tracking_domains.py +0 -0
  39. {sendora-2.2.0 → sendora-2.3.0}/src/sendora/skills/sendora/SKILL.md +0 -0
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.3.0
4
+
5
+ - Two events, `OpenedEvent` and `ClickedEvent`, for `opened` and `clicked`:
6
+ a tracked copy's pixel loaded, or a tracked link was followed. A webhook
7
+ gets them only if it names them, so `webhooks.create` without `events`
8
+ takes every other event.
9
+
3
10
  ## 2.2.0
4
11
 
5
12
  - Each `MessageRecipient` has `tracked`, `opens`, `clicks`, `first_open_at`
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sendora
3
- Version: 2.2.0
3
+ Version: 2.3.0
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
@@ -310,7 +310,9 @@ Events: `delivered`, `bounced`, `deferred`, `spam_complaint` and
310
310
  `unsubscribed` about a message, with `message_id`, `stream_id`,
311
311
  `recipient`, `occurred_at`, `server_id`, your `tag` and `metadata`, and
312
312
  `details` from the receiver, or for `unsubscribed` the `source` (`link` or
313
- `one_click`); `cap_warning` and `cap_reached` about usage, with `scope`,
313
+ `one_click`); `opened` and `clicked` about a tracked copy, sent only to a
314
+ webhook that names them, with the same fields and `details` that say whether
315
+ a machine did it and, for `clicked`, give the `link`; `cap_warning` and `cap_reached` about usage, with `scope`,
314
316
  `cap`, `used` and the period; `inbound` about a message received on an
315
317
  inbound stream, with `inbound_message_id`, `envelope_recipient`, the
316
318
  sizes, the `authentication` verdicts and, for a webhook with
@@ -318,7 +320,8 @@ sizes, the `authentication` verdicts and, for a webhook with
318
320
  `to`, `subject`, `text`, `html`, `attachments` and the rest.
319
321
  `inbound_cap_warning` and `inbound_cap_reached` say that received mail
320
322
  nears or reaches the account's monthly cap, with `cap`, `used` and the
321
- period. Every event carries `id`, the delivery id, and `attempt`.
323
+ period. Every event carries `id`, the delivery id, and `attempt`. Tracking is
324
+ not open to accounts yet, so `opened` and `clicked` are not sent until it is.
322
325
 
323
326
  A failed delivery is retried with growing delays for about a day, then it
324
327
  is `dead` until you replay it. Deliveries arrive at least once and not
@@ -288,7 +288,9 @@ Events: `delivered`, `bounced`, `deferred`, `spam_complaint` and
288
288
  `unsubscribed` about a message, with `message_id`, `stream_id`,
289
289
  `recipient`, `occurred_at`, `server_id`, your `tag` and `metadata`, and
290
290
  `details` from the receiver, or for `unsubscribed` the `source` (`link` or
291
- `one_click`); `cap_warning` and `cap_reached` about usage, with `scope`,
291
+ `one_click`); `opened` and `clicked` about a tracked copy, sent only to a
292
+ webhook that names them, with the same fields and `details` that say whether
293
+ a machine did it and, for `clicked`, give the `link`; `cap_warning` and `cap_reached` about usage, with `scope`,
292
294
  `cap`, `used` and the period; `inbound` about a message received on an
293
295
  inbound stream, with `inbound_message_id`, `envelope_recipient`, the
294
296
  sizes, the `authentication` verdicts and, for a webhook with
@@ -296,7 +298,8 @@ sizes, the `authentication` verdicts and, for a webhook with
296
298
  `to`, `subject`, `text`, `html`, `attachments` and the rest.
297
299
  `inbound_cap_warning` and `inbound_cap_reached` say that received mail
298
300
  nears or reaches the account's monthly cap, with `cap`, `used` and the
299
- period. Every event carries `id`, the delivery id, and `attempt`.
301
+ period. Every event carries `id`, the delivery id, and `attempt`. Tracking is
302
+ not open to accounts yet, so `opened` and `clicked` are not sent until it is.
300
303
 
301
304
  A failed delivery is retried with growing delays for about a day, then it
302
305
  is `dead` until you replay it. Deliveries arrive at least once and not
@@ -33,11 +33,13 @@ from .types import (
33
33
  BouncedEvent,
34
34
  CapReachedEvent,
35
35
  CapWarningEvent,
36
+ ClickedEvent,
36
37
  DeferredEvent,
37
38
  DeliveredEvent,
38
39
  InboundCapReachedEvent,
39
40
  InboundCapWarningEvent,
40
41
  InboundEvent,
42
+ OpenedEvent,
41
43
  SendoraErrorCode,
42
44
  SpamComplaintEvent,
43
45
  UnknownEvent,
@@ -58,6 +60,7 @@ __all__ = [
58
60
  "BouncedEvent",
59
61
  "CapReachedEvent",
60
62
  "CapWarningEvent",
63
+ "ClickedEvent",
61
64
  "ConflictError",
62
65
  "DeferredEvent",
63
66
  "DeliveredEvent",
@@ -67,6 +70,7 @@ __all__ = [
67
70
  "InboundEvent",
68
71
  "InternalServerError",
69
72
  "NotFoundError",
73
+ "OpenedEvent",
70
74
  "PermissionDeniedError",
71
75
  "RateLimitError",
72
76
  "ResponseValidationError",
@@ -1,3 +1,3 @@
1
1
  """The package's version: the build reads it, and every request names it."""
2
2
 
3
- __version__ = "2.2.0"
3
+ __version__ = "2.3.0"
@@ -15,12 +15,14 @@ from .types import (
15
15
  BouncedEvent,
16
16
  CapReachedEvent,
17
17
  CapWarningEvent,
18
+ ClickedEvent,
18
19
  DeferredEvent,
19
20
  DeliveredEvent,
20
21
  InboundCapReachedEvent,
21
22
  InboundCapWarningEvent,
22
23
  InboundEvent,
23
24
  InboundEventContent,
25
+ OpenedEvent,
24
26
  SpamComplaintEvent,
25
27
  UnknownEvent,
26
28
  UnsubscribedEvent,
@@ -169,6 +171,8 @@ _EVENTS: dict[str, Callable[[dict[str, object]], WebhookEvent]] = {
169
171
  "deferred": lambda payload: decode(DeferredEvent, payload),
170
172
  "spam_complaint": lambda payload: decode(SpamComplaintEvent, payload),
171
173
  "unsubscribed": lambda payload: decode(UnsubscribedEvent, payload),
174
+ "opened": lambda payload: decode(OpenedEvent, payload),
175
+ "clicked": lambda payload: decode(ClickedEvent, payload),
172
176
  "cap_warning": lambda payload: decode(CapWarningEvent, payload),
173
177
  "cap_reached": lambda payload: decode(CapReachedEvent, payload),
174
178
  "inbound": _inbound,
@@ -44,7 +44,8 @@ class Webhooks:
44
44
  Args:
45
45
  url: Where the events are posted: https, without credentials or
46
46
  a fragment.
47
- events: The events to deliver; every event when left out.
47
+ events: The events to deliver; every event except ``opened`` and
48
+ ``clicked`` when left out.
48
49
  stream_id: The one stream whose message events it receives;
49
50
  every stream when left out. Usage events arrive either way.
50
51
  inbound_content: How the ``inbound`` event carries a received
@@ -261,7 +262,8 @@ class AsyncWebhooks:
261
262
  Args:
262
263
  url: Where the events are posted: https, without credentials or
263
264
  a fragment.
264
- events: The events to deliver; every event when left out.
265
+ events: The events to deliver; every event except ``opened`` and
266
+ ``clicked`` when left out.
265
267
  stream_id: The one stream whose message events it receives;
266
268
  every stream when left out. Usage events arrive either way.
267
269
  inbound_content: How the ``inbound`` event carries a received
@@ -48,6 +48,8 @@ __all__ = [
48
48
  "CapEventFields",
49
49
  "CapReachedEvent",
50
50
  "CapWarningEvent",
51
+ "ClickedDetails",
52
+ "ClickedEvent",
51
53
  "CnameRecord",
52
54
  "CreatedServer",
53
55
  "CreatedToken",
@@ -99,6 +101,8 @@ __all__ = [
99
101
  "MessageStatus",
100
102
  "MessageTracking",
101
103
  "MxRecord",
104
+ "OpenedDetails",
105
+ "OpenedEvent",
102
106
  "OwnServer",
103
107
  "RecipientKind",
104
108
  "RecipientStatus",
@@ -493,7 +497,7 @@ report received by mail, ``Feedback`` for a spam complaint, ``Open`` and
493
497
 
494
498
  TrackingAutomated = Literal["likely", "unlikely", "unknown"]
495
499
  """Whether a machine rather than a person did it: ``unknown`` where nobody
496
- can tell, as with Apple Mail."""
500
+ can tell, as with Apple's privacy protection."""
497
501
 
498
502
  TrackingAgent = Literal[
499
503
  "apple_privacy",
@@ -1189,6 +1193,8 @@ WebhookEventName = Literal[
1189
1193
  "deferred",
1190
1194
  "spam_complaint",
1191
1195
  "unsubscribed",
1196
+ "opened",
1197
+ "clicked",
1192
1198
  "cap_warning",
1193
1199
  "cap_reached",
1194
1200
  "inbound",
@@ -2083,6 +2089,86 @@ class UnsubscribedEvent(MessageEventFields):
2083
2089
  details: UnsubscribedDetails
2084
2090
 
2085
2091
 
2092
+ @model
2093
+ class OpenedDetails(Answer):
2094
+ """What tracking knows of an open.
2095
+
2096
+ Attributes:
2097
+ automated: Whether a machine rather than a person did it.
2098
+ agent: The proxy or machine it came through, where Sendora knows it.
2099
+ reasons: Why it was labelled a machine's.
2100
+ client: The mail program, where Sendora knows it.
2101
+ device: The kind of device, where Sendora knows it.
2102
+ first: The recipient's first event of this kind.
2103
+ first_human: The recipient's first event of this kind that was
2104
+ likely a person's.
2105
+ """
2106
+
2107
+ automated: TrackingAutomated
2108
+ agent: TrackingAgent | None
2109
+ reasons: list[TrackingReason]
2110
+ client: TrackingClient | None
2111
+ device: TrackingDevice | None
2112
+ first: bool
2113
+ first_human: bool
2114
+
2115
+
2116
+ @model
2117
+ class OpenedEvent(MessageEventFields):
2118
+ """The pixel of a tracked copy loaded, for a person or a machine.
2119
+
2120
+ A webhook gets this event only if it names it.
2121
+
2122
+ Attributes:
2123
+ event: ``opened``.
2124
+ details: What tracking knows of it.
2125
+ """
2126
+
2127
+ event: Literal["opened"]
2128
+ details: OpenedDetails
2129
+
2130
+
2131
+ @model
2132
+ class ClickedDetails(Answer):
2133
+ """What tracking knows of a click.
2134
+
2135
+ Attributes:
2136
+ automated: Whether a machine rather than a person did it.
2137
+ agent: The proxy or machine it came through, where Sendora knows it.
2138
+ reasons: Why it was labelled a machine's.
2139
+ client: The mail program, where Sendora knows it.
2140
+ device: The kind of device, where Sendora knows it.
2141
+ first: The recipient's first event of this kind.
2142
+ first_human: The recipient's first event of this kind that was
2143
+ likely a person's.
2144
+ link: The link that was followed.
2145
+ """
2146
+
2147
+ automated: TrackingAutomated
2148
+ agent: TrackingAgent | None
2149
+ reasons: list[TrackingReason]
2150
+ client: TrackingClient | None
2151
+ device: TrackingDevice | None
2152
+ first: bool
2153
+ first_human: bool
2154
+ link: TrackedLink
2155
+
2156
+
2157
+ @model
2158
+ class ClickedEvent(MessageEventFields):
2159
+ """A tracked link was followed, by a person or a machine.
2160
+
2161
+ A webhook gets this event only if it names it.
2162
+
2163
+ Attributes:
2164
+ event: ``clicked``.
2165
+ details: What tracking knows of it.
2166
+ """
2167
+
2168
+ event: Literal["clicked"]
2169
+ details: ClickedDetails
2170
+
2171
+
2086
2172
  @model
2087
2173
  class CapEventFields(Answer):
2088
2174
  """What both usage events carry.
@@ -2316,6 +2402,8 @@ WebhookEvent = (
2316
2402
  | DeferredEvent
2317
2403
  | SpamComplaintEvent
2318
2404
  | UnsubscribedEvent
2405
+ | OpenedEvent
2406
+ | ClickedEvent
2319
2407
  | CapWarningEvent
2320
2408
  | CapReachedEvent
2321
2409
  | 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