From 21f19c1338b3e9199a84675b8e1cad1450a9bdf9 Mon Sep 17 00:00:00 2001 From: Bjarn Bronsveld Date: Wed, 19 Aug 2026 22:26:53 +0200 Subject: [PATCH] feat(api): support message tags and route suppression --- src/lettermint/endpoints/email.py | 10 ++++++ src/lettermint/types.py | 52 ++++++++++++++++--------------- tests/test_email.py | 7 +++-- 3 files changed, 41 insertions(+), 28 deletions(-) diff --git a/src/lettermint/endpoints/email.py b/src/lettermint/endpoints/email.py index 087769a..e00529f 100644 --- a/src/lettermint/endpoints/email.py +++ b/src/lettermint/endpoints/email.py @@ -276,6 +276,11 @@ def tag(self, tag: str) -> Self: self._payload["tag"] = tag return self + def tags(self, tags: list[dict[str, str]]) -> Self: + """Set reusable name-value tags for the email.""" + self._payload["tags"] = tags + return self + def send(self) -> SendEmailResponse: """Send the composed email. @@ -555,6 +560,11 @@ def tag(self, tag: str) -> Self: self._payload["tag"] = tag return self + def tags(self, tags: list[dict[str, str]]) -> Self: + """Set reusable name-value tags for the email.""" + self._payload["tags"] = tags + return self + def send(self) -> Coroutine[Any, Any, SendEmailResponse]: """Send the composed email asynchronously. diff --git a/src/lettermint/types.py b/src/lettermint/types.py index bd87644..e0ff536 100644 --- a/src/lettermint/types.py +++ b/src/lettermint/types.py @@ -36,6 +36,7 @@ "headers": "NotRequired[dict[str, str]]", "metadata": "NotRequired[dict[str, str]]", "tag": "NotRequired[str | None]", + "tags": "NotRequired[list[dict[str, Any]]]", "settings": "NotRequired[dict[str, Any] | None]", "html": "NotRequired[str | None]", "text": "NotRequired[str | None]", @@ -43,7 +44,7 @@ }, ) -SendBatchMailRequest: TypeAlias = list[SendMailRequest] +SendBatchMailRequest: TypeAlias = list[dict[str, Any]] AttachmentDelivery: TypeAlias = Literal["inline", "url"] BuiltInTeamRole: TypeAlias = Literal["owner", "admin", "member"] CursorPaginator = TypedDict( @@ -150,6 +151,7 @@ "status": "Required[MessageStatus]", "status_changed_at": "Required[str | None]", "tag": "Required[str | None]", + "tags": "Required[list[dict[str, Any]]]", "from_email": "Required[str]", "from_name": "Required[str | None]", "reply_to": "Required[list[str] | None]", @@ -192,6 +194,8 @@ { "message_id": "Required[str]", "event": "Required[MessageEventType]", + "tag": "Required[str | None]", + "tags": "Required[list[dict[str, Any]]]", "metadata": "Required[dict[str, Any] | None]", "timestamp": "Required[str]", }, @@ -212,6 +216,7 @@ "bcc": "Required[list[MessageRecipientData] | None]", "reply_to": "Required[list[str] | None]", "tag": "Required[str | None]", + "tags": "Required[list[dict[str, Any]]]", "status_changed_at": "Required[str | None]", "created_at": "Required[str]", }, @@ -229,7 +234,6 @@ Plan: TypeAlias = Literal["free", "starter", "growth", "pro"] ProjectAccessScope: TypeAlias = Literal["all", "selected"] -RouteType: TypeAlias = Literal["transactional", "broadcast", "inbound"] RouteStatisticData = TypedDict( "RouteStatisticData", { @@ -250,6 +254,7 @@ }, ) +RouteType: TypeAlias = Literal["transactional", "broadcast", "inbound"] RouteData = TypedDict( "RouteData", { @@ -373,6 +378,13 @@ }, ) +StatsInboundData = TypedDict( + "StatsInboundData", + { + "received": "Required[int]", + }, +) + StatsTypeData = TypedDict( "StatsTypeData", { @@ -382,13 +394,6 @@ }, ) -StatsInboundData = TypedDict( - "StatsInboundData", - { - "received": "Required[int]", - }, -) - StatsDailyData = TypedDict( "StatsDailyData", { @@ -478,8 +483,8 @@ }, ) -SuppressionScope: TypeAlias = Literal["global", "team", "project", "route"] SuppressionReason: TypeAlias = Literal["spam_complaint", "hard_bounce", "unsubscribe", "manual"] +SuppressionScope: TypeAlias = Literal["global", "team", "project", "route"] StoreSuppressionData = TypedDict( "StoreSuppressionData", { @@ -644,6 +649,15 @@ }, ) +UpdateRouteInboundSettingsData = TypedDict( + "UpdateRouteInboundSettingsData", + { + "inbound_domain": "NotRequired[str | None]", + "inbound_spam_threshold": "NotRequired[float | None]", + "attachment_delivery": "NotRequired[AttachmentDelivery | None]", + }, +) + UpdateRouteSettingsData = TypedDict( "UpdateRouteSettingsData", { @@ -651,21 +665,13 @@ "track_clicks": "NotRequired[bool | None]", "generate_plaintext_fallback": "NotRequired[bool | None]", "suppress_auto_responders": "NotRequired[bool | None]", + "suppress_disposable_recipients": "NotRequired[bool | None]", "tls": "NotRequired[TlsPolicy | None]", "disable_hosted_unsubscribe": "NotRequired[bool | None]", "redact_email_content": "NotRequired[bool | None]", }, ) -UpdateRouteInboundSettingsData = TypedDict( - "UpdateRouteInboundSettingsData", - { - "inbound_domain": "NotRequired[str | None]", - "inbound_spam_threshold": "NotRequired[float | None]", - "attachment_delivery": "NotRequired[AttachmentDelivery | None]", - }, -) - UpdateRouteData = TypedDict( "UpdateRouteData", { @@ -860,12 +866,8 @@ "MessageEventsResponse", { "data": "Required[list[MessageEventData]]", - "path": "Required[str | None]", - "per_page": "Required[int]", - "next_cursor": "Required[str | None]", - "next_page_url": "Required[str | None]", - "prev_cursor": "Required[str | None]", - "prev_page_url": "Required[str | None]", + "links": "Required[list[str]]", + "meta": "Required[dict[str, Any]]", }, ) diff --git a/tests/test_email.py b/tests/test_email.py index 596c2c2..9a56cdc 100644 --- a/tests/test_email.py +++ b/tests/test_email.py @@ -186,15 +186,16 @@ def test_send_with_metadata_and_tag(self, api_token: str) -> None: with Lettermint(api_token=api_token) as client: client.email.from_("sender@example.com").to("recipient@example.com").subject( "Test" - ).metadata({"campaign_id": "123"}).tag("welcome").settings( - {"track_opens": False, "track_clicks": True, "tls": "enforced"} - ).send() + ).metadata({"campaign_id": "123"}).tag("welcome").tags( + [{"name": "campaign", "value": "welcome-v2"}] + ).settings({"track_opens": False, "track_clicks": True, "tls": "enforced"}).send() import json body = json.loads(route.calls.last.request.content) assert body["metadata"] == {"campaign_id": "123"} assert body["tag"] == "welcome" + assert body["tags"] == [{"name": "campaign", "value": "welcome-v2"}] assert body["settings"] == { "track_opens": False, "track_clicks": True,