Skip to content
4 changes: 4 additions & 0 deletions src/textbee/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
Direction,
Message,
MessageDevice,
MessageIssueEntry,
MessageIssueSource,
MessageList,
MessageListMeta,
MessageStatus,
Expand All @@ -39,6 +41,8 @@
"Direction",
"Message",
"MessageDevice",
"MessageIssueEntry",
"MessageIssueSource",
"MessageList",
"MessageListMeta",
"MessageStatus",
Expand Down
45 changes: 45 additions & 0 deletions src/textbee/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,32 @@ class MessageDevice(_MessageDeviceRequired, total=False):
enabled: bool


MessageIssueSource = Literal["push", "device", "server"]
"""Where a failure was reported from."""


class _MessageIssueEntryRequired(TypedDict):
code: str
at: str
"""When the failure happened. ISO date."""

source: MessageIssueSource


class MessageIssueEntry(_MessageIssueEntryRequired, total=False):
"""One failure a message had. Kept after the message later succeeds."""

message: str
backfilled: bool
"""Set on entries rebuilt from old message fields."""

deliveryReport: bool
"""True for a delivery report that did not confirm delivery.

The message was sent; this is not a send failure.
"""


class _MessageRequired(TypedDict):
_id: str
message: str
Expand All @@ -142,9 +168,28 @@ class _MessageFields(_MessageRequired, total=False):
sentAt: str
deliveredAt: str
failedAt: str
"""When the message failed. Present only while status is ``failed``."""

receivedAt: str
errorCode: str
"""Current failure code. Present only while status is ``failed``.

Test accounts can keep ``FCM_SEND_SKIPPED``.
"""

errorMessage: str
"""Current failure text.

Present only while status is ``failed``, or as a timeout note on ``unknown``.
"""

issueHistory: List[MessageIssueEntry]
"""Every failure the message had, plus unconfirmed delivery reports.

Delivery reports carry ``deliveryReport: true`` and are not failures.
Oldest first. At most 15: the first 5 and the newest 10.
"""

simSubscriptionId: int
smsBatch: str
createdAt: str
Expand Down
2 changes: 1 addition & 1 deletion src/textbee/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.1.1"
__version__ = "0.2.0"
28 changes: 28 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,34 @@ def test_returns_get_messages_as_data_and_meta_because_it_has_no_outer_wrapper(
assert query["limit"] == "50"
assert "type" not in query

def test_returns_error_history_on_a_message_that_failed_and_later_succeeded(
self, http: FakeUrlopen
) -> None:
entry = {
"code": "FCM_SEND_FAILED",
"message": "push failed",
"at": "2026-10-01T10:00:00.000Z",
"source": "push",
"backfilled": True,
}
page = {
"data": [
{
"_id": "sms-2",
"message": "hi",
"type": "SENT",
"status": "sent",
"issueHistory": [entry],
}
],
"meta": {},
}
http.respond(200, page)

result = client().get_messages()

assert result["data"][0]["issueHistory"] == [entry]

def test_sends_no_query_at_all_for_a_bare_get_messages(self, http: FakeUrlopen) -> None:
http.respond(200, {"data": [], "meta": {}})

Expand Down
Loading