messagebird-sdk 0.2.1__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.
Files changed (22) hide show
  1. {messagebird_sdk-0.2.1 → messagebird_sdk-0.2.2}/PKG-INFO +1 -1
  2. {messagebird_sdk-0.2.1 → messagebird_sdk-0.2.2}/src/bird/__init__.py +2 -0
  3. {messagebird_sdk-0.2.1 → messagebird_sdk-0.2.2}/src/bird/_base_client.py +6 -6
  4. {messagebird_sdk-0.2.1 → messagebird_sdk-0.2.2}/src/bird/_event_types.py +2 -0
  5. {messagebird_sdk-0.2.1 → messagebird_sdk-0.2.2}/src/bird/_exceptions.py +24 -0
  6. {messagebird_sdk-0.2.1 → messagebird_sdk-0.2.2}/src/bird/_generated.py +178 -20
  7. messagebird_sdk-0.2.2/src/bird/_version.py +1 -0
  8. messagebird_sdk-0.2.1/src/bird/_version.py +0 -1
  9. {messagebird_sdk-0.2.1 → messagebird_sdk-0.2.2}/.gitignore +0 -0
  10. {messagebird_sdk-0.2.1 → messagebird_sdk-0.2.2}/LICENSE +0 -0
  11. {messagebird_sdk-0.2.1 → messagebird_sdk-0.2.2}/README.md +0 -0
  12. {messagebird_sdk-0.2.1 → messagebird_sdk-0.2.2}/pyproject.toml +0 -0
  13. {messagebird_sdk-0.2.1 → messagebird_sdk-0.2.2}/src/bird/_client.py +0 -0
  14. {messagebird_sdk-0.2.1 → messagebird_sdk-0.2.2}/src/bird/_constants.py +0 -0
  15. {messagebird_sdk-0.2.1 → messagebird_sdk-0.2.2}/src/bird/_models.py +0 -0
  16. {messagebird_sdk-0.2.1 → messagebird_sdk-0.2.2}/src/bird/_response.py +0 -0
  17. {messagebird_sdk-0.2.1 → messagebird_sdk-0.2.2}/src/bird/_types.py +0 -0
  18. {messagebird_sdk-0.2.1 → messagebird_sdk-0.2.2}/src/bird/pagination.py +0 -0
  19. {messagebird_sdk-0.2.1 → messagebird_sdk-0.2.2}/src/bird/py.typed +0 -0
  20. {messagebird_sdk-0.2.1 → messagebird_sdk-0.2.2}/src/bird/resources/__init__.py +0 -0
  21. {messagebird_sdk-0.2.1 → messagebird_sdk-0.2.2}/src/bird/resources/email.py +0 -0
  22. {messagebird_sdk-0.2.1 → messagebird_sdk-0.2.2}/src/bird/resources/webhooks.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: messagebird-sdk
3
- Version: 0.2.1
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
@@ -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,15 +27,15 @@ 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
- # X-Bird-* client-identity headers (ADR-0067): the API attributes the SDK
30
+ # Bird-* client-identity headers (ADR-0074): the API attributes the SDK
31
31
  # surface from these, not the User-Agent. Telemetry labels only; computed once.
32
32
  # Keys use the canonical wire casing (matching the Go/TS SDKs).
33
33
  _CLIENT_HEADERS = {
34
- "X-Bird-Surface": "sdk-python",
35
- "X-Bird-Version": __version__,
36
- "X-Bird-Lang": platform.python_implementation().lower(),
37
- "X-Bird-Os": platform.system().lower(),
38
- "X-Bird-Arch": platform.machine().lower(),
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
39
  }
40
40
 
41
41
  _MUTATING_METHODS = {"POST", "PUT", "PATCH", "DELETE"}
@@ -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 Type5(str, Enum):
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 Type6(str, Enum):
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 Type7(str, Enum):
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 Type8(str, Enum):
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 Type9(str, Enum):
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 Type10(str, Enum):
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 Type11(str, Enum):
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 Type12(str, Enum):
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 Type13(str, Enum):
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 Type14(str, Enum):
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 Type15(str, Enum):
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 Type16(str, Enum):
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 Type17(str, Enum):
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 Type18(str, Enum):
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 Type19(str, Enum):
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 Type20(str, Enum):
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 Type21(str, Enum):
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 Type22(str, Enum):
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 Type23(str, Enum):
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"
@@ -1 +0,0 @@
1
- __version__ = "0.2.1"
File without changes