From db0ee979b3d06c495dd7652433f0799519439aa3 Mon Sep 17 00:00:00 2001 From: Bjarn Bronsveld Date: Sat, 5 Sep 2026 13:03:12 +0200 Subject: [PATCH 1/2] feat(api): support quarantined inbound message processing --- src/lettermint/endpoints/api.py | 16 ++++++++++++++++ src/lettermint/types.py | 2 ++ tests/test_api_surface.py | 6 ++++++ 3 files changed, 24 insertions(+) diff --git a/src/lettermint/endpoints/api.py b/src/lettermint/endpoints/api.py index 1b10503..78e730f 100644 --- a/src/lettermint/endpoints/api.py +++ b/src/lettermint/endpoints/api.py @@ -96,6 +96,14 @@ def cancel(self, message_id: str) -> lm_types.RescheduleMessageResponse: ), ) + def process(self, message_id: str) -> dict[str, Any]: + return cast( + dict[str, Any], + self._client.post( + self._path("/messages/{messageId}/process", messageId=message_id), data={} + ), + ) + def events(self, message_id: str, query: Query | None = None) -> lm_types.MessageEventsResponse: return cast( lm_types.MessageEventsResponse, @@ -374,6 +382,14 @@ async def cancel(self, message_id: str) -> lm_types.RescheduleMessageResponse: ), ) + async def process(self, message_id: str) -> dict[str, Any]: + return cast( + dict[str, Any], + await self._client.post( + self._path("/messages/{messageId}/process", messageId=message_id), data={} + ), + ) + async def delete(self, domain_id: str) -> lm_types.DomainDestroyResponse: return cast( lm_types.DomainDestroyResponse, diff --git a/src/lettermint/types.py b/src/lettermint/types.py index 9768eae..7d2c641 100644 --- a/src/lettermint/types.py +++ b/src/lettermint/types.py @@ -10,6 +10,7 @@ "scheduled", "pending", "queued", + "quarantined", "suppressed", "processed", "delivered", @@ -194,6 +195,7 @@ "inbound_received", "inbound_queued", "inbound_spam_blocked", + "inbound_released", "inbound_processed", "inbound_retry", ] diff --git a/tests/test_api_surface.py b/tests/test_api_surface.py index e00bdaf..91746c5 100644 --- a/tests/test_api_surface.py +++ b/tests/test_api_surface.py @@ -186,6 +186,9 @@ def test_scheduled_message_endpoints(self) -> None: 200, json={"message_id": "message/id", "status": "canceled", "scheduled_at": None} ) ) + process_route = respx.post("https://api.lettermint.co/v1/messages/message%2Fid/process").mock( + return_value=Response(202, json={"data": {"message_id": "message/id", "status": "queued", "webhook_target_count": 1}}) + ) with Lettermint.api("api-token") as api: assert ( @@ -195,11 +198,13 @@ def test_scheduled_message_endpoints(self) -> None: == "scheduled" ) assert api.messages.cancel("message/id")["status"] == "canceled" + assert api.messages.process("message/id")["data"]["status"] == "queued" assert json.loads(reschedule_route.calls.last.request.content) == { "scheduled_at": "2026-08-27T09:00:00Z" } assert cancel_route.called + assert process_route.called @respx.mock def test_team_role_and_member_assignment_endpoints(self) -> None: @@ -244,6 +249,7 @@ def test_documented_operations_are_exposed(self) -> None: (Lettermint.api("token").messages, "retrieve"), (Lettermint.api("token").messages, "reschedule"), (Lettermint.api("token").messages, "cancel"), + (Lettermint.api("token").messages, "process"), (Lettermint.api("token").messages, "events"), (Lettermint.api("token").messages, "source"), (Lettermint.api("token").messages, "html"), From 90174dae5f374fc7fb46f07d228f7c2242924f83 Mon Sep 17 00:00:00 2001 From: Bjarn Bronsveld <14638441+bjarn@users.noreply.github.com> Date: Sat, 5 Sep 2026 13:19:07 +0200 Subject: [PATCH 2/2] fix(api): type quarantined message processing response (#23) --- src/lettermint/endpoints/api.py | 8 ++++---- src/lettermint/types.py | 6 ++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/lettermint/endpoints/api.py b/src/lettermint/endpoints/api.py index 78e730f..8d52cc3 100644 --- a/src/lettermint/endpoints/api.py +++ b/src/lettermint/endpoints/api.py @@ -96,9 +96,9 @@ def cancel(self, message_id: str) -> lm_types.RescheduleMessageResponse: ), ) - def process(self, message_id: str) -> dict[str, Any]: + def process(self, message_id: str) -> lm_types.ProcessInboundMessageResponse: return cast( - dict[str, Any], + lm_types.ProcessInboundMessageResponse, self._client.post( self._path("/messages/{messageId}/process", messageId=message_id), data={} ), @@ -382,9 +382,9 @@ async def cancel(self, message_id: str) -> lm_types.RescheduleMessageResponse: ), ) - async def process(self, message_id: str) -> dict[str, Any]: + async def process(self, message_id: str) -> lm_types.ProcessInboundMessageResponse: return cast( - dict[str, Any], + lm_types.ProcessInboundMessageResponse, await self._client.post( self._path("/messages/{messageId}/process", messageId=message_id), data={} ), diff --git a/src/lettermint/types.py b/src/lettermint/types.py index 7d2c641..4b012f5 100644 --- a/src/lettermint/types.py +++ b/src/lettermint/types.py @@ -887,6 +887,12 @@ ) MessageShowResponse: TypeAlias = MessageData +ProcessInboundMessageResponse = TypedDict( + "ProcessInboundMessageResponse", + { + "data": "Required[dict[str, str | int]]", + }, +) MessageEventsResponse = TypedDict( "MessageEventsResponse", {