Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,9 +224,10 @@ sit alongside it. Transient failures are retried before they ever reach you (see
## Messages

```python
# One page (newest first). Filter by status, channel, template, or metadata.
# One page (newest first). Filter by status, channel, template, or metadata,
# or search (substring over id, recipient, template slug, and metadata).
page = sk.messages.list(status="delivered", channel="email", limit=50,
metadata={"user_id": "usr_123"})
metadata={"user_id": "usr_123"}, search="user@example.com")
for m in page.data:
print(m.public_id, m.status)
print(page.next_cursor) # pass as cursor= for the next page, or None when done
Expand Down
2 changes: 2 additions & 0 deletions src/senderkit/_serialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ def list_messages_query(
template: Optional[str],
metadata: Optional[Dict[str, Any]],
tail: Optional[str],
search: Optional[str] = None,
) -> Dict[str, Any]:
"""Build the query dict for ``GET /v1/messages``, including ``metadata[key]``."""
query: Dict[str, Any] = _prune(
Expand All @@ -178,6 +179,7 @@ def list_messages_query(
"status": status.value if isinstance(status, Channel) else status,
"channel": _channel_value(channel),
"template": template,
"search": search,
"tail": tail,
}
)
Expand Down
15 changes: 14 additions & 1 deletion src/senderkit/resources/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,23 @@ def list(
channel: Optional[ChannelLike] = None,
template: Optional[str] = None,
metadata: Optional[Dict[str, Any]] = None,
search: Optional[str] = None,
tail: Optional[str] = None,
) -> MessageList:
"""Return one page of messages, newest first, with a cursor for the next."""
"""Return one page of messages, newest first, with a cursor for the next.

``search`` is a case-insensitive substring match over a message's public
id, recipient, template slug, and metadata keys/values; use ``metadata``
for an exact match. Capped at 512 characters by the API.
"""
query = list_messages_query(
limit=limit,
cursor=cursor,
status=status,
channel=channel,
template=template,
metadata=metadata,
search=search,
tail=tail,
)
return MessageList.from_dict(self._t.request_json("GET", "/v1/messages", query=query))
Expand All @@ -46,6 +53,7 @@ def iter(
channel: Optional[ChannelLike] = None,
template: Optional[str] = None,
metadata: Optional[Dict[str, Any]] = None,
search: Optional[str] = None,
) -> Iterator[Message]:
"""Yield every matching message, following ``next_cursor`` across pages."""
cursor: Optional[str] = None
Expand All @@ -57,6 +65,7 @@ def iter(
channel=channel,
template=template,
metadata=metadata,
search=search,
)
yield from page.data
if not page.next_cursor:
Expand Down Expand Up @@ -87,6 +96,7 @@ async def list(
channel: Optional[ChannelLike] = None,
template: Optional[str] = None,
metadata: Optional[Dict[str, Any]] = None,
search: Optional[str] = None,
tail: Optional[str] = None,
) -> MessageList:
query = list_messages_query(
Expand All @@ -96,6 +106,7 @@ async def list(
channel=channel,
template=template,
metadata=metadata,
search=search,
tail=tail,
)
return MessageList.from_dict(await self._t.request_json("GET", "/v1/messages", query=query))
Expand All @@ -108,6 +119,7 @@ async def aiter(
channel: Optional[ChannelLike] = None,
template: Optional[str] = None,
metadata: Optional[Dict[str, Any]] = None,
search: Optional[str] = None,
) -> AsyncIterator[Message]:
cursor: Optional[str] = None
while True:
Expand All @@ -118,6 +130,7 @@ async def aiter(
channel=channel,
template=template,
metadata=metadata,
search=search,
)
for message in page.data:
yield message
Expand Down
9 changes: 9 additions & 0 deletions tests/test_messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ def test_list_with_filters_builds_query(client):
assert params["metadata[userId]"] == "usr_123"


@respx.mock
def test_list_forwards_search(client):
route = respx.get(f"{BASE_URL}/v1/messages").mock(
return_value=httpx.Response(200, json={"data": [], "nextCursor": None})
)
client.messages.list(search="user@example.com")
assert route.calls.last.request.url.params["search"] == "user@example.com"


@respx.mock
def test_iter_paginates(client):
respx.get(f"{BASE_URL}/v1/messages").mock(
Expand Down