messagebird-sdk 0.2.0__tar.gz → 0.2.2__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.
- {messagebird_sdk-0.2.0 → messagebird_sdk-0.2.2}/PKG-INFO +5 -1
- {messagebird_sdk-0.2.0 → messagebird_sdk-0.2.2}/pyproject.toml +7 -0
- {messagebird_sdk-0.2.0 → messagebird_sdk-0.2.2}/src/bird/__init__.py +2 -0
- {messagebird_sdk-0.2.0 → messagebird_sdk-0.2.2}/src/bird/_base_client.py +17 -2
- {messagebird_sdk-0.2.0 → messagebird_sdk-0.2.2}/src/bird/_client.py +0 -2
- {messagebird_sdk-0.2.0 → messagebird_sdk-0.2.2}/src/bird/_event_types.py +2 -0
- {messagebird_sdk-0.2.0 → messagebird_sdk-0.2.2}/src/bird/_exceptions.py +24 -0
- {messagebird_sdk-0.2.0 → messagebird_sdk-0.2.2}/src/bird/_generated.py +178 -20
- messagebird_sdk-0.2.2/src/bird/_version.py +1 -0
- {messagebird_sdk-0.2.0 → messagebird_sdk-0.2.2}/src/bird/resources/email.py +0 -14
- {messagebird_sdk-0.2.0 → messagebird_sdk-0.2.2}/src/bird/resources/webhooks.py +0 -2
- messagebird_sdk-0.2.0/src/bird/_version.py +0 -1
- {messagebird_sdk-0.2.0 → messagebird_sdk-0.2.2}/.gitignore +0 -0
- {messagebird_sdk-0.2.0 → messagebird_sdk-0.2.2}/LICENSE +0 -0
- {messagebird_sdk-0.2.0 → messagebird_sdk-0.2.2}/README.md +0 -0
- {messagebird_sdk-0.2.0 → messagebird_sdk-0.2.2}/src/bird/_constants.py +0 -0
- {messagebird_sdk-0.2.0 → messagebird_sdk-0.2.2}/src/bird/_models.py +0 -0
- {messagebird_sdk-0.2.0 → messagebird_sdk-0.2.2}/src/bird/_response.py +0 -0
- {messagebird_sdk-0.2.0 → messagebird_sdk-0.2.2}/src/bird/_types.py +0 -0
- {messagebird_sdk-0.2.0 → messagebird_sdk-0.2.2}/src/bird/pagination.py +0 -0
- {messagebird_sdk-0.2.0 → messagebird_sdk-0.2.2}/src/bird/py.typed +0 -0
- {messagebird_sdk-0.2.0 → messagebird_sdk-0.2.2}/src/bird/resources/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: messagebird-sdk
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: The official Python SDK for the Bird email platform.
|
|
5
5
|
Project-URL: Homepage, https://bird.com
|
|
6
6
|
Project-URL: Documentation, https://bird.com/docs/sdks/python
|
|
@@ -25,12 +25,16 @@ Requires-Python: >=3.10
|
|
|
25
25
|
Requires-Dist: httpx>=0.27
|
|
26
26
|
Requires-Dist: pydantic>=2.0
|
|
27
27
|
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: django>=4.2; extra == 'dev'
|
|
29
|
+
Requires-Dist: fastapi>=0.111; extra == 'dev'
|
|
30
|
+
Requires-Dist: flask>=3.0; extra == 'dev'
|
|
28
31
|
Requires-Dist: pyright>=1.1; extra == 'dev'
|
|
29
32
|
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
30
33
|
Requires-Dist: pytest>=8; extra == 'dev'
|
|
31
34
|
Requires-Dist: pyyaml>=6; extra == 'dev'
|
|
32
35
|
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
33
36
|
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
37
|
+
Requires-Dist: starlette>=0.37; extra == 'dev'
|
|
34
38
|
Description-Content-Type: text/markdown
|
|
35
39
|
|
|
36
40
|
# Bird Python SDK
|
|
@@ -45,6 +45,13 @@ dev = [
|
|
|
45
45
|
"ruff>=0.6",
|
|
46
46
|
"pyright>=1.1",
|
|
47
47
|
"pyyaml>=6",
|
|
48
|
+
# Web frameworks used only by the homepage integration examples under
|
|
49
|
+
# examples/python/ — dev-only so they type-check (pyright) but never ship
|
|
50
|
+
# in the wheel (runtime deps stay httpx + pydantic).
|
|
51
|
+
"django>=4.2",
|
|
52
|
+
"flask>=3.0",
|
|
53
|
+
"fastapi>=0.111",
|
|
54
|
+
"starlette>=0.37",
|
|
48
55
|
]
|
|
49
56
|
|
|
50
57
|
[tool.hatch.version]
|
|
@@ -32,6 +32,7 @@ from bird._exceptions import (
|
|
|
32
32
|
APITimeoutError,
|
|
33
33
|
BirdError,
|
|
34
34
|
ErrorDetail,
|
|
35
|
+
ErrorNextAction,
|
|
35
36
|
ErrorType,
|
|
36
37
|
RateLimitError,
|
|
37
38
|
ValidationError,
|
|
@@ -66,6 +67,7 @@ __all__ = [
|
|
|
66
67
|
"APITimeoutError",
|
|
67
68
|
"WebhookVerificationError",
|
|
68
69
|
"ErrorDetail",
|
|
70
|
+
"ErrorNextAction",
|
|
69
71
|
"ErrorType",
|
|
70
72
|
"__version__",
|
|
71
73
|
]
|
|
@@ -27,9 +27,23 @@ from bird._version import __version__
|
|
|
27
27
|
|
|
28
28
|
USER_AGENT = f"bird-sdk-python/{__version__} ({platform.python_implementation().lower()}/{platform.python_version()})"
|
|
29
29
|
|
|
30
|
+
# Bird-* client-identity headers (ADR-0074): the API attributes the SDK
|
|
31
|
+
# surface from these, not the User-Agent. Telemetry labels only; computed once.
|
|
32
|
+
# Keys use the canonical wire casing (matching the Go/TS SDKs).
|
|
33
|
+
_CLIENT_HEADERS = {
|
|
34
|
+
"Bird-Surface": "sdk-python",
|
|
35
|
+
"Bird-Version": __version__,
|
|
36
|
+
"Bird-Lang": platform.python_implementation().lower(),
|
|
37
|
+
"Bird-Os": platform.system().lower(),
|
|
38
|
+
"Bird-Arch": platform.machine().lower(),
|
|
39
|
+
}
|
|
40
|
+
|
|
30
41
|
_MUTATING_METHODS = {"POST", "PUT", "PATCH", "DELETE"}
|
|
31
|
-
# SDK-owned headers a caller's extra_headers must never override.
|
|
32
|
-
|
|
42
|
+
# SDK-owned headers a caller's extra_headers must never override. Derived from
|
|
43
|
+
# _CLIENT_HEADERS so the two can't drift; matched case-insensitively.
|
|
44
|
+
_RESERVED_HEADERS = {"authorization", "user-agent", "x-bird-api-version", "idempotency-key"} | {
|
|
45
|
+
key.lower() for key in _CLIENT_HEADERS
|
|
46
|
+
}
|
|
33
47
|
|
|
34
48
|
# Bound to the concrete client so `with`/`async with` preserve the subclass type
|
|
35
49
|
# (e.g. `with Bird(...) as c` keeps `c` typed as Bird, not SyncAPIClient).
|
|
@@ -83,6 +97,7 @@ class BaseClient:
|
|
|
83
97
|
headers[key] = value
|
|
84
98
|
headers["Authorization"] = f"Bearer {self.api_key}"
|
|
85
99
|
headers["User-Agent"] = USER_AGENT
|
|
100
|
+
headers.update(_CLIENT_HEADERS)
|
|
86
101
|
if self.api_version:
|
|
87
102
|
headers["X-Bird-API-Version"] = self.api_version
|
|
88
103
|
if idempotency_key:
|
|
@@ -95,12 +95,10 @@ class Bird(SyncAPIClient):
|
|
|
95
95
|
(`with Bird(...) as client`) to close the underlying HTTP client.
|
|
96
96
|
|
|
97
97
|
```python
|
|
98
|
-
# bird:snippet:start client.verbs
|
|
99
98
|
from bird import EmailMessage
|
|
100
99
|
|
|
101
100
|
message = client.get("/v1/email/messages/em_01krd...", cast_to=EmailMessage)
|
|
102
101
|
client.post("/v1/some/new/endpoint", body={"key": "value"})
|
|
103
|
-
# bird:snippet:end client.verbs
|
|
104
102
|
```
|
|
105
103
|
|
|
106
104
|
A single `Bird` instance is safe to share across threads — the `httpx` client
|
|
@@ -18,6 +18,7 @@ class WebhookEventType:
|
|
|
18
18
|
DOMAIN_VERIFIED: Final = "domain.verified"
|
|
19
19
|
EMAIL_ACCEPTED: Final = "email.accepted"
|
|
20
20
|
EMAIL_BOUNCED: Final = "email.bounced"
|
|
21
|
+
EMAIL_CANCELED: Final = "email.canceled"
|
|
21
22
|
EMAIL_CLICKED: Final = "email.clicked"
|
|
22
23
|
EMAIL_COMPLAINED: Final = "email.complained"
|
|
23
24
|
EMAIL_DEFERRED: Final = "email.deferred"
|
|
@@ -28,6 +29,7 @@ class WebhookEventType:
|
|
|
28
29
|
EMAIL_PROCESSED: Final = "email.processed"
|
|
29
30
|
EMAIL_RECEIVED: Final = "email.received"
|
|
30
31
|
EMAIL_REJECTED: Final = "email.rejected"
|
|
32
|
+
EMAIL_SCHEDULED: Final = "email.scheduled"
|
|
31
33
|
EMAIL_SUPPRESSION_CREATED: Final = "email_suppression.created"
|
|
32
34
|
EMAIL_UNSUBSCRIBED: Final = "email.unsubscribed"
|
|
33
35
|
SMS_ACCEPTED: Final = "sms.accepted"
|
|
@@ -48,6 +48,16 @@ class ErrorDetail:
|
|
|
48
48
|
message: str
|
|
49
49
|
|
|
50
50
|
|
|
51
|
+
@dataclass(frozen=True)
|
|
52
|
+
class ErrorNextAction:
|
|
53
|
+
"""One recovery operation the server suggests (ADR-0073): call it to resolve
|
|
54
|
+
the error, then retry the original request."""
|
|
55
|
+
|
|
56
|
+
operation: str
|
|
57
|
+
description: str | None = None
|
|
58
|
+
scope: str | None = None
|
|
59
|
+
|
|
60
|
+
|
|
51
61
|
class BirdError(Exception):
|
|
52
62
|
"""Base class for every error raised by the SDK."""
|
|
53
63
|
|
|
@@ -101,6 +111,8 @@ class APIStatusError(APIError):
|
|
|
101
111
|
request_id: str | None = None,
|
|
102
112
|
param: str | None = None,
|
|
103
113
|
vendor_code: str | None = None,
|
|
114
|
+
remediation: str | None = None,
|
|
115
|
+
next: list[ErrorNextAction] | None = None,
|
|
104
116
|
) -> None:
|
|
105
117
|
super().__init__(message)
|
|
106
118
|
self.status_code = status_code
|
|
@@ -111,6 +123,8 @@ class APIStatusError(APIError):
|
|
|
111
123
|
self.request_id = request_id
|
|
112
124
|
self.param = param
|
|
113
125
|
self.vendor_code = vendor_code
|
|
126
|
+
self.remediation = remediation
|
|
127
|
+
self.next = next or []
|
|
114
128
|
|
|
115
129
|
def __str__(self) -> str:
|
|
116
130
|
return f"{self.message} (status {self.status_code}, type {self.type}, request_id {self.request_id})"
|
|
@@ -196,6 +210,16 @@ def from_response(status_code: int, body: bytes | str, headers: Mapping[str, str
|
|
|
196
210
|
"request_id": request_id,
|
|
197
211
|
"param": data.get("param"),
|
|
198
212
|
"vendor_code": data.get("vendor_code"),
|
|
213
|
+
"remediation": data.get("remediation"),
|
|
214
|
+
"next": [
|
|
215
|
+
ErrorNextAction(
|
|
216
|
+
operation=n.get("operation", ""),
|
|
217
|
+
description=n.get("description"),
|
|
218
|
+
scope=n.get("scope"),
|
|
219
|
+
)
|
|
220
|
+
for n in (data.get("next") or []) # `or []` handles both an absent key and an explicit null
|
|
221
|
+
if isinstance(n, dict)
|
|
222
|
+
],
|
|
199
223
|
}
|
|
200
224
|
|
|
201
225
|
if type_ == ErrorType.rate_limit:
|
|
@@ -25,6 +25,33 @@ class ErrorDetail(BaseModel):
|
|
|
25
25
|
]
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
class ErrorNextAction(BaseModel):
|
|
29
|
+
model_config = ConfigDict(
|
|
30
|
+
extra='allow',
|
|
31
|
+
)
|
|
32
|
+
operation: Annotated[
|
|
33
|
+
str,
|
|
34
|
+
Field(
|
|
35
|
+
description='The operationId of a follow-up operation that resolves this error. Call it, then retry the original request.',
|
|
36
|
+
min_length=1,
|
|
37
|
+
),
|
|
38
|
+
]
|
|
39
|
+
description: Annotated[
|
|
40
|
+
str | None,
|
|
41
|
+
Field(
|
|
42
|
+
description='Short human-readable label for the recovery step.',
|
|
43
|
+
min_length=1,
|
|
44
|
+
),
|
|
45
|
+
] = None
|
|
46
|
+
scope: Annotated[
|
|
47
|
+
str | None,
|
|
48
|
+
Field(
|
|
49
|
+
description='The permission scope the recovery operation requires, when it is scoped. Omitted for operations that need no scope.',
|
|
50
|
+
min_length=1,
|
|
51
|
+
),
|
|
52
|
+
] = None
|
|
53
|
+
|
|
54
|
+
|
|
28
55
|
class Type(str, Enum):
|
|
29
56
|
auth_error = 'auth_error'
|
|
30
57
|
bad_request_error = 'bad_request_error'
|
|
@@ -107,6 +134,19 @@ class ErrorBody(BaseModel):
|
|
|
107
134
|
description='Per-field validation errors. Present only on validation_error responses.'
|
|
108
135
|
),
|
|
109
136
|
] = None
|
|
137
|
+
remediation: Annotated[
|
|
138
|
+
str | None,
|
|
139
|
+
Field(
|
|
140
|
+
description='A human-readable next step to resolve this error. Present when a recovery is known.',
|
|
141
|
+
min_length=1,
|
|
142
|
+
),
|
|
143
|
+
] = None
|
|
144
|
+
next: Annotated[
|
|
145
|
+
list[ErrorNextAction] | None,
|
|
146
|
+
Field(
|
|
147
|
+
description='Operations that resolve this error, in the order to try them. Present for errors with a well-defined recovery, such as unmet preconditions and conflicts.'
|
|
148
|
+
),
|
|
149
|
+
] = None
|
|
110
150
|
|
|
111
151
|
|
|
112
152
|
class Error(BaseModel):
|
|
@@ -250,6 +290,7 @@ class Category(str, Enum):
|
|
|
250
290
|
|
|
251
291
|
|
|
252
292
|
class Status(str, Enum):
|
|
293
|
+
scheduled = 'scheduled'
|
|
253
294
|
accepted = 'accepted'
|
|
254
295
|
processed = 'processed'
|
|
255
296
|
deferred = 'deferred'
|
|
@@ -258,6 +299,7 @@ class Status(str, Enum):
|
|
|
258
299
|
bounced = 'bounced'
|
|
259
300
|
complained = 'complained'
|
|
260
301
|
rejected = 'rejected'
|
|
302
|
+
canceled = 'canceled'
|
|
261
303
|
|
|
262
304
|
|
|
263
305
|
class EmailMessage(BaseModel):
|
|
@@ -309,7 +351,7 @@ class EmailMessage(BaseModel):
|
|
|
309
351
|
status: Annotated[
|
|
310
352
|
Status,
|
|
311
353
|
Field(
|
|
312
|
-
description="Aggregate delivery status derived from recipient states. `accepted` means Bird has the send and is preparing to deliver. `processed` means Bird has processed the message and queued it for delivery to the recipient's mail server.\n"
|
|
354
|
+
description="Aggregate delivery status derived from recipient states. `scheduled` means the message is queued to send at a future time and has not been dispatched yet. `accepted` means Bird has the send and is preparing to deliver. `processed` means Bird has processed the message and queued it for delivery to the recipient's mail server. `canceled` means a scheduled message was canceled before it was sent.\n"
|
|
313
355
|
),
|
|
314
356
|
]
|
|
315
357
|
accepted_count: Annotated[
|
|
@@ -421,6 +463,12 @@ class EmailMessage(BaseModel):
|
|
|
421
463
|
description='When all recipients reached a terminal delivered state, or null if not yet fully delivered.'
|
|
422
464
|
),
|
|
423
465
|
] = None
|
|
466
|
+
scheduled_at: Annotated[
|
|
467
|
+
str | None,
|
|
468
|
+
Field(
|
|
469
|
+
description='When this message is scheduled to send, for a send created with a future send time. Null for an immediate send. Stays set after the scheduled send fires.'
|
|
470
|
+
),
|
|
471
|
+
] = None
|
|
424
472
|
|
|
425
473
|
|
|
426
474
|
class EmailMessageList(FieldListEnvelope):
|
|
@@ -959,6 +1007,72 @@ class EventEmailBounced(BaseModel):
|
|
|
959
1007
|
data: EventEmailBouncedData
|
|
960
1008
|
|
|
961
1009
|
|
|
1010
|
+
class EventEmailMessageBase(BaseModel):
|
|
1011
|
+
model_config = ConfigDict(
|
|
1012
|
+
extra='allow',
|
|
1013
|
+
)
|
|
1014
|
+
email_id: Annotated[
|
|
1015
|
+
str,
|
|
1016
|
+
Field(
|
|
1017
|
+
description='ID of the email send.',
|
|
1018
|
+
examples=['em_01krdgeqcxet5s7t44vh8rt9mg'],
|
|
1019
|
+
min_length=1,
|
|
1020
|
+
pattern='^em_[0-9a-hjkmnp-tv-z]{26}$',
|
|
1021
|
+
),
|
|
1022
|
+
]
|
|
1023
|
+
workspace_id: Annotated[
|
|
1024
|
+
str,
|
|
1025
|
+
Field(
|
|
1026
|
+
description='ID of the workspace.',
|
|
1027
|
+
examples=['ws_01krdgeqcxet5s7t44vh8rt9mg'],
|
|
1028
|
+
min_length=1,
|
|
1029
|
+
pattern='^ws_[0-9a-hjkmnp-tv-z]{26}$',
|
|
1030
|
+
),
|
|
1031
|
+
]
|
|
1032
|
+
tags: Annotated[
|
|
1033
|
+
list[EmailTag] | None,
|
|
1034
|
+
Field(
|
|
1035
|
+
description='Tags provided on the send request, echoed on the event so you can route and correlate without an extra lookup. Null when the send carried no tags.\n'
|
|
1036
|
+
),
|
|
1037
|
+
]
|
|
1038
|
+
metadata: Annotated[
|
|
1039
|
+
dict[str, Any] | None,
|
|
1040
|
+
Field(
|
|
1041
|
+
description='The metadata object provided on the send request, echoed on the event so you can correlate events with your own records. Null when the send carried no metadata.\n',
|
|
1042
|
+
examples=[{'order_id': 'ord_123'}],
|
|
1043
|
+
),
|
|
1044
|
+
]
|
|
1045
|
+
|
|
1046
|
+
|
|
1047
|
+
class EventEmailCanceledData(EventEmailMessageBase):
|
|
1048
|
+
model_config = ConfigDict(
|
|
1049
|
+
extra='allow',
|
|
1050
|
+
)
|
|
1051
|
+
|
|
1052
|
+
|
|
1053
|
+
class Type5(str, Enum):
|
|
1054
|
+
email_canceled = 'email.canceled'
|
|
1055
|
+
|
|
1056
|
+
|
|
1057
|
+
class EventEmailCanceled(BaseModel):
|
|
1058
|
+
model_config = ConfigDict(
|
|
1059
|
+
extra='allow',
|
|
1060
|
+
)
|
|
1061
|
+
type: Annotated[
|
|
1062
|
+
Literal['email.canceled'],
|
|
1063
|
+
Field(description='Event type.', examples=['email.canceled']),
|
|
1064
|
+
]
|
|
1065
|
+
timestamp: Annotated[
|
|
1066
|
+
str,
|
|
1067
|
+
Field(
|
|
1068
|
+
description='Time the scheduled send was canceled.',
|
|
1069
|
+
examples=['2026-05-21 12:00:00+00:00'],
|
|
1070
|
+
min_length=1,
|
|
1071
|
+
),
|
|
1072
|
+
]
|
|
1073
|
+
data: EventEmailCanceledData
|
|
1074
|
+
|
|
1075
|
+
|
|
962
1076
|
class EventEmailClickedData(EventEmailBase):
|
|
963
1077
|
model_config = ConfigDict(
|
|
964
1078
|
extra='allow',
|
|
@@ -987,7 +1101,7 @@ class EventEmailClickedData(EventEmailBase):
|
|
|
987
1101
|
]
|
|
988
1102
|
|
|
989
1103
|
|
|
990
|
-
class
|
|
1104
|
+
class Type6(str, Enum):
|
|
991
1105
|
email_clicked = 'email.clicked'
|
|
992
1106
|
|
|
993
1107
|
|
|
@@ -1023,7 +1137,7 @@ class EventEmailComplainedData(EventEmailBase):
|
|
|
1023
1137
|
]
|
|
1024
1138
|
|
|
1025
1139
|
|
|
1026
|
-
class
|
|
1140
|
+
class Type7(str, Enum):
|
|
1027
1141
|
email_complained = 'email.complained'
|
|
1028
1142
|
|
|
1029
1143
|
|
|
@@ -1076,7 +1190,7 @@ class EventEmailDeferredData(EventEmailBase):
|
|
|
1076
1190
|
]
|
|
1077
1191
|
|
|
1078
1192
|
|
|
1079
|
-
class
|
|
1193
|
+
class Type8(str, Enum):
|
|
1080
1194
|
email_deferred = 'email.deferred'
|
|
1081
1195
|
|
|
1082
1196
|
|
|
@@ -1105,7 +1219,7 @@ class EventEmailDeliveredData(EventEmailBase):
|
|
|
1105
1219
|
)
|
|
1106
1220
|
|
|
1107
1221
|
|
|
1108
|
-
class
|
|
1222
|
+
class Type9(str, Enum):
|
|
1109
1223
|
email_delivered = 'email.delivered'
|
|
1110
1224
|
|
|
1111
1225
|
|
|
@@ -1134,7 +1248,7 @@ class EventEmailListUnsubscribedData(EventEmailBase):
|
|
|
1134
1248
|
)
|
|
1135
1249
|
|
|
1136
1250
|
|
|
1137
|
-
class
|
|
1251
|
+
class Type10(str, Enum):
|
|
1138
1252
|
email_list_unsubscribed = 'email.list_unsubscribed'
|
|
1139
1253
|
|
|
1140
1254
|
|
|
@@ -1177,7 +1291,7 @@ class EventEmailOpenedData(EventEmailBase):
|
|
|
1177
1291
|
]
|
|
1178
1292
|
|
|
1179
1293
|
|
|
1180
|
-
class
|
|
1294
|
+
class Type11(str, Enum):
|
|
1181
1295
|
email_opened = 'email.opened'
|
|
1182
1296
|
|
|
1183
1297
|
|
|
@@ -1237,7 +1351,7 @@ class EventEmailOutOfBandBounceData(EventEmailBase):
|
|
|
1237
1351
|
]
|
|
1238
1352
|
|
|
1239
1353
|
|
|
1240
|
-
class
|
|
1354
|
+
class Type12(str, Enum):
|
|
1241
1355
|
email_out_of_band_bounce = 'email.out_of_band_bounce'
|
|
1242
1356
|
|
|
1243
1357
|
|
|
@@ -1266,7 +1380,7 @@ class EventEmailProcessedData(EventEmailBase):
|
|
|
1266
1380
|
)
|
|
1267
1381
|
|
|
1268
1382
|
|
|
1269
|
-
class
|
|
1383
|
+
class Type13(str, Enum):
|
|
1270
1384
|
email_processed = 'email.processed'
|
|
1271
1385
|
|
|
1272
1386
|
|
|
@@ -1374,7 +1488,7 @@ class EventEmailReceivedData(BaseModel):
|
|
|
1374
1488
|
] = None
|
|
1375
1489
|
|
|
1376
1490
|
|
|
1377
|
-
class
|
|
1491
|
+
class Type14(str, Enum):
|
|
1378
1492
|
email_received = 'email.received'
|
|
1379
1493
|
|
|
1380
1494
|
|
|
@@ -1402,6 +1516,9 @@ class EmailRejectionReason(str, Enum):
|
|
|
1402
1516
|
transmission_failed = 'transmission_failed'
|
|
1403
1517
|
generation_failure = 'generation_failure'
|
|
1404
1518
|
policy_rejection = 'policy_rejection'
|
|
1519
|
+
domain_unverified = 'domain_unverified'
|
|
1520
|
+
quota_exceeded = 'quota_exceeded'
|
|
1521
|
+
recipient_not_allowed = 'recipient_not_allowed'
|
|
1405
1522
|
|
|
1406
1523
|
|
|
1407
1524
|
class EventEmailRejectedData(EventEmailBase):
|
|
@@ -1411,7 +1528,7 @@ class EventEmailRejectedData(EventEmailBase):
|
|
|
1411
1528
|
rejection_reason: EmailRejectionReason
|
|
1412
1529
|
|
|
1413
1530
|
|
|
1414
|
-
class
|
|
1531
|
+
class Type15(str, Enum):
|
|
1415
1532
|
email_rejected = 'email.rejected'
|
|
1416
1533
|
|
|
1417
1534
|
|
|
@@ -1434,13 +1551,50 @@ class EventEmailRejected(BaseModel):
|
|
|
1434
1551
|
data: EventEmailRejectedData
|
|
1435
1552
|
|
|
1436
1553
|
|
|
1554
|
+
class EventEmailScheduledData(EventEmailMessageBase):
|
|
1555
|
+
model_config = ConfigDict(
|
|
1556
|
+
extra='allow',
|
|
1557
|
+
)
|
|
1558
|
+
scheduled_at: Annotated[
|
|
1559
|
+
str,
|
|
1560
|
+
Field(
|
|
1561
|
+
description='When the message is scheduled to send.',
|
|
1562
|
+
examples=['2026-05-22 09:00:00+00:00'],
|
|
1563
|
+
min_length=1,
|
|
1564
|
+
),
|
|
1565
|
+
]
|
|
1566
|
+
|
|
1567
|
+
|
|
1568
|
+
class Type16(str, Enum):
|
|
1569
|
+
email_scheduled = 'email.scheduled'
|
|
1570
|
+
|
|
1571
|
+
|
|
1572
|
+
class EventEmailScheduled(BaseModel):
|
|
1573
|
+
model_config = ConfigDict(
|
|
1574
|
+
extra='allow',
|
|
1575
|
+
)
|
|
1576
|
+
type: Annotated[
|
|
1577
|
+
Literal['email.scheduled'],
|
|
1578
|
+
Field(description='Event type.', examples=['email.scheduled']),
|
|
1579
|
+
]
|
|
1580
|
+
timestamp: Annotated[
|
|
1581
|
+
str,
|
|
1582
|
+
Field(
|
|
1583
|
+
description='Time the send was scheduled.',
|
|
1584
|
+
examples=['2026-05-21 12:00:00+00:00'],
|
|
1585
|
+
min_length=1,
|
|
1586
|
+
),
|
|
1587
|
+
]
|
|
1588
|
+
data: EventEmailScheduledData
|
|
1589
|
+
|
|
1590
|
+
|
|
1437
1591
|
class EventEmailUnsubscribedData(EventEmailBase):
|
|
1438
1592
|
model_config = ConfigDict(
|
|
1439
1593
|
extra='allow',
|
|
1440
1594
|
)
|
|
1441
1595
|
|
|
1442
1596
|
|
|
1443
|
-
class
|
|
1597
|
+
class Type17(str, Enum):
|
|
1444
1598
|
email_unsubscribed = 'email.unsubscribed'
|
|
1445
1599
|
|
|
1446
1600
|
|
|
@@ -1463,7 +1617,7 @@ class EventEmailUnsubscribed(BaseModel):
|
|
|
1463
1617
|
data: EventEmailUnsubscribedData
|
|
1464
1618
|
|
|
1465
1619
|
|
|
1466
|
-
class
|
|
1620
|
+
class Type18(str, Enum):
|
|
1467
1621
|
email_suppression_created = 'email_suppression.created'
|
|
1468
1622
|
|
|
1469
1623
|
|
|
@@ -1552,7 +1706,7 @@ class EventSMSAcceptedData(EventSMSBase):
|
|
|
1552
1706
|
)
|
|
1553
1707
|
|
|
1554
1708
|
|
|
1555
|
-
class
|
|
1709
|
+
class Type19(str, Enum):
|
|
1556
1710
|
sms_accepted = 'sms.accepted'
|
|
1557
1711
|
|
|
1558
1712
|
|
|
@@ -1595,7 +1749,7 @@ class EventSMSDeliveredData(EventSMSBase):
|
|
|
1595
1749
|
]
|
|
1596
1750
|
|
|
1597
1751
|
|
|
1598
|
-
class
|
|
1752
|
+
class Type20(str, Enum):
|
|
1599
1753
|
sms_delivered = 'sms.delivered'
|
|
1600
1754
|
|
|
1601
1755
|
|
|
@@ -1624,7 +1778,7 @@ class EventSMSExpiredData(EventSMSBase):
|
|
|
1624
1778
|
)
|
|
1625
1779
|
|
|
1626
1780
|
|
|
1627
|
-
class
|
|
1781
|
+
class Type21(str, Enum):
|
|
1628
1782
|
sms_expired = 'sms.expired'
|
|
1629
1783
|
|
|
1630
1784
|
|
|
@@ -1656,7 +1810,7 @@ class EventSMSFailedData(EventSMSBase):
|
|
|
1656
1810
|
]
|
|
1657
1811
|
|
|
1658
1812
|
|
|
1659
|
-
class
|
|
1813
|
+
class Type22(str, Enum):
|
|
1660
1814
|
sms_failed = 'sms.failed'
|
|
1661
1815
|
|
|
1662
1816
|
|
|
@@ -1688,7 +1842,7 @@ class EventSMSRejectedData(EventSMSBase):
|
|
|
1688
1842
|
]
|
|
1689
1843
|
|
|
1690
1844
|
|
|
1691
|
-
class
|
|
1845
|
+
class Type23(str, Enum):
|
|
1692
1846
|
sms_rejected = 'sms.rejected'
|
|
1693
1847
|
|
|
1694
1848
|
|
|
@@ -1731,7 +1885,7 @@ class EventSMSSentData(EventSMSBase):
|
|
|
1731
1885
|
]
|
|
1732
1886
|
|
|
1733
1887
|
|
|
1734
|
-
class
|
|
1888
|
+
class Type24(str, Enum):
|
|
1735
1889
|
sms_sent = 'sms.sent'
|
|
1736
1890
|
|
|
1737
1891
|
|
|
@@ -1762,7 +1916,7 @@ class EventSMSUndeliveredData(EventSMSBase):
|
|
|
1762
1916
|
]
|
|
1763
1917
|
|
|
1764
1918
|
|
|
1765
|
-
class
|
|
1919
|
+
class Type25(str, Enum):
|
|
1766
1920
|
sms_undelivered = 'sms.undelivered'
|
|
1767
1921
|
|
|
1768
1922
|
|
|
@@ -1791,6 +1945,7 @@ class WebhookEvent(
|
|
|
1791
1945
|
| EventDomainVerified
|
|
1792
1946
|
| EventEmailAccepted
|
|
1793
1947
|
| EventEmailBounced
|
|
1948
|
+
| EventEmailCanceled
|
|
1794
1949
|
| EventEmailClicked
|
|
1795
1950
|
| EventEmailComplained
|
|
1796
1951
|
| EventEmailDeferred
|
|
@@ -1801,6 +1956,7 @@ class WebhookEvent(
|
|
|
1801
1956
|
| EventEmailProcessed
|
|
1802
1957
|
| EventEmailReceived
|
|
1803
1958
|
| EventEmailRejected
|
|
1959
|
+
| EventEmailScheduled
|
|
1804
1960
|
| EventEmailUnsubscribed
|
|
1805
1961
|
| EventEmailSuppressionCreated
|
|
1806
1962
|
| EventSMSAccepted
|
|
@@ -1817,6 +1973,7 @@ class WebhookEvent(
|
|
|
1817
1973
|
| EventDomainVerified
|
|
1818
1974
|
| EventEmailAccepted
|
|
1819
1975
|
| EventEmailBounced
|
|
1976
|
+
| EventEmailCanceled
|
|
1820
1977
|
| EventEmailClicked
|
|
1821
1978
|
| EventEmailComplained
|
|
1822
1979
|
| EventEmailDeferred
|
|
@@ -1827,6 +1984,7 @@ class WebhookEvent(
|
|
|
1827
1984
|
| EventEmailProcessed
|
|
1828
1985
|
| EventEmailReceived
|
|
1829
1986
|
| EventEmailRejected
|
|
1987
|
+
| EventEmailScheduled
|
|
1830
1988
|
| EventEmailUnsubscribed
|
|
1831
1989
|
| EventEmailSuppressionCreated
|
|
1832
1990
|
| EventSMSAccepted
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.2"
|
|
@@ -182,7 +182,6 @@ class Email:
|
|
|
182
182
|
the server accepts both plain addresses and RFC 5322 mailbox form and normalises them.
|
|
183
183
|
|
|
184
184
|
```python
|
|
185
|
-
# bird:snippet:start email.send
|
|
186
185
|
msg = client.email.send(
|
|
187
186
|
from_={"email": "onboarding@messagebird.dev", "name": "Bird"},
|
|
188
187
|
to=["delivered@messagebird.dev"],
|
|
@@ -190,13 +189,11 @@ class Email:
|
|
|
190
189
|
html="<p>My first Bird email.</p>",
|
|
191
190
|
)
|
|
192
191
|
print(msg.id, msg.status)
|
|
193
|
-
# bird:snippet:end email.send
|
|
194
192
|
```
|
|
195
193
|
|
|
196
194
|
Pass ``options`` for per-call overrides (``timeout``, ``idempotency_key``, …):
|
|
197
195
|
|
|
198
196
|
```python
|
|
199
|
-
# bird:snippet:start email.options
|
|
200
197
|
client.email.send(
|
|
201
198
|
from_={"email": "onboarding@messagebird.dev", "name": "Bird"},
|
|
202
199
|
to=["delivered@messagebird.dev"],
|
|
@@ -204,13 +201,11 @@ class Email:
|
|
|
204
201
|
text="My first Bird email.",
|
|
205
202
|
options={"timeout": 10, "max_retries": 0},
|
|
206
203
|
)
|
|
207
|
-
# bird:snippet:end email.options
|
|
208
204
|
```
|
|
209
205
|
|
|
210
206
|
A failure raises a typed ``APIError``; catch the subclasses you act on:
|
|
211
207
|
|
|
212
208
|
```python
|
|
213
|
-
# bird:snippet:start email.errors
|
|
214
209
|
from bird import APIStatusError, RateLimitError, ValidationError
|
|
215
210
|
|
|
216
211
|
try:
|
|
@@ -226,7 +221,6 @@ class Email:
|
|
|
226
221
|
print(err.status_code, err.details)
|
|
227
222
|
except APIStatusError as err:
|
|
228
223
|
print(err.status_code, err.code, err.request_id)
|
|
229
|
-
# bird:snippet:end email.errors
|
|
230
224
|
```
|
|
231
225
|
"""
|
|
232
226
|
body = _send_body(
|
|
@@ -254,7 +248,6 @@ class Email:
|
|
|
254
248
|
value always wins — the same merge :meth:`send` applies.
|
|
255
249
|
|
|
256
250
|
```python
|
|
257
|
-
# bird:snippet:start email.sendBatch
|
|
258
251
|
batch = client.email.send_batch(
|
|
259
252
|
messages=[
|
|
260
253
|
{
|
|
@@ -273,7 +266,6 @@ class Email:
|
|
|
273
266
|
)
|
|
274
267
|
for item in batch.data:
|
|
275
268
|
print(item.id, item.status)
|
|
276
|
-
# bird:snippet:end email.sendBatch
|
|
277
269
|
```
|
|
278
270
|
"""
|
|
279
271
|
body = _batch_body(messages, self._defaults)
|
|
@@ -303,14 +295,10 @@ class Email:
|
|
|
303
295
|
"""List messages, newest first; iterate the page to auto-paginate.
|
|
304
296
|
|
|
305
297
|
```python
|
|
306
|
-
# bird:snippet:start email.list.paginate
|
|
307
|
-
# bird:snippet:start email.list.iterate
|
|
308
298
|
for message in client.email.list(status="delivered"):
|
|
309
299
|
print(message.id)
|
|
310
|
-
# bird:snippet:end email.list.iterate
|
|
311
300
|
page = client.email.list(status="delivered") # page.data, page.next_cursor
|
|
312
301
|
print(len(page.data), page.next_cursor)
|
|
313
|
-
# bird:snippet:end email.list.paginate
|
|
314
302
|
```
|
|
315
303
|
"""
|
|
316
304
|
query = _list_query({
|
|
@@ -325,13 +313,11 @@ class AsyncEmail:
|
|
|
325
313
|
"""Async mirror of `Email`: ``await`` each call, ``async for`` over a list.
|
|
326
314
|
|
|
327
315
|
```python
|
|
328
|
-
# bird:snippet:start email.list.async
|
|
329
316
|
from bird import AsyncBird
|
|
330
317
|
|
|
331
318
|
async with AsyncBird() as client:
|
|
332
319
|
async for message in client.email.list(status="delivered"):
|
|
333
320
|
print(message.id)
|
|
334
|
-
# bird:snippet:end email.list.async
|
|
335
321
|
```
|
|
336
322
|
"""
|
|
337
323
|
|
|
@@ -119,12 +119,10 @@ class Webhooks:
|
|
|
119
119
|
missing headers.
|
|
120
120
|
|
|
121
121
|
```python
|
|
122
|
-
# bird:snippet:start webhook.unwrap
|
|
123
122
|
# Pass the RAW request body (bytes) and the request headers.
|
|
124
123
|
event = client.webhooks.unwrap(request.body, request.headers)
|
|
125
124
|
if event.root.type == "email.delivered":
|
|
126
125
|
print(event.root.data.email_id)
|
|
127
|
-
# bird:snippet:end webhook.unwrap
|
|
128
126
|
```
|
|
129
127
|
"""
|
|
130
128
|
return _verify_and_parse(payload, headers, secret or self._secret, tolerance)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.2.0"
|
|
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
|