From 892cca70e6330d53c23df59c6a2c6aecd60b4315 Mon Sep 17 00:00:00 2001 From: Bjarn Bronsveld Date: Wed, 30 Sep 2026 23:06:51 +0200 Subject: [PATCH 1/2] feat(api): add analytics and project report forwarding --- src/lettermint/client.py | 4 + src/lettermint/endpoints/api.py | 96 ++ src/lettermint/lettermint.py | 6 + src/lettermint/types.py | 1530 +++++++++++++++++++++++++++- tests/test_analytics_forwarding.py | 99 ++ tests/test_api_surface.py | 8 +- 6 files changed, 1730 insertions(+), 13 deletions(-) create mode 100644 tests/test_analytics_forwarding.py diff --git a/src/lettermint/client.py b/src/lettermint/client.py index 9f54766..5efabfa 100644 --- a/src/lettermint/client.py +++ b/src/lettermint/client.py @@ -62,6 +62,8 @@ def __exit__(self, *args: Any) -> None: def _handle_response(self, response: httpx.Response) -> Any: """Handle the HTTP response and raise appropriate exceptions.""" if response.is_success: + if response.status_code == 204: + return None return response.json() try: @@ -287,6 +289,8 @@ async def __aexit__(self, *args: Any) -> None: def _handle_response(self, response: httpx.Response) -> Any: """Handle the HTTP response and raise appropriate exceptions.""" if response.is_success: + if response.status_code == 204: + return None return response.json() try: diff --git a/src/lettermint/endpoints/api.py b/src/lettermint/endpoints/api.py index ba81f95..c86e71d 100644 --- a/src/lettermint/endpoints/api.py +++ b/src/lettermint/endpoints/api.py @@ -126,6 +126,53 @@ def text(self, message_id: str) -> str: class ProjectsEndpoint(Endpoint): + def retrieve_report_forwarding(self, project_id: str) -> lm_types.GetReportForwardingResponse: + return cast( + lm_types.GetReportForwardingResponse, + self._client.get( + self._path("/projects/{projectId}/report-forwarding", projectId=project_id) + ), + ) + + def update_report_forwarding( + self, project_id: str, data: lm_types.UpdateReportForwardingRequest + ) -> lm_types.UpdateReportForwardingResponse: + return cast( + lm_types.UpdateReportForwardingResponse, + self._client.put( + self._path("/projects/{projectId}/report-forwarding", projectId=project_id), + data=data, + ), + ) + + def delete_report_forwarding(self, project_id: str) -> None: + self._client.delete( + self._path("/projects/{projectId}/report-forwarding", projectId=project_id) + ) + + def verify_report_forwarding( + self, project_id: str, data: lm_types.VerifyReportForwardingRequest + ) -> lm_types.VerifyReportForwardingResponse: + return cast( + lm_types.VerifyReportForwardingResponse, + self._client.post( + self._path("/projects/{projectId}/report-forwarding/verify", projectId=project_id), + data=data, + ), + ) + + def resend_report_forwarding_code( + self, project_id: str + ) -> lm_types.ResendReportForwardingCodeResponse: + return cast( + lm_types.ResendReportForwardingCodeResponse, + self._client.post( + self._path( + "/projects/{projectId}/report-forwarding/resend-code", projectId=project_id + ) + ), + ) + def list(self, query: Query | None = None) -> lm_types.ProjectIndexResponse: return cast(lm_types.ProjectIndexResponse, self._client.get("/projects", params=query)) @@ -473,6 +520,55 @@ async def text(self, message_id: str) -> str: class AsyncProjectsEndpoint(AsyncEndpoint): + async def retrieve_report_forwarding( + self, project_id: str + ) -> lm_types.GetReportForwardingResponse: + return cast( + lm_types.GetReportForwardingResponse, + await self._client.get( + self._path("/projects/{projectId}/report-forwarding", projectId=project_id) + ), + ) + + async def update_report_forwarding( + self, project_id: str, data: lm_types.UpdateReportForwardingRequest + ) -> lm_types.UpdateReportForwardingResponse: + return cast( + lm_types.UpdateReportForwardingResponse, + await self._client.put( + self._path("/projects/{projectId}/report-forwarding", projectId=project_id), + data=data, + ), + ) + + async def delete_report_forwarding(self, project_id: str) -> None: + await self._client.delete( + self._path("/projects/{projectId}/report-forwarding", projectId=project_id) + ) + + async def verify_report_forwarding( + self, project_id: str, data: lm_types.VerifyReportForwardingRequest + ) -> lm_types.VerifyReportForwardingResponse: + return cast( + lm_types.VerifyReportForwardingResponse, + await self._client.post( + self._path("/projects/{projectId}/report-forwarding/verify", projectId=project_id), + data=data, + ), + ) + + async def resend_report_forwarding_code( + self, project_id: str + ) -> lm_types.ResendReportForwardingCodeResponse: + return cast( + lm_types.ResendReportForwardingCodeResponse, + await self._client.post( + self._path( + "/projects/{projectId}/report-forwarding/resend-code", projectId=project_id + ) + ), + ) + async def list(self, query: Query | None = None) -> lm_types.ProjectIndexResponse: return cast( lm_types.ProjectIndexResponse, await self._client.get("/projects", params=query) diff --git a/src/lettermint/lettermint.py b/src/lettermint/lettermint.py index 90010f4..d2ad7ed 100644 --- a/src/lettermint/lettermint.py +++ b/src/lettermint/lettermint.py @@ -52,6 +52,9 @@ def __get__( class ApiClient: + def analytics(self, data: lm_types.AnalyticsRequest) -> lm_types.AnalyticsResponse: + return cast(lm_types.AnalyticsResponse, self._client.post("/analytics", data=data)) + """Synchronous client for the full Lettermint API.""" def __init__( @@ -96,6 +99,9 @@ def blocked_file_types(self) -> lm_types.BlockedFileTypesResponse: class AsyncApiClient: + async def analytics(self, data: lm_types.AnalyticsRequest) -> lm_types.AnalyticsResponse: + return cast(lm_types.AnalyticsResponse, await self._client.post("/analytics", data=data)) + """Asynchronous client for the full Lettermint API.""" def __init__( diff --git a/src/lettermint/types.py b/src/lettermint/types.py index 22ecd6f..153f3c1 100644 --- a/src/lettermint/types.py +++ b/src/lettermint/types.py @@ -54,7 +54,7 @@ "metadata": "NotRequired[dict[str, str]]", "tag": "NotRequired[str | None]", "tags": "NotRequired[list[dict[str, Any]]]", - "settings": "NotRequired[dict[str, Any] | None]", + "settings": "NotRequired[dict[str, Any]]", "html": "NotRequired[str | None]", "text": "NotRequired[str | None]", "attachments": "NotRequired[list[dict[str, Any]]]", @@ -337,6 +337,7 @@ { "id": "Required[str]", "name": "Required[str]", + "delivery_mode": "Required[DeliveryMode]", "smtp_enabled": "Required[bool]", "routes_count": "Required[int]", "domains_count": "Required[int]", @@ -346,6 +347,7 @@ }, ) + RbacConflictCode: TypeAlias = Literal[ "stale_resource", "owner_protected", @@ -388,6 +390,7 @@ "webhooks:delete", "webhooks:rotate_secret", "stats:read", + "analytics:read", "messages:read", "messages:read_content", "messages:send", @@ -508,21 +511,29 @@ { "name": "Required[str]", "smtp_enabled": "NotRequired[bool]", + "delivery_mode": "NotRequired[DeliveryMode]", "initial_routes": "NotRequired[InitialRoutes]", "short_token": "NotRequired[bool]", - "delivery_mode": "NotRequired[DeliveryMode]", + "redact_email_content": "NotRequired[bool]", }, ) + StoreRouteData = TypedDict( "StoreRouteData", { "name": "Required[str]", "route_type": "Required[RouteType]", "slug": "NotRequired[str | None]", + "settings": "NotRequired[UpdateRouteSettingsData | None]", + "inbound_settings": "NotRequired[UpdateRouteInboundSettingsData | None]", + "inbound_domain": "NotRequired[str | None]", + "inbound_spam_threshold": "NotRequired[float | None]", + "attachment_delivery": "NotRequired[AttachmentDelivery | None]", }, ) + SuppressionReason: TypeAlias = Literal["spam_complaint", "hard_bounce", "unsubscribe", "manual"] SuppressionScope: TypeAlias = Literal["team", "project", "route"] SuppressionAppliesTo: TypeAlias = Literal["all", "broadcast"] @@ -731,16 +742,21 @@ "name": "NotRequired[str | None]", "settings": "NotRequired[UpdateRouteSettingsData | None]", "inbound_settings": "NotRequired[UpdateRouteInboundSettingsData | None]", + "inbound_domain": "NotRequired[str | None]", + "inbound_spam_threshold": "NotRequired[float | None]", + "attachment_delivery": "NotRequired[AttachmentDelivery | None]", }, ) + UpdateTeamData = TypedDict( "UpdateTeamData", { - "name": "NotRequired[str | None]", + "name": "NotRequired[str]", }, ) + UpdateTeamMemberAssignmentData = TypedDict( "UpdateTeamMemberAssignmentData", { @@ -821,6 +837,7 @@ "source_project_id": "Required[str | None]", "source_route_id": "Required[str | None]", "status": "Required[WebhookDeliveryStatus]", + "sandbox": "Required[bool]", "attempt_number": "Required[int]", "http_status_code": "Required[int | None]", "duration_ms": "Required[int | None]", @@ -829,6 +846,7 @@ }, ) + WebhookListData = TypedDict( "WebhookListData", { @@ -841,12 +859,14 @@ "url": "Required[str]", "events": "Required[list[str]]", "enabled": "Required[bool]", + "delivery_mode_filter": "Required[WebhookDeliveryModeFilter]", "last_called_at": "Required[str | None]", "created_at": "Required[str]", "updated_at": "Required[str]", }, ) + WebhookSecretData = TypedDict( "WebhookSecretData", { @@ -882,16 +902,17 @@ SendMailResponse = TypedDict( "SendMailResponse", { - "message_id": "Required[str | None]", - "status": "Required[MessageStatus]", - "scheduled_at": "NotRequired[str]", + "message_id": "Required[str]", + "status": "Required[Literal['pending', 'scheduled']]", "sandbox": "NotRequired[Literal[True]]", "sandbox_result": "NotRequired[SandboxResult]", + "scheduled_at": "NotRequired[str]", }, ) + SendBatchMailResponse: TypeAlias = list[SendMailResponse] -PingResponse: TypeAlias = Literal[200] +PingResponse: TypeAlias = str DomainIndexResponse = TypedDict( "DomainIndexResponse", { @@ -1009,11 +1030,12 @@ "ProjectStoreResponse", { "data": "Required[ProjectData]", - "message": "Required[Literal['Project created successfully.']]", - "api_token": "Required[str]", + "message": "Required[str]", + "api_token": "NotRequired[str]", }, ) + ProjectShowResponse: TypeAlias = ProjectData ProjectUpdateRequest: TypeAlias = UpdateProjectData ProjectUpdateResponse = TypedDict( @@ -1086,12 +1108,13 @@ }, ) + StatsIndexResponse: TypeAlias = StatsData SuppressionIndexResponse = TypedDict( "SuppressionIndexResponse", { "data": "Required[list[SuppressedRecipientData]]", - "path": "Required[str | None]", + "path": "Required[str]", "per_page": "Required[int]", "next_cursor": "Required[str | None]", "next_page_url": "Required[str | None]", @@ -1100,6 +1123,7 @@ }, ) + SuppressionStoreRequest: TypeAlias = StoreSuppressionData SuppressionStoreResponse = TypedDict( "SuppressionStoreResponse", @@ -1112,13 +1136,15 @@ SuppressionDestroyResponse = TypedDict( "SuppressionDestroyResponse", { - "success": "Required[bool]", - "status": "Required[Literal['removed']]", + "success": "Required[Literal[True]]", + "status": "Required[Literal['removed', 'review_ticket_created', 'review_ticket_exists']]", "message": "Required[str]", "confidence": "NotRequired[float]", + "ticket_identifier": "NotRequired[str]", }, ) + TeamShowResponse: TypeAlias = TeamData TeamUpdateRequest: TypeAlias = UpdateTeamData TeamUpdateResponse = TypedDict( @@ -1224,3 +1250,1483 @@ WebhookShowDeliveryResponse: TypeAlias = WebhookDeliveryData SendEmailResponse: TypeAlias = SendMailResponse SendBatchEmailResponse: TypeAlias = SendBatchMailResponse + +ProjectCreatedData = TypedDict( + "ProjectCreatedData", + { + "data": "Required[ProjectData]", + "message": "Required[str]", + "api_token": "NotRequired[str]", + }, +) + + +ReportForwardingRequest = TypedDict( + "ReportForwardingRequest", + { + "destination": "Required[str]", + }, +) + + +ReportForwardingResource = TypedDict( + "ReportForwardingResource", + { + "destination": "Required[str | None]", + "verified": "Required[bool]", + "verified_at": "Required[str | None]", + }, +) + + +VerifyReportForwardingRequest = TypedDict( + "VerifyReportForwardingRequest", + { + "code": "Required[str]", + }, +) + + +UpdateReportForwardingRequest: TypeAlias = ReportForwardingRequest + +GetReportForwardingResponse = TypedDict( + "GetReportForwardingResponse", + { + "data": "Required[ReportForwardingResource]", + }, +) + + +UpdateReportForwardingResponse = TypedDict( + "UpdateReportForwardingResponse", + { + "data": "Required[ReportForwardingResource]", + }, +) + + +VerifyReportForwardingResponse = TypedDict( + "VerifyReportForwardingResponse", + { + "data": "Required[ReportForwardingResource]", + }, +) + + +ResendReportForwardingCodeResponse = TypedDict( + "ResendReportForwardingCodeResponse", + { + "data": "Required[ReportForwardingResource]", + }, +) + + +AnalyticsResponseData = TypedDict( + "AnalyticsResponseData", + { + "data": "Required[dict[str, Any]]", + "meta": "Required[dict[str, Any]]", + "pagination": "Required[list[str]]", + }, +) + + +AnalyticsRequestFiltersItem = TypedDict( + "AnalyticsRequestFiltersItem", + { + "dimension": "Required[str]", + "operator": "Required[Literal['eq', 'in', 'not_in', 'is_null', 'is_not_null']]", + "values": "NotRequired[list[str]]", + }, +) + + +AnalyticsRequestSort = TypedDict( + "AnalyticsRequestSort", + { + "metric": "Required[Literal['accepted', 'processed', 'suppressed', 'policy_rejected', 'application_failed', 'mta_accepted', 'canceled', 'messages', 'delivered', 'bounced', 'soft_bounced', 'administratively_bounced', 'deferred_recipients', 'deferred_events', 'delivery_attempts', 'attempted_recipients', 'transport_outcome_recipients', 'effective_delivered', 'open_tracked_delivered', 'click_tracked_delivered', 'out_of_band_bounced_recipients', 'out_of_band_bounce_events', 'complained', 'unsubscribed', 'human_opens', 'human_opens_events', 'human_clicks', 'human_clicks_events', 'machine_opens', 'machine_opens_events', 'machine_clicks', 'machine_clicks_events', 'privacy_opens', 'privacy_opens_events', 'privacy_clicks', 'privacy_clicks_events', 'bot_opens', 'bot_opens_events', 'bot_clicks', 'bot_clicks_events', 'scanner_opens', 'scanner_opens_events', 'scanner_clicks', 'scanner_clicks_events', 'observed_opens', 'observed_opens_events', 'observed_clicks', 'observed_clicks_events', 'delivery_rate', 'effective_delivery_rate', 'bounce_rate', 'deferral_rate', 'complaint_rate', 'human_open_rate', 'human_click_rate', 'processing_latency_p50_ms', 'processing_latency_p95_ms', 'processing_latency_p99_ms', 'processing_latency_samples', 'delivery_latency_p50_ms', 'delivery_latency_p95_ms', 'delivery_latency_p99_ms', 'delivery_latency_samples', 'total_latency_p50_ms', 'total_latency_p95_ms', 'total_latency_p99_ms', 'total_latency_samples']]", + "direction": "Required[Literal['asc', 'desc']]", + }, +) + + +AnalyticsRequest = TypedDict( + "AnalyticsRequest", + { + "metrics": "Required[list[Literal['accepted', 'processed', 'suppressed', 'policy_rejected', 'application_failed', 'mta_accepted', 'canceled', 'messages', 'delivered', 'bounced', 'soft_bounced', 'administratively_bounced', 'deferred_recipients', 'deferred_events', 'delivery_attempts', 'attempted_recipients', 'transport_outcome_recipients', 'effective_delivered', 'open_tracked_delivered', 'click_tracked_delivered', 'out_of_band_bounced_recipients', 'out_of_band_bounce_events', 'complained', 'unsubscribed', 'human_opens', 'human_opens_events', 'human_clicks', 'human_clicks_events', 'machine_opens', 'machine_opens_events', 'machine_clicks', 'machine_clicks_events', 'privacy_opens', 'privacy_opens_events', 'privacy_clicks', 'privacy_clicks_events', 'bot_opens', 'bot_opens_events', 'bot_clicks', 'bot_clicks_events', 'scanner_opens', 'scanner_opens_events', 'scanner_clicks', 'scanner_clicks_events', 'observed_opens', 'observed_opens_events', 'observed_clicks', 'observed_clicks_events', 'delivery_rate', 'effective_delivery_rate', 'bounce_rate', 'deferral_rate', 'complaint_rate', 'human_open_rate', 'human_click_rate', 'processing_latency_p50_ms', 'processing_latency_p95_ms', 'processing_latency_p99_ms', 'processing_latency_samples', 'delivery_latency_p50_ms', 'delivery_latency_p95_ms', 'delivery_latency_p99_ms', 'delivery_latency_samples', 'total_latency_p50_ms', 'total_latency_p95_ms', 'total_latency_p99_ms', 'total_latency_samples']]]", + "from": "NotRequired[str]", + "to": "NotRequired[str]", + "timezone": "NotRequired[str]", + "include": "NotRequired[list[Literal['summary', 'time_series', 'breakdown']]]", + "group_by": "NotRequired[list[str]]", + "filters": "NotRequired[list[AnalyticsRequestFiltersItem]]", + "interval": "NotRequired[Literal['hour', 'day']]", + "compare": "NotRequired[Literal['previous_period']]", + "include_trend": "NotRequired[bool]", + "sort": "NotRequired[AnalyticsRequestSort]", + "limit": "NotRequired[int]", + "cursor": "NotRequired[str]", + }, +) + + +AnalyticsResponseMetaComparison = TypedDict( + "AnalyticsResponseMetaComparison", + { + "from": "Required[str]", + "to": "Required[str]", + "partial": "Required[bool]", + }, +) + + +AnalyticsResponseMeta = TypedDict( + "AnalyticsResponseMeta", + { + "time_basis": "NotRequired[Literal['event']]", + "timezone": "NotRequired[str]", + "interval": "NotRequired[Literal['hour', 'day']]", + "from": "NotRequired[str]", + "to": "NotRequired[str]", + "effective_to": "NotRequired[str]", + "alignment": "NotRequired[Literal['hour', 'day']]", + "generated_at": "NotRequired[str]", + "available_since": "NotRequired[str]", + "partial": "NotRequired[bool]", + "ongoing": "NotRequired[bool]", + "collection_completeness": "NotRequired[Literal['best_effort']]", + "last_ingested_at": "NotRequired[str | None]", + "metric_definition_version": "NotRequired[str]", + "ranked_group_limit": "NotRequired[int]", + "comparison": "NotRequired[AnalyticsResponseMetaComparison]", + }, +) + + +AnalyticsResponsePagination = TypedDict( + "AnalyticsResponsePagination", + { + "total_groups": "Required[int]", + "returned_groups": "Required[int]", + "next_cursor": "Required[str | None]", + "truncated": "Required[bool]", + }, +) + + +AnalyticsResponse = TypedDict( + "AnalyticsResponse", + { + "data": "Required[AnalyticsResponsePayload]", + "meta": "Required[AnalyticsResponseMeta]", + "pagination": "Required[AnalyticsResponsePagination]", + }, +) + + +AnalyticsResponsePayloadSummaryMetrics = TypedDict( + "AnalyticsResponsePayloadSummaryMetrics", + { + "accepted": "NotRequired[int | None]", + "processed": "NotRequired[int | None]", + "suppressed": "NotRequired[int | None]", + "policy_rejected": "NotRequired[int | None]", + "application_failed": "NotRequired[int | None]", + "mta_accepted": "NotRequired[int | None]", + "canceled": "NotRequired[int | None]", + "messages": "NotRequired[int | None]", + "delivered": "NotRequired[int | None]", + "bounced": "NotRequired[int | None]", + "soft_bounced": "NotRequired[int | None]", + "administratively_bounced": "NotRequired[int | None]", + "deferred_recipients": "NotRequired[int | None]", + "deferred_events": "NotRequired[int | None]", + "delivery_attempts": "NotRequired[int | None]", + "attempted_recipients": "NotRequired[int | None]", + "transport_outcome_recipients": "NotRequired[int | None]", + "effective_delivered": "NotRequired[int | None]", + "open_tracked_delivered": "NotRequired[int | None]", + "click_tracked_delivered": "NotRequired[int | None]", + "out_of_band_bounced_recipients": "NotRequired[int | None]", + "out_of_band_bounce_events": "NotRequired[int | None]", + "complained": "NotRequired[int | None]", + "unsubscribed": "NotRequired[int | None]", + "human_opens": "NotRequired[int | None]", + "human_opens_events": "NotRequired[int | None]", + "human_clicks": "NotRequired[int | None]", + "human_clicks_events": "NotRequired[int | None]", + "machine_opens": "NotRequired[int | None]", + "machine_opens_events": "NotRequired[int | None]", + "machine_clicks": "NotRequired[int | None]", + "machine_clicks_events": "NotRequired[int | None]", + "privacy_opens": "NotRequired[int | None]", + "privacy_opens_events": "NotRequired[int | None]", + "privacy_clicks": "NotRequired[int | None]", + "privacy_clicks_events": "NotRequired[int | None]", + "bot_opens": "NotRequired[int | None]", + "bot_opens_events": "NotRequired[int | None]", + "bot_clicks": "NotRequired[int | None]", + "bot_clicks_events": "NotRequired[int | None]", + "scanner_opens": "NotRequired[int | None]", + "scanner_opens_events": "NotRequired[int | None]", + "scanner_clicks": "NotRequired[int | None]", + "scanner_clicks_events": "NotRequired[int | None]", + "observed_opens": "NotRequired[int | None]", + "observed_opens_events": "NotRequired[int | None]", + "observed_clicks": "NotRequired[int | None]", + "observed_clicks_events": "NotRequired[int | None]", + "delivery_rate": "NotRequired[float | None]", + "effective_delivery_rate": "NotRequired[float | None]", + "bounce_rate": "NotRequired[float | None]", + "deferral_rate": "NotRequired[float | None]", + "complaint_rate": "NotRequired[float | None]", + "human_open_rate": "NotRequired[float | None]", + "human_click_rate": "NotRequired[float | None]", + "processing_latency_p50_ms": "NotRequired[float | None]", + "processing_latency_p95_ms": "NotRequired[float | None]", + "processing_latency_p99_ms": "NotRequired[float | None]", + "processing_latency_samples": "NotRequired[int | None]", + "delivery_latency_p50_ms": "NotRequired[float | None]", + "delivery_latency_p95_ms": "NotRequired[float | None]", + "delivery_latency_p99_ms": "NotRequired[float | None]", + "delivery_latency_samples": "NotRequired[int | None]", + "total_latency_p50_ms": "NotRequired[float | None]", + "total_latency_p95_ms": "NotRequired[float | None]", + "total_latency_p99_ms": "NotRequired[float | None]", + "total_latency_samples": "NotRequired[int | None]", + }, +) + + +AnalyticsResponsePayloadSummaryRateBasesDeliveryRate = TypedDict( + "AnalyticsResponsePayloadSummaryRateBasesDeliveryRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadSummaryRateBasesEffectiveDeliveryRate = TypedDict( + "AnalyticsResponsePayloadSummaryRateBasesEffectiveDeliveryRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadSummaryRateBasesBounceRate = TypedDict( + "AnalyticsResponsePayloadSummaryRateBasesBounceRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadSummaryRateBasesDeferralRate = TypedDict( + "AnalyticsResponsePayloadSummaryRateBasesDeferralRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadSummaryRateBasesComplaintRate = TypedDict( + "AnalyticsResponsePayloadSummaryRateBasesComplaintRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadSummaryRateBasesHumanOpenRate = TypedDict( + "AnalyticsResponsePayloadSummaryRateBasesHumanOpenRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadSummaryRateBasesHumanClickRate = TypedDict( + "AnalyticsResponsePayloadSummaryRateBasesHumanClickRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadSummaryRateBases = TypedDict( + "AnalyticsResponsePayloadSummaryRateBases", + { + "delivery_rate": "NotRequired[AnalyticsResponsePayloadSummaryRateBasesDeliveryRate]", + "effective_delivery_rate": "NotRequired[AnalyticsResponsePayloadSummaryRateBasesEffectiveDeliveryRate]", + "bounce_rate": "NotRequired[AnalyticsResponsePayloadSummaryRateBasesBounceRate]", + "deferral_rate": "NotRequired[AnalyticsResponsePayloadSummaryRateBasesDeferralRate]", + "complaint_rate": "NotRequired[AnalyticsResponsePayloadSummaryRateBasesComplaintRate]", + "human_open_rate": "NotRequired[AnalyticsResponsePayloadSummaryRateBasesHumanOpenRate]", + "human_click_rate": "NotRequired[AnalyticsResponsePayloadSummaryRateBasesHumanClickRate]", + }, +) + + +AnalyticsResponsePayloadSummaryPreviousMetrics = TypedDict( + "AnalyticsResponsePayloadSummaryPreviousMetrics", + { + "accepted": "NotRequired[int | None]", + "processed": "NotRequired[int | None]", + "suppressed": "NotRequired[int | None]", + "policy_rejected": "NotRequired[int | None]", + "application_failed": "NotRequired[int | None]", + "mta_accepted": "NotRequired[int | None]", + "canceled": "NotRequired[int | None]", + "messages": "NotRequired[int | None]", + "delivered": "NotRequired[int | None]", + "bounced": "NotRequired[int | None]", + "soft_bounced": "NotRequired[int | None]", + "administratively_bounced": "NotRequired[int | None]", + "deferred_recipients": "NotRequired[int | None]", + "deferred_events": "NotRequired[int | None]", + "delivery_attempts": "NotRequired[int | None]", + "attempted_recipients": "NotRequired[int | None]", + "transport_outcome_recipients": "NotRequired[int | None]", + "effective_delivered": "NotRequired[int | None]", + "open_tracked_delivered": "NotRequired[int | None]", + "click_tracked_delivered": "NotRequired[int | None]", + "out_of_band_bounced_recipients": "NotRequired[int | None]", + "out_of_band_bounce_events": "NotRequired[int | None]", + "complained": "NotRequired[int | None]", + "unsubscribed": "NotRequired[int | None]", + "human_opens": "NotRequired[int | None]", + "human_opens_events": "NotRequired[int | None]", + "human_clicks": "NotRequired[int | None]", + "human_clicks_events": "NotRequired[int | None]", + "machine_opens": "NotRequired[int | None]", + "machine_opens_events": "NotRequired[int | None]", + "machine_clicks": "NotRequired[int | None]", + "machine_clicks_events": "NotRequired[int | None]", + "privacy_opens": "NotRequired[int | None]", + "privacy_opens_events": "NotRequired[int | None]", + "privacy_clicks": "NotRequired[int | None]", + "privacy_clicks_events": "NotRequired[int | None]", + "bot_opens": "NotRequired[int | None]", + "bot_opens_events": "NotRequired[int | None]", + "bot_clicks": "NotRequired[int | None]", + "bot_clicks_events": "NotRequired[int | None]", + "scanner_opens": "NotRequired[int | None]", + "scanner_opens_events": "NotRequired[int | None]", + "scanner_clicks": "NotRequired[int | None]", + "scanner_clicks_events": "NotRequired[int | None]", + "observed_opens": "NotRequired[int | None]", + "observed_opens_events": "NotRequired[int | None]", + "observed_clicks": "NotRequired[int | None]", + "observed_clicks_events": "NotRequired[int | None]", + "delivery_rate": "NotRequired[float | None]", + "effective_delivery_rate": "NotRequired[float | None]", + "bounce_rate": "NotRequired[float | None]", + "deferral_rate": "NotRequired[float | None]", + "complaint_rate": "NotRequired[float | None]", + "human_open_rate": "NotRequired[float | None]", + "human_click_rate": "NotRequired[float | None]", + "processing_latency_p50_ms": "NotRequired[float | None]", + "processing_latency_p95_ms": "NotRequired[float | None]", + "processing_latency_p99_ms": "NotRequired[float | None]", + "processing_latency_samples": "NotRequired[int | None]", + "delivery_latency_p50_ms": "NotRequired[float | None]", + "delivery_latency_p95_ms": "NotRequired[float | None]", + "delivery_latency_p99_ms": "NotRequired[float | None]", + "delivery_latency_samples": "NotRequired[int | None]", + "total_latency_p50_ms": "NotRequired[float | None]", + "total_latency_p95_ms": "NotRequired[float | None]", + "total_latency_p99_ms": "NotRequired[float | None]", + "total_latency_samples": "NotRequired[int | None]", + }, +) + + +AnalyticsResponsePayloadSummaryPreviousRateBasesDeliveryRate = TypedDict( + "AnalyticsResponsePayloadSummaryPreviousRateBasesDeliveryRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadSummaryPreviousRateBasesEffectiveDeliveryRate = TypedDict( + "AnalyticsResponsePayloadSummaryPreviousRateBasesEffectiveDeliveryRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadSummaryPreviousRateBasesBounceRate = TypedDict( + "AnalyticsResponsePayloadSummaryPreviousRateBasesBounceRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadSummaryPreviousRateBasesDeferralRate = TypedDict( + "AnalyticsResponsePayloadSummaryPreviousRateBasesDeferralRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadSummaryPreviousRateBasesComplaintRate = TypedDict( + "AnalyticsResponsePayloadSummaryPreviousRateBasesComplaintRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadSummaryPreviousRateBasesHumanOpenRate = TypedDict( + "AnalyticsResponsePayloadSummaryPreviousRateBasesHumanOpenRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadSummaryPreviousRateBasesHumanClickRate = TypedDict( + "AnalyticsResponsePayloadSummaryPreviousRateBasesHumanClickRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadSummaryPreviousRateBases = TypedDict( + "AnalyticsResponsePayloadSummaryPreviousRateBases", + { + "delivery_rate": "NotRequired[AnalyticsResponsePayloadSummaryPreviousRateBasesDeliveryRate]", + "effective_delivery_rate": "NotRequired[AnalyticsResponsePayloadSummaryPreviousRateBasesEffectiveDeliveryRate]", + "bounce_rate": "NotRequired[AnalyticsResponsePayloadSummaryPreviousRateBasesBounceRate]", + "deferral_rate": "NotRequired[AnalyticsResponsePayloadSummaryPreviousRateBasesDeferralRate]", + "complaint_rate": "NotRequired[AnalyticsResponsePayloadSummaryPreviousRateBasesComplaintRate]", + "human_open_rate": "NotRequired[AnalyticsResponsePayloadSummaryPreviousRateBasesHumanOpenRate]", + "human_click_rate": "NotRequired[AnalyticsResponsePayloadSummaryPreviousRateBasesHumanClickRate]", + }, +) + + +AnalyticsResponsePayloadSummaryPrevious = TypedDict( + "AnalyticsResponsePayloadSummaryPrevious", + { + "metrics": "Required[AnalyticsResponsePayloadSummaryPreviousMetrics]", + "rate_bases": "Required[AnalyticsResponsePayloadSummaryPreviousRateBases]", + }, +) + + +AnalyticsResponsePayloadSummary = TypedDict( + "AnalyticsResponsePayloadSummary", + { + "metrics": "NotRequired[AnalyticsResponsePayloadSummaryMetrics]", + "rate_bases": "NotRequired[AnalyticsResponsePayloadSummaryRateBases]", + "previous": "NotRequired[AnalyticsResponsePayloadSummaryPrevious]", + "change": "NotRequired[dict[str, dict[str, Any]]]", + }, +) + + +AnalyticsResponsePayloadTimeSeriesItemMetrics = TypedDict( + "AnalyticsResponsePayloadTimeSeriesItemMetrics", + { + "accepted": "NotRequired[int | None]", + "processed": "NotRequired[int | None]", + "suppressed": "NotRequired[int | None]", + "policy_rejected": "NotRequired[int | None]", + "application_failed": "NotRequired[int | None]", + "mta_accepted": "NotRequired[int | None]", + "canceled": "NotRequired[int | None]", + "messages": "NotRequired[int | None]", + "delivered": "NotRequired[int | None]", + "bounced": "NotRequired[int | None]", + "soft_bounced": "NotRequired[int | None]", + "administratively_bounced": "NotRequired[int | None]", + "deferred_recipients": "NotRequired[int | None]", + "deferred_events": "NotRequired[int | None]", + "delivery_attempts": "NotRequired[int | None]", + "attempted_recipients": "NotRequired[int | None]", + "transport_outcome_recipients": "NotRequired[int | None]", + "effective_delivered": "NotRequired[int | None]", + "open_tracked_delivered": "NotRequired[int | None]", + "click_tracked_delivered": "NotRequired[int | None]", + "out_of_band_bounced_recipients": "NotRequired[int | None]", + "out_of_band_bounce_events": "NotRequired[int | None]", + "complained": "NotRequired[int | None]", + "unsubscribed": "NotRequired[int | None]", + "human_opens": "NotRequired[int | None]", + "human_opens_events": "NotRequired[int | None]", + "human_clicks": "NotRequired[int | None]", + "human_clicks_events": "NotRequired[int | None]", + "machine_opens": "NotRequired[int | None]", + "machine_opens_events": "NotRequired[int | None]", + "machine_clicks": "NotRequired[int | None]", + "machine_clicks_events": "NotRequired[int | None]", + "privacy_opens": "NotRequired[int | None]", + "privacy_opens_events": "NotRequired[int | None]", + "privacy_clicks": "NotRequired[int | None]", + "privacy_clicks_events": "NotRequired[int | None]", + "bot_opens": "NotRequired[int | None]", + "bot_opens_events": "NotRequired[int | None]", + "bot_clicks": "NotRequired[int | None]", + "bot_clicks_events": "NotRequired[int | None]", + "scanner_opens": "NotRequired[int | None]", + "scanner_opens_events": "NotRequired[int | None]", + "scanner_clicks": "NotRequired[int | None]", + "scanner_clicks_events": "NotRequired[int | None]", + "observed_opens": "NotRequired[int | None]", + "observed_opens_events": "NotRequired[int | None]", + "observed_clicks": "NotRequired[int | None]", + "observed_clicks_events": "NotRequired[int | None]", + "delivery_rate": "NotRequired[float | None]", + "effective_delivery_rate": "NotRequired[float | None]", + "bounce_rate": "NotRequired[float | None]", + "deferral_rate": "NotRequired[float | None]", + "complaint_rate": "NotRequired[float | None]", + "human_open_rate": "NotRequired[float | None]", + "human_click_rate": "NotRequired[float | None]", + "processing_latency_p50_ms": "NotRequired[float | None]", + "processing_latency_p95_ms": "NotRequired[float | None]", + "processing_latency_p99_ms": "NotRequired[float | None]", + "processing_latency_samples": "NotRequired[int | None]", + "delivery_latency_p50_ms": "NotRequired[float | None]", + "delivery_latency_p95_ms": "NotRequired[float | None]", + "delivery_latency_p99_ms": "NotRequired[float | None]", + "delivery_latency_samples": "NotRequired[int | None]", + "total_latency_p50_ms": "NotRequired[float | None]", + "total_latency_p95_ms": "NotRequired[float | None]", + "total_latency_p99_ms": "NotRequired[float | None]", + "total_latency_samples": "NotRequired[int | None]", + }, +) + + +AnalyticsResponsePayloadTimeSeriesItemRateBasesDeliveryRate = TypedDict( + "AnalyticsResponsePayloadTimeSeriesItemRateBasesDeliveryRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadTimeSeriesItemRateBasesEffectiveDeliveryRate = TypedDict( + "AnalyticsResponsePayloadTimeSeriesItemRateBasesEffectiveDeliveryRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadTimeSeriesItemRateBasesBounceRate = TypedDict( + "AnalyticsResponsePayloadTimeSeriesItemRateBasesBounceRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadTimeSeriesItemRateBasesDeferralRate = TypedDict( + "AnalyticsResponsePayloadTimeSeriesItemRateBasesDeferralRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadTimeSeriesItemRateBasesComplaintRate = TypedDict( + "AnalyticsResponsePayloadTimeSeriesItemRateBasesComplaintRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadTimeSeriesItemRateBasesHumanOpenRate = TypedDict( + "AnalyticsResponsePayloadTimeSeriesItemRateBasesHumanOpenRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadTimeSeriesItemRateBasesHumanClickRate = TypedDict( + "AnalyticsResponsePayloadTimeSeriesItemRateBasesHumanClickRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadTimeSeriesItemRateBases = TypedDict( + "AnalyticsResponsePayloadTimeSeriesItemRateBases", + { + "delivery_rate": "NotRequired[AnalyticsResponsePayloadTimeSeriesItemRateBasesDeliveryRate]", + "effective_delivery_rate": "NotRequired[AnalyticsResponsePayloadTimeSeriesItemRateBasesEffectiveDeliveryRate]", + "bounce_rate": "NotRequired[AnalyticsResponsePayloadTimeSeriesItemRateBasesBounceRate]", + "deferral_rate": "NotRequired[AnalyticsResponsePayloadTimeSeriesItemRateBasesDeferralRate]", + "complaint_rate": "NotRequired[AnalyticsResponsePayloadTimeSeriesItemRateBasesComplaintRate]", + "human_open_rate": "NotRequired[AnalyticsResponsePayloadTimeSeriesItemRateBasesHumanOpenRate]", + "human_click_rate": "NotRequired[AnalyticsResponsePayloadTimeSeriesItemRateBasesHumanClickRate]", + }, +) + + +AnalyticsResponsePayloadTimeSeriesItemPreviousMetrics = TypedDict( + "AnalyticsResponsePayloadTimeSeriesItemPreviousMetrics", + { + "accepted": "NotRequired[int | None]", + "processed": "NotRequired[int | None]", + "suppressed": "NotRequired[int | None]", + "policy_rejected": "NotRequired[int | None]", + "application_failed": "NotRequired[int | None]", + "mta_accepted": "NotRequired[int | None]", + "canceled": "NotRequired[int | None]", + "messages": "NotRequired[int | None]", + "delivered": "NotRequired[int | None]", + "bounced": "NotRequired[int | None]", + "soft_bounced": "NotRequired[int | None]", + "administratively_bounced": "NotRequired[int | None]", + "deferred_recipients": "NotRequired[int | None]", + "deferred_events": "NotRequired[int | None]", + "delivery_attempts": "NotRequired[int | None]", + "attempted_recipients": "NotRequired[int | None]", + "transport_outcome_recipients": "NotRequired[int | None]", + "effective_delivered": "NotRequired[int | None]", + "open_tracked_delivered": "NotRequired[int | None]", + "click_tracked_delivered": "NotRequired[int | None]", + "out_of_band_bounced_recipients": "NotRequired[int | None]", + "out_of_band_bounce_events": "NotRequired[int | None]", + "complained": "NotRequired[int | None]", + "unsubscribed": "NotRequired[int | None]", + "human_opens": "NotRequired[int | None]", + "human_opens_events": "NotRequired[int | None]", + "human_clicks": "NotRequired[int | None]", + "human_clicks_events": "NotRequired[int | None]", + "machine_opens": "NotRequired[int | None]", + "machine_opens_events": "NotRequired[int | None]", + "machine_clicks": "NotRequired[int | None]", + "machine_clicks_events": "NotRequired[int | None]", + "privacy_opens": "NotRequired[int | None]", + "privacy_opens_events": "NotRequired[int | None]", + "privacy_clicks": "NotRequired[int | None]", + "privacy_clicks_events": "NotRequired[int | None]", + "bot_opens": "NotRequired[int | None]", + "bot_opens_events": "NotRequired[int | None]", + "bot_clicks": "NotRequired[int | None]", + "bot_clicks_events": "NotRequired[int | None]", + "scanner_opens": "NotRequired[int | None]", + "scanner_opens_events": "NotRequired[int | None]", + "scanner_clicks": "NotRequired[int | None]", + "scanner_clicks_events": "NotRequired[int | None]", + "observed_opens": "NotRequired[int | None]", + "observed_opens_events": "NotRequired[int | None]", + "observed_clicks": "NotRequired[int | None]", + "observed_clicks_events": "NotRequired[int | None]", + "delivery_rate": "NotRequired[float | None]", + "effective_delivery_rate": "NotRequired[float | None]", + "bounce_rate": "NotRequired[float | None]", + "deferral_rate": "NotRequired[float | None]", + "complaint_rate": "NotRequired[float | None]", + "human_open_rate": "NotRequired[float | None]", + "human_click_rate": "NotRequired[float | None]", + "processing_latency_p50_ms": "NotRequired[float | None]", + "processing_latency_p95_ms": "NotRequired[float | None]", + "processing_latency_p99_ms": "NotRequired[float | None]", + "processing_latency_samples": "NotRequired[int | None]", + "delivery_latency_p50_ms": "NotRequired[float | None]", + "delivery_latency_p95_ms": "NotRequired[float | None]", + "delivery_latency_p99_ms": "NotRequired[float | None]", + "delivery_latency_samples": "NotRequired[int | None]", + "total_latency_p50_ms": "NotRequired[float | None]", + "total_latency_p95_ms": "NotRequired[float | None]", + "total_latency_p99_ms": "NotRequired[float | None]", + "total_latency_samples": "NotRequired[int | None]", + }, +) + + +AnalyticsResponsePayloadTimeSeriesItemPreviousRateBasesDeliveryRate = TypedDict( + "AnalyticsResponsePayloadTimeSeriesItemPreviousRateBasesDeliveryRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadTimeSeriesItemPreviousRateBasesEffectiveDeliveryRate = TypedDict( + "AnalyticsResponsePayloadTimeSeriesItemPreviousRateBasesEffectiveDeliveryRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadTimeSeriesItemPreviousRateBasesBounceRate = TypedDict( + "AnalyticsResponsePayloadTimeSeriesItemPreviousRateBasesBounceRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadTimeSeriesItemPreviousRateBasesDeferralRate = TypedDict( + "AnalyticsResponsePayloadTimeSeriesItemPreviousRateBasesDeferralRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadTimeSeriesItemPreviousRateBasesComplaintRate = TypedDict( + "AnalyticsResponsePayloadTimeSeriesItemPreviousRateBasesComplaintRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadTimeSeriesItemPreviousRateBasesHumanOpenRate = TypedDict( + "AnalyticsResponsePayloadTimeSeriesItemPreviousRateBasesHumanOpenRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadTimeSeriesItemPreviousRateBasesHumanClickRate = TypedDict( + "AnalyticsResponsePayloadTimeSeriesItemPreviousRateBasesHumanClickRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadTimeSeriesItemPreviousRateBases = TypedDict( + "AnalyticsResponsePayloadTimeSeriesItemPreviousRateBases", + { + "delivery_rate": "NotRequired[AnalyticsResponsePayloadTimeSeriesItemPreviousRateBasesDeliveryRate]", + "effective_delivery_rate": "NotRequired[AnalyticsResponsePayloadTimeSeriesItemPreviousRateBasesEffectiveDeliveryRate]", + "bounce_rate": "NotRequired[AnalyticsResponsePayloadTimeSeriesItemPreviousRateBasesBounceRate]", + "deferral_rate": "NotRequired[AnalyticsResponsePayloadTimeSeriesItemPreviousRateBasesDeferralRate]", + "complaint_rate": "NotRequired[AnalyticsResponsePayloadTimeSeriesItemPreviousRateBasesComplaintRate]", + "human_open_rate": "NotRequired[AnalyticsResponsePayloadTimeSeriesItemPreviousRateBasesHumanOpenRate]", + "human_click_rate": "NotRequired[AnalyticsResponsePayloadTimeSeriesItemPreviousRateBasesHumanClickRate]", + }, +) + + +AnalyticsResponsePayloadTimeSeriesItemPrevious = TypedDict( + "AnalyticsResponsePayloadTimeSeriesItemPrevious", + { + "metrics": "Required[AnalyticsResponsePayloadTimeSeriesItemPreviousMetrics]", + "rate_bases": "Required[AnalyticsResponsePayloadTimeSeriesItemPreviousRateBases]", + }, +) + + +AnalyticsResponsePayloadTimeSeriesItem = TypedDict( + "AnalyticsResponsePayloadTimeSeriesItem", + { + "metrics": "Required[AnalyticsResponsePayloadTimeSeriesItemMetrics]", + "rate_bases": "Required[AnalyticsResponsePayloadTimeSeriesItemRateBases]", + "previous": "NotRequired[AnalyticsResponsePayloadTimeSeriesItemPrevious]", + "change": "NotRequired[dict[str, dict[str, Any]]]", + "from": "Required[str]", + "to": "Required[str]", + "available": "Required[bool]", + "partial": "Required[bool]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemMetrics = TypedDict( + "AnalyticsResponsePayloadBreakdownItemMetrics", + { + "accepted": "NotRequired[int | None]", + "processed": "NotRequired[int | None]", + "suppressed": "NotRequired[int | None]", + "policy_rejected": "NotRequired[int | None]", + "application_failed": "NotRequired[int | None]", + "mta_accepted": "NotRequired[int | None]", + "canceled": "NotRequired[int | None]", + "messages": "NotRequired[int | None]", + "delivered": "NotRequired[int | None]", + "bounced": "NotRequired[int | None]", + "soft_bounced": "NotRequired[int | None]", + "administratively_bounced": "NotRequired[int | None]", + "deferred_recipients": "NotRequired[int | None]", + "deferred_events": "NotRequired[int | None]", + "delivery_attempts": "NotRequired[int | None]", + "attempted_recipients": "NotRequired[int | None]", + "transport_outcome_recipients": "NotRequired[int | None]", + "effective_delivered": "NotRequired[int | None]", + "open_tracked_delivered": "NotRequired[int | None]", + "click_tracked_delivered": "NotRequired[int | None]", + "out_of_band_bounced_recipients": "NotRequired[int | None]", + "out_of_band_bounce_events": "NotRequired[int | None]", + "complained": "NotRequired[int | None]", + "unsubscribed": "NotRequired[int | None]", + "human_opens": "NotRequired[int | None]", + "human_opens_events": "NotRequired[int | None]", + "human_clicks": "NotRequired[int | None]", + "human_clicks_events": "NotRequired[int | None]", + "machine_opens": "NotRequired[int | None]", + "machine_opens_events": "NotRequired[int | None]", + "machine_clicks": "NotRequired[int | None]", + "machine_clicks_events": "NotRequired[int | None]", + "privacy_opens": "NotRequired[int | None]", + "privacy_opens_events": "NotRequired[int | None]", + "privacy_clicks": "NotRequired[int | None]", + "privacy_clicks_events": "NotRequired[int | None]", + "bot_opens": "NotRequired[int | None]", + "bot_opens_events": "NotRequired[int | None]", + "bot_clicks": "NotRequired[int | None]", + "bot_clicks_events": "NotRequired[int | None]", + "scanner_opens": "NotRequired[int | None]", + "scanner_opens_events": "NotRequired[int | None]", + "scanner_clicks": "NotRequired[int | None]", + "scanner_clicks_events": "NotRequired[int | None]", + "observed_opens": "NotRequired[int | None]", + "observed_opens_events": "NotRequired[int | None]", + "observed_clicks": "NotRequired[int | None]", + "observed_clicks_events": "NotRequired[int | None]", + "delivery_rate": "NotRequired[float | None]", + "effective_delivery_rate": "NotRequired[float | None]", + "bounce_rate": "NotRequired[float | None]", + "deferral_rate": "NotRequired[float | None]", + "complaint_rate": "NotRequired[float | None]", + "human_open_rate": "NotRequired[float | None]", + "human_click_rate": "NotRequired[float | None]", + "processing_latency_p50_ms": "NotRequired[float | None]", + "processing_latency_p95_ms": "NotRequired[float | None]", + "processing_latency_p99_ms": "NotRequired[float | None]", + "processing_latency_samples": "NotRequired[int | None]", + "delivery_latency_p50_ms": "NotRequired[float | None]", + "delivery_latency_p95_ms": "NotRequired[float | None]", + "delivery_latency_p99_ms": "NotRequired[float | None]", + "delivery_latency_samples": "NotRequired[int | None]", + "total_latency_p50_ms": "NotRequired[float | None]", + "total_latency_p95_ms": "NotRequired[float | None]", + "total_latency_p99_ms": "NotRequired[float | None]", + "total_latency_samples": "NotRequired[int | None]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemRateBasesDeliveryRate = TypedDict( + "AnalyticsResponsePayloadBreakdownItemRateBasesDeliveryRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemRateBasesEffectiveDeliveryRate = TypedDict( + "AnalyticsResponsePayloadBreakdownItemRateBasesEffectiveDeliveryRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemRateBasesBounceRate = TypedDict( + "AnalyticsResponsePayloadBreakdownItemRateBasesBounceRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemRateBasesDeferralRate = TypedDict( + "AnalyticsResponsePayloadBreakdownItemRateBasesDeferralRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemRateBasesComplaintRate = TypedDict( + "AnalyticsResponsePayloadBreakdownItemRateBasesComplaintRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemRateBasesHumanOpenRate = TypedDict( + "AnalyticsResponsePayloadBreakdownItemRateBasesHumanOpenRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemRateBasesHumanClickRate = TypedDict( + "AnalyticsResponsePayloadBreakdownItemRateBasesHumanClickRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemRateBases = TypedDict( + "AnalyticsResponsePayloadBreakdownItemRateBases", + { + "delivery_rate": "NotRequired[AnalyticsResponsePayloadBreakdownItemRateBasesDeliveryRate]", + "effective_delivery_rate": "NotRequired[AnalyticsResponsePayloadBreakdownItemRateBasesEffectiveDeliveryRate]", + "bounce_rate": "NotRequired[AnalyticsResponsePayloadBreakdownItemRateBasesBounceRate]", + "deferral_rate": "NotRequired[AnalyticsResponsePayloadBreakdownItemRateBasesDeferralRate]", + "complaint_rate": "NotRequired[AnalyticsResponsePayloadBreakdownItemRateBasesComplaintRate]", + "human_open_rate": "NotRequired[AnalyticsResponsePayloadBreakdownItemRateBasesHumanOpenRate]", + "human_click_rate": "NotRequired[AnalyticsResponsePayloadBreakdownItemRateBasesHumanClickRate]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemPreviousMetrics = TypedDict( + "AnalyticsResponsePayloadBreakdownItemPreviousMetrics", + { + "accepted": "NotRequired[int | None]", + "processed": "NotRequired[int | None]", + "suppressed": "NotRequired[int | None]", + "policy_rejected": "NotRequired[int | None]", + "application_failed": "NotRequired[int | None]", + "mta_accepted": "NotRequired[int | None]", + "canceled": "NotRequired[int | None]", + "messages": "NotRequired[int | None]", + "delivered": "NotRequired[int | None]", + "bounced": "NotRequired[int | None]", + "soft_bounced": "NotRequired[int | None]", + "administratively_bounced": "NotRequired[int | None]", + "deferred_recipients": "NotRequired[int | None]", + "deferred_events": "NotRequired[int | None]", + "delivery_attempts": "NotRequired[int | None]", + "attempted_recipients": "NotRequired[int | None]", + "transport_outcome_recipients": "NotRequired[int | None]", + "effective_delivered": "NotRequired[int | None]", + "open_tracked_delivered": "NotRequired[int | None]", + "click_tracked_delivered": "NotRequired[int | None]", + "out_of_band_bounced_recipients": "NotRequired[int | None]", + "out_of_band_bounce_events": "NotRequired[int | None]", + "complained": "NotRequired[int | None]", + "unsubscribed": "NotRequired[int | None]", + "human_opens": "NotRequired[int | None]", + "human_opens_events": "NotRequired[int | None]", + "human_clicks": "NotRequired[int | None]", + "human_clicks_events": "NotRequired[int | None]", + "machine_opens": "NotRequired[int | None]", + "machine_opens_events": "NotRequired[int | None]", + "machine_clicks": "NotRequired[int | None]", + "machine_clicks_events": "NotRequired[int | None]", + "privacy_opens": "NotRequired[int | None]", + "privacy_opens_events": "NotRequired[int | None]", + "privacy_clicks": "NotRequired[int | None]", + "privacy_clicks_events": "NotRequired[int | None]", + "bot_opens": "NotRequired[int | None]", + "bot_opens_events": "NotRequired[int | None]", + "bot_clicks": "NotRequired[int | None]", + "bot_clicks_events": "NotRequired[int | None]", + "scanner_opens": "NotRequired[int | None]", + "scanner_opens_events": "NotRequired[int | None]", + "scanner_clicks": "NotRequired[int | None]", + "scanner_clicks_events": "NotRequired[int | None]", + "observed_opens": "NotRequired[int | None]", + "observed_opens_events": "NotRequired[int | None]", + "observed_clicks": "NotRequired[int | None]", + "observed_clicks_events": "NotRequired[int | None]", + "delivery_rate": "NotRequired[float | None]", + "effective_delivery_rate": "NotRequired[float | None]", + "bounce_rate": "NotRequired[float | None]", + "deferral_rate": "NotRequired[float | None]", + "complaint_rate": "NotRequired[float | None]", + "human_open_rate": "NotRequired[float | None]", + "human_click_rate": "NotRequired[float | None]", + "processing_latency_p50_ms": "NotRequired[float | None]", + "processing_latency_p95_ms": "NotRequired[float | None]", + "processing_latency_p99_ms": "NotRequired[float | None]", + "processing_latency_samples": "NotRequired[int | None]", + "delivery_latency_p50_ms": "NotRequired[float | None]", + "delivery_latency_p95_ms": "NotRequired[float | None]", + "delivery_latency_p99_ms": "NotRequired[float | None]", + "delivery_latency_samples": "NotRequired[int | None]", + "total_latency_p50_ms": "NotRequired[float | None]", + "total_latency_p95_ms": "NotRequired[float | None]", + "total_latency_p99_ms": "NotRequired[float | None]", + "total_latency_samples": "NotRequired[int | None]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemPreviousRateBasesDeliveryRate = TypedDict( + "AnalyticsResponsePayloadBreakdownItemPreviousRateBasesDeliveryRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemPreviousRateBasesEffectiveDeliveryRate = TypedDict( + "AnalyticsResponsePayloadBreakdownItemPreviousRateBasesEffectiveDeliveryRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemPreviousRateBasesBounceRate = TypedDict( + "AnalyticsResponsePayloadBreakdownItemPreviousRateBasesBounceRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemPreviousRateBasesDeferralRate = TypedDict( + "AnalyticsResponsePayloadBreakdownItemPreviousRateBasesDeferralRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemPreviousRateBasesComplaintRate = TypedDict( + "AnalyticsResponsePayloadBreakdownItemPreviousRateBasesComplaintRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemPreviousRateBasesHumanOpenRate = TypedDict( + "AnalyticsResponsePayloadBreakdownItemPreviousRateBasesHumanOpenRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemPreviousRateBasesHumanClickRate = TypedDict( + "AnalyticsResponsePayloadBreakdownItemPreviousRateBasesHumanClickRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemPreviousRateBases = TypedDict( + "AnalyticsResponsePayloadBreakdownItemPreviousRateBases", + { + "delivery_rate": "NotRequired[AnalyticsResponsePayloadBreakdownItemPreviousRateBasesDeliveryRate]", + "effective_delivery_rate": "NotRequired[AnalyticsResponsePayloadBreakdownItemPreviousRateBasesEffectiveDeliveryRate]", + "bounce_rate": "NotRequired[AnalyticsResponsePayloadBreakdownItemPreviousRateBasesBounceRate]", + "deferral_rate": "NotRequired[AnalyticsResponsePayloadBreakdownItemPreviousRateBasesDeferralRate]", + "complaint_rate": "NotRequired[AnalyticsResponsePayloadBreakdownItemPreviousRateBasesComplaintRate]", + "human_open_rate": "NotRequired[AnalyticsResponsePayloadBreakdownItemPreviousRateBasesHumanOpenRate]", + "human_click_rate": "NotRequired[AnalyticsResponsePayloadBreakdownItemPreviousRateBasesHumanClickRate]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemPrevious = TypedDict( + "AnalyticsResponsePayloadBreakdownItemPrevious", + { + "metrics": "Required[AnalyticsResponsePayloadBreakdownItemPreviousMetrics]", + "rate_bases": "Required[AnalyticsResponsePayloadBreakdownItemPreviousRateBases]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemTrendItemMetrics = TypedDict( + "AnalyticsResponsePayloadBreakdownItemTrendItemMetrics", + { + "accepted": "NotRequired[int | None]", + "processed": "NotRequired[int | None]", + "suppressed": "NotRequired[int | None]", + "policy_rejected": "NotRequired[int | None]", + "application_failed": "NotRequired[int | None]", + "mta_accepted": "NotRequired[int | None]", + "canceled": "NotRequired[int | None]", + "messages": "NotRequired[int | None]", + "delivered": "NotRequired[int | None]", + "bounced": "NotRequired[int | None]", + "soft_bounced": "NotRequired[int | None]", + "administratively_bounced": "NotRequired[int | None]", + "deferred_recipients": "NotRequired[int | None]", + "deferred_events": "NotRequired[int | None]", + "delivery_attempts": "NotRequired[int | None]", + "attempted_recipients": "NotRequired[int | None]", + "transport_outcome_recipients": "NotRequired[int | None]", + "effective_delivered": "NotRequired[int | None]", + "open_tracked_delivered": "NotRequired[int | None]", + "click_tracked_delivered": "NotRequired[int | None]", + "out_of_band_bounced_recipients": "NotRequired[int | None]", + "out_of_band_bounce_events": "NotRequired[int | None]", + "complained": "NotRequired[int | None]", + "unsubscribed": "NotRequired[int | None]", + "human_opens": "NotRequired[int | None]", + "human_opens_events": "NotRequired[int | None]", + "human_clicks": "NotRequired[int | None]", + "human_clicks_events": "NotRequired[int | None]", + "machine_opens": "NotRequired[int | None]", + "machine_opens_events": "NotRequired[int | None]", + "machine_clicks": "NotRequired[int | None]", + "machine_clicks_events": "NotRequired[int | None]", + "privacy_opens": "NotRequired[int | None]", + "privacy_opens_events": "NotRequired[int | None]", + "privacy_clicks": "NotRequired[int | None]", + "privacy_clicks_events": "NotRequired[int | None]", + "bot_opens": "NotRequired[int | None]", + "bot_opens_events": "NotRequired[int | None]", + "bot_clicks": "NotRequired[int | None]", + "bot_clicks_events": "NotRequired[int | None]", + "scanner_opens": "NotRequired[int | None]", + "scanner_opens_events": "NotRequired[int | None]", + "scanner_clicks": "NotRequired[int | None]", + "scanner_clicks_events": "NotRequired[int | None]", + "observed_opens": "NotRequired[int | None]", + "observed_opens_events": "NotRequired[int | None]", + "observed_clicks": "NotRequired[int | None]", + "observed_clicks_events": "NotRequired[int | None]", + "delivery_rate": "NotRequired[float | None]", + "effective_delivery_rate": "NotRequired[float | None]", + "bounce_rate": "NotRequired[float | None]", + "deferral_rate": "NotRequired[float | None]", + "complaint_rate": "NotRequired[float | None]", + "human_open_rate": "NotRequired[float | None]", + "human_click_rate": "NotRequired[float | None]", + "processing_latency_p50_ms": "NotRequired[float | None]", + "processing_latency_p95_ms": "NotRequired[float | None]", + "processing_latency_p99_ms": "NotRequired[float | None]", + "processing_latency_samples": "NotRequired[int | None]", + "delivery_latency_p50_ms": "NotRequired[float | None]", + "delivery_latency_p95_ms": "NotRequired[float | None]", + "delivery_latency_p99_ms": "NotRequired[float | None]", + "delivery_latency_samples": "NotRequired[int | None]", + "total_latency_p50_ms": "NotRequired[float | None]", + "total_latency_p95_ms": "NotRequired[float | None]", + "total_latency_p99_ms": "NotRequired[float | None]", + "total_latency_samples": "NotRequired[int | None]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemTrendItemRateBasesDeliveryRate = TypedDict( + "AnalyticsResponsePayloadBreakdownItemTrendItemRateBasesDeliveryRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemTrendItemRateBasesEffectiveDeliveryRate = TypedDict( + "AnalyticsResponsePayloadBreakdownItemTrendItemRateBasesEffectiveDeliveryRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemTrendItemRateBasesBounceRate = TypedDict( + "AnalyticsResponsePayloadBreakdownItemTrendItemRateBasesBounceRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemTrendItemRateBasesDeferralRate = TypedDict( + "AnalyticsResponsePayloadBreakdownItemTrendItemRateBasesDeferralRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemTrendItemRateBasesComplaintRate = TypedDict( + "AnalyticsResponsePayloadBreakdownItemTrendItemRateBasesComplaintRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemTrendItemRateBasesHumanOpenRate = TypedDict( + "AnalyticsResponsePayloadBreakdownItemTrendItemRateBasesHumanOpenRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemTrendItemRateBasesHumanClickRate = TypedDict( + "AnalyticsResponsePayloadBreakdownItemTrendItemRateBasesHumanClickRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemTrendItemRateBases = TypedDict( + "AnalyticsResponsePayloadBreakdownItemTrendItemRateBases", + { + "delivery_rate": "NotRequired[AnalyticsResponsePayloadBreakdownItemTrendItemRateBasesDeliveryRate]", + "effective_delivery_rate": "NotRequired[AnalyticsResponsePayloadBreakdownItemTrendItemRateBasesEffectiveDeliveryRate]", + "bounce_rate": "NotRequired[AnalyticsResponsePayloadBreakdownItemTrendItemRateBasesBounceRate]", + "deferral_rate": "NotRequired[AnalyticsResponsePayloadBreakdownItemTrendItemRateBasesDeferralRate]", + "complaint_rate": "NotRequired[AnalyticsResponsePayloadBreakdownItemTrendItemRateBasesComplaintRate]", + "human_open_rate": "NotRequired[AnalyticsResponsePayloadBreakdownItemTrendItemRateBasesHumanOpenRate]", + "human_click_rate": "NotRequired[AnalyticsResponsePayloadBreakdownItemTrendItemRateBasesHumanClickRate]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemTrendItemPreviousMetrics = TypedDict( + "AnalyticsResponsePayloadBreakdownItemTrendItemPreviousMetrics", + { + "accepted": "NotRequired[int | None]", + "processed": "NotRequired[int | None]", + "suppressed": "NotRequired[int | None]", + "policy_rejected": "NotRequired[int | None]", + "application_failed": "NotRequired[int | None]", + "mta_accepted": "NotRequired[int | None]", + "canceled": "NotRequired[int | None]", + "messages": "NotRequired[int | None]", + "delivered": "NotRequired[int | None]", + "bounced": "NotRequired[int | None]", + "soft_bounced": "NotRequired[int | None]", + "administratively_bounced": "NotRequired[int | None]", + "deferred_recipients": "NotRequired[int | None]", + "deferred_events": "NotRequired[int | None]", + "delivery_attempts": "NotRequired[int | None]", + "attempted_recipients": "NotRequired[int | None]", + "transport_outcome_recipients": "NotRequired[int | None]", + "effective_delivered": "NotRequired[int | None]", + "open_tracked_delivered": "NotRequired[int | None]", + "click_tracked_delivered": "NotRequired[int | None]", + "out_of_band_bounced_recipients": "NotRequired[int | None]", + "out_of_band_bounce_events": "NotRequired[int | None]", + "complained": "NotRequired[int | None]", + "unsubscribed": "NotRequired[int | None]", + "human_opens": "NotRequired[int | None]", + "human_opens_events": "NotRequired[int | None]", + "human_clicks": "NotRequired[int | None]", + "human_clicks_events": "NotRequired[int | None]", + "machine_opens": "NotRequired[int | None]", + "machine_opens_events": "NotRequired[int | None]", + "machine_clicks": "NotRequired[int | None]", + "machine_clicks_events": "NotRequired[int | None]", + "privacy_opens": "NotRequired[int | None]", + "privacy_opens_events": "NotRequired[int | None]", + "privacy_clicks": "NotRequired[int | None]", + "privacy_clicks_events": "NotRequired[int | None]", + "bot_opens": "NotRequired[int | None]", + "bot_opens_events": "NotRequired[int | None]", + "bot_clicks": "NotRequired[int | None]", + "bot_clicks_events": "NotRequired[int | None]", + "scanner_opens": "NotRequired[int | None]", + "scanner_opens_events": "NotRequired[int | None]", + "scanner_clicks": "NotRequired[int | None]", + "scanner_clicks_events": "NotRequired[int | None]", + "observed_opens": "NotRequired[int | None]", + "observed_opens_events": "NotRequired[int | None]", + "observed_clicks": "NotRequired[int | None]", + "observed_clicks_events": "NotRequired[int | None]", + "delivery_rate": "NotRequired[float | None]", + "effective_delivery_rate": "NotRequired[float | None]", + "bounce_rate": "NotRequired[float | None]", + "deferral_rate": "NotRequired[float | None]", + "complaint_rate": "NotRequired[float | None]", + "human_open_rate": "NotRequired[float | None]", + "human_click_rate": "NotRequired[float | None]", + "processing_latency_p50_ms": "NotRequired[float | None]", + "processing_latency_p95_ms": "NotRequired[float | None]", + "processing_latency_p99_ms": "NotRequired[float | None]", + "processing_latency_samples": "NotRequired[int | None]", + "delivery_latency_p50_ms": "NotRequired[float | None]", + "delivery_latency_p95_ms": "NotRequired[float | None]", + "delivery_latency_p99_ms": "NotRequired[float | None]", + "delivery_latency_samples": "NotRequired[int | None]", + "total_latency_p50_ms": "NotRequired[float | None]", + "total_latency_p95_ms": "NotRequired[float | None]", + "total_latency_p99_ms": "NotRequired[float | None]", + "total_latency_samples": "NotRequired[int | None]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemTrendItemPreviousRateBasesDeliveryRate = TypedDict( + "AnalyticsResponsePayloadBreakdownItemTrendItemPreviousRateBasesDeliveryRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemTrendItemPreviousRateBasesEffectiveDeliveryRate = TypedDict( + "AnalyticsResponsePayloadBreakdownItemTrendItemPreviousRateBasesEffectiveDeliveryRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemTrendItemPreviousRateBasesBounceRate = TypedDict( + "AnalyticsResponsePayloadBreakdownItemTrendItemPreviousRateBasesBounceRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemTrendItemPreviousRateBasesDeferralRate = TypedDict( + "AnalyticsResponsePayloadBreakdownItemTrendItemPreviousRateBasesDeferralRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemTrendItemPreviousRateBasesComplaintRate = TypedDict( + "AnalyticsResponsePayloadBreakdownItemTrendItemPreviousRateBasesComplaintRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemTrendItemPreviousRateBasesHumanOpenRate = TypedDict( + "AnalyticsResponsePayloadBreakdownItemTrendItemPreviousRateBasesHumanOpenRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemTrendItemPreviousRateBasesHumanClickRate = TypedDict( + "AnalyticsResponsePayloadBreakdownItemTrendItemPreviousRateBasesHumanClickRate", + { + "numerator": "Required[int | None]", + "denominator": "Required[int | None]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemTrendItemPreviousRateBases = TypedDict( + "AnalyticsResponsePayloadBreakdownItemTrendItemPreviousRateBases", + { + "delivery_rate": "NotRequired[AnalyticsResponsePayloadBreakdownItemTrendItemPreviousRateBasesDeliveryRate]", + "effective_delivery_rate": "NotRequired[AnalyticsResponsePayloadBreakdownItemTrendItemPreviousRateBasesEffectiveDeliveryRate]", + "bounce_rate": "NotRequired[AnalyticsResponsePayloadBreakdownItemTrendItemPreviousRateBasesBounceRate]", + "deferral_rate": "NotRequired[AnalyticsResponsePayloadBreakdownItemTrendItemPreviousRateBasesDeferralRate]", + "complaint_rate": "NotRequired[AnalyticsResponsePayloadBreakdownItemTrendItemPreviousRateBasesComplaintRate]", + "human_open_rate": "NotRequired[AnalyticsResponsePayloadBreakdownItemTrendItemPreviousRateBasesHumanOpenRate]", + "human_click_rate": "NotRequired[AnalyticsResponsePayloadBreakdownItemTrendItemPreviousRateBasesHumanClickRate]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemTrendItemPrevious = TypedDict( + "AnalyticsResponsePayloadBreakdownItemTrendItemPrevious", + { + "metrics": "Required[AnalyticsResponsePayloadBreakdownItemTrendItemPreviousMetrics]", + "rate_bases": "Required[AnalyticsResponsePayloadBreakdownItemTrendItemPreviousRateBases]", + }, +) + + +AnalyticsResponsePayloadBreakdownItemTrendItem = TypedDict( + "AnalyticsResponsePayloadBreakdownItemTrendItem", + { + "metrics": "Required[AnalyticsResponsePayloadBreakdownItemTrendItemMetrics]", + "rate_bases": "Required[AnalyticsResponsePayloadBreakdownItemTrendItemRateBases]", + "previous": "NotRequired[AnalyticsResponsePayloadBreakdownItemTrendItemPrevious]", + "change": "NotRequired[dict[str, dict[str, Any]]]", + "from": "Required[str]", + "to": "Required[str]", + "available": "Required[bool]", + "partial": "Required[bool]", + }, +) + + +AnalyticsResponsePayloadBreakdownItem = TypedDict( + "AnalyticsResponsePayloadBreakdownItem", + { + "metrics": "Required[AnalyticsResponsePayloadBreakdownItemMetrics]", + "rate_bases": "Required[AnalyticsResponsePayloadBreakdownItemRateBases]", + "previous": "NotRequired[AnalyticsResponsePayloadBreakdownItemPrevious]", + "change": "NotRequired[dict[str, dict[str, Any]]]", + "dimensions": "Required[dict[str, str | None]]", + "trend": "NotRequired[list[AnalyticsResponsePayloadBreakdownItemTrendItem]]", + }, +) + + +AnalyticsResponsePayload = TypedDict( + "AnalyticsResponsePayload", + { + "summary": "NotRequired[AnalyticsResponsePayloadSummary]", + "time_series": "NotRequired[list[AnalyticsResponsePayloadTimeSeriesItem]]", + "breakdown": "NotRequired[list[AnalyticsResponsePayloadBreakdownItem]]", + }, +) diff --git a/tests/test_analytics_forwarding.py b/tests/test_analytics_forwarding.py new file mode 100644 index 0000000..df99030 --- /dev/null +++ b/tests/test_analytics_forwarding.py @@ -0,0 +1,99 @@ +import json + +import pytest +import respx +from httpx import Response + +from lettermint import AsyncLettermint, Lettermint, types + +PAYLOAD = { + "metrics": ["accepted", "delivered"], + "include": ["summary"], + "interval": "day", + "filters": [{"dimension": "project", "operator": "eq", "values": ["project/id"]}], + "sort": {"metric": "accepted", "direction": "desc"}, + "limit": 10, +} +RESOURCE = {"destination": None, "verified": False, "verified_at": None} + + +def routes(): + base = "https://api.lettermint.co/v1" + path = base + "/projects/project%2Fid/report-forwarding" + return [ + respx.post(base + "/analytics").mock( + return_value=Response(200, json={"data": {}, "meta": {}, "pagination": ["cursor"]}) + ), + respx.get(path).mock(return_value=Response(200, json={"data": RESOURCE})), + respx.put(path).mock(return_value=Response(200, json={"data": RESOURCE})), + respx.post(path + "/verify").mock(return_value=Response(200, json={"data": RESOURCE})), + respx.post(path + "/resend-code").mock(return_value=Response(200, json={"data": RESOURCE})), + respx.delete(path).mock(return_value=Response(204)), + ] + + +def assert_requests(registered): + for route in registered: + assert route.call_count == 1 + request = route.calls.last.request + assert request.headers["authorization"] == "Bearer team-token" + assert "x-lettermint-token" not in request.headers + assert json.loads(registered[0].calls.last.request.content) == PAYLOAD + assert json.loads(registered[2].calls.last.request.content) == { + "destination": "reports@example.com" + } + assert json.loads(registered[3].calls.last.request.content) == {"code": "123456"} + + +@respx.mock +def test_sync_analytics_and_forwarding(): + registered = routes() + with Lettermint.api("team-token") as api: + api.analytics(PAYLOAD) + assert api.projects.retrieve_report_forwarding("project/id")["data"] == RESOURCE + api.projects.update_report_forwarding("project/id", {"destination": "reports@example.com"}) + api.projects.verify_report_forwarding("project/id", {"code": "123456"}) + api.projects.resend_report_forwarding_code("project/id") + assert api.projects.delete_report_forwarding("project/id") is None + assert_requests(registered) + + +@pytest.mark.asyncio +@respx.mock +async def test_async_analytics_and_forwarding(): + registered = routes() + async with AsyncLettermint.api("team-token") as api: + await api.analytics(PAYLOAD) + assert (await api.projects.retrieve_report_forwarding("project/id"))["data"] == RESOURCE + await api.projects.update_report_forwarding( + "project/id", {"destination": "reports@example.com"} + ) + await api.projects.verify_report_forwarding("project/id", {"code": "123456"}) + await api.projects.resend_report_forwarding_code("project/id") + assert await api.projects.delete_report_forwarding("project/id") is None + assert_requests(registered) + + +def test_public_types_and_new_fields(): + for name in ("RescheduleMessageResponse", "CancelScheduledMessageResponse", "CursorPaginator"): + assert hasattr(types, name) + assert "api_token" in types.ProjectStoreResponse.__annotations__ + assert "delivery_mode" in types.ProjectListData.__annotations__ + assert "delivery_mode_filter" in types.WebhookListData.__annotations__ + assert "sandbox" in types.WebhookDeliveryListData.__annotations__ + assert "ticket_identifier" in types.SuppressionDestroyResponse.__annotations__ + assert set(types.AnalyticsRequest.__annotations__) == { + "metrics", + "from", + "to", + "timezone", + "include", + "group_by", + "filters", + "interval", + "compare", + "include_trend", + "sort", + "limit", + "cursor", + } diff --git a/tests/test_api_surface.py b/tests/test_api_surface.py index 22b5eea..e8b6b43 100644 --- a/tests/test_api_surface.py +++ b/tests/test_api_surface.py @@ -292,6 +292,12 @@ def test_team_role_and_member_assignment_endpoints(self) -> None: def test_documented_operations_are_exposed(self) -> None: operations = [ + (Lettermint.api("token"), "analytics"), + (Lettermint.api("token").projects, "retrieve_report_forwarding"), + (Lettermint.api("token").projects, "update_report_forwarding"), + (Lettermint.api("token").projects, "delete_report_forwarding"), + (Lettermint.api("token").projects, "verify_report_forwarding"), + (Lettermint.api("token").projects, "resend_report_forwarding_code"), (Lettermint.email("token"), "send"), (Lettermint.email("token"), "send_batch"), (Lettermint.email("token"), "ping"), @@ -349,7 +355,7 @@ def test_documented_operations_are_exposed(self) -> None: missing = [method for endpoint, method in operations if not hasattr(endpoint, method)] - assert len(operations) == 53 + assert len(operations) == 59 assert missing == [] def test_generated_types_match_current_team_schema(self) -> None: From f45533e05b5e8e3e22434157ec7c5fe75934528d Mon Sep 17 00:00:00 2001 From: Bjarn Bronsveld Date: Thu, 1 Oct 2026 07:39:14 +0200 Subject: [PATCH 2/2] fix(api): forward legacy async message calls to message endpoints --- src/lettermint/endpoints/api.py | 11 +++++++++++ tests/test_api_surface.py | 23 +++++++++++++++++------ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/lettermint/endpoints/api.py b/src/lettermint/endpoints/api.py index c86e71d..c7e0e74 100644 --- a/src/lettermint/endpoints/api.py +++ b/src/lettermint/endpoints/api.py @@ -411,6 +411,17 @@ async def retrieve( ), ) + async def reschedule( + self, message_id: str, data: lm_types.RescheduleMessageRequest + ) -> lm_types.RescheduleMessageResponse: + return await AsyncMessagesEndpoint(self._client).reschedule(message_id, data) + + async def cancel(self, message_id: str) -> lm_types.RescheduleMessageResponse: + return await AsyncMessagesEndpoint(self._client).cancel(message_id) + + async def process(self, message_id: str) -> lm_types.ProcessInboundMessageResponse: + return await AsyncMessagesEndpoint(self._client).process(message_id) + async def delete(self, domain_id: str) -> lm_types.DomainDestroyResponse: return cast( lm_types.DomainDestroyResponse, diff --git a/tests/test_api_surface.py b/tests/test_api_surface.py index e8b6b43..259ea5c 100644 --- a/tests/test_api_surface.py +++ b/tests/test_api_surface.py @@ -220,7 +220,8 @@ def test_scheduled_message_endpoints(self) -> None: @respx.mock @pytest.mark.asyncio - async def test_async_scheduled_message_endpoints(self) -> None: + @pytest.mark.parametrize("endpoint_group", ["messages", "domains"]) + async def test_async_scheduled_message_endpoints(self, endpoint_group: str) -> None: reschedule_route = respx.patch("https://api.lettermint.co/v1/messages/message%2Fid").mock( return_value=Response( 200, @@ -253,17 +254,27 @@ async def test_async_scheduled_message_endpoints(self) -> None: ) async with AsyncLettermint.api("api-token") as api: + endpoint = getattr(api, endpoint_group) assert ( - await api.messages.reschedule( - "message/id", {"scheduled_at": "2026-08-27T09:00:00Z"} - ) + await endpoint.reschedule("message/id", {"scheduled_at": "2026-08-27T09:00:00Z"}) )["status"] == "scheduled" - assert (await api.messages.cancel("message/id"))["status"] == "canceled" - assert (await api.messages.process("message/id"))["data"]["status"] == "queued" + canceled: lm_types.RescheduleMessageResponse = await endpoint.cancel("message/id") + assert canceled["message_id"] == "message/id" + assert canceled["status"] == "canceled" + assert canceled["scheduled_at"] is None + assert (await endpoint.process("message/id"))["data"]["status"] == "queued" assert reschedule_route.called assert cancel_route.called assert process_route.called + assert json.loads(reschedule_route.calls.last.request.content) == { + "scheduled_at": "2026-08-27T09:00:00Z" + } + for route in (reschedule_route, cancel_route, process_route): + assert route.call_count == 1 + assert route.calls.last.request.headers["authorization"] == "Bearer api-token" + assert json.loads(cancel_route.calls.last.request.content) == {} + assert json.loads(process_route.calls.last.request.content) == {} @respx.mock def test_team_role_and_member_assignment_endpoints(self) -> None: