Skip to content
Open
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
154 changes: 139 additions & 15 deletions spec/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -993,7 +993,7 @@
"get": {
"operationId": "getEmailMetrics",
"summary": "Retrieve email metrics",
"description": "Resend-compatible counts and rates over a date range, for transactional and broadcast email together. Leave dimensions out for one totals row; add period, domain, email or broadcast (email and broadcast cannot be combined) to break the numbers down. Each lifecycle timestamp counts in its own period. Sends where every recipient is the SES mailbox simulator are left out. Rates are percentages with one decimal. Open and click metrics need tracking on. Requires the email:read scope.",
"description": "Resend-compatible counts and rates over a date range, for transactional and broadcast email together. Leave dimensions out for one totals row; add period, domain, email or broadcast (email and broadcast cannot be combined) to break the numbers down. Each lifecycle timestamp counts in its own period. Sends where every recipient is the SES mailbox simulator are left out. Rates are percentages with one decimal. Open and click metrics need tracking on. Add include=providers for a breakdown by mailbox provider (Gmail, Outlook, iCloud, Yahoo, Proton, other). Requires the email:read scope.",
"tags": [
"Emails"
],
Expand Down Expand Up @@ -1077,6 +1077,17 @@
"schema": {
"type": "string"
}
},
{
"name": "include",
"in": "query",
"description": "providers adds a providers array: sent email by the recipient's mailbox provider over the range and the id filters, each email counted once by its first To address, by the time it was created and its current status. Not part of Resend's API.",
"schema": {
"type": "string",
"enum": [
"providers"
]
}
}
],
"responses": {
Expand Down Expand Up @@ -1218,6 +1229,13 @@
"open_rate": 49.7
}
}
},
"providers": {
"type": "array",
"description": "With include=providers: one row per mailbox provider with at least one sent email, most sent first. A recipient domain not classified yet counts as other for that request (up to 50 new domains are looked up per request).",
"items": {
"$ref": "#/components/schemas/MailboxProviderMetrics"
}
}
}
}
Expand Down Expand Up @@ -7290,7 +7308,8 @@
"properties": {
"tier": {
"type": "string",
"example": "pro"
"example": "growth",
"description": "The plan: free, starter, growth or scale."
},
"status": {
"type": "string",
Expand Down Expand Up @@ -17020,10 +17039,11 @@
"items": {
"type": "string",
"format": "email"
}
},
"maxItems": 50
}
],
"description": "Reply-To address or addresses"
"description": "Reply-To address or addresses, up to 50. Every address goes in the Reply-To header."
},
"subject": {
"type": "string",
Expand Down Expand Up @@ -17252,7 +17272,7 @@
"string",
"null"
],
"description": "The Reply-To address the email was sent with, or null."
"description": "The Reply-To address the email was sent with, or the first one when it had several, or null."
},
"tags": {
"type": "array",
Expand Down Expand Up @@ -17663,7 +17683,7 @@
},
"example": {
"company": "Acme",
"plan": "pro"
"plan": "growth"
}
},
"segment_ids": {
Expand Down Expand Up @@ -17874,17 +17894,18 @@
},
"contact_limit": {
"type": "integer",
"example": 25000,
"example": 50000,
"description": "The plan's contact limit, with any override for the account"
},
"contact_limit_base": {
"type": "integer",
"example": 25000,
"example": 50000,
"description": "The plan's contact limit without overrides"
},
"tier": {
"type": "string",
"example": "pro"
"example": "growth",
"description": "The plan: free, starter, growth or scale."
}
}
},
Expand Down Expand Up @@ -18285,7 +18306,8 @@
},
"tier": {
"type": "string",
"example": "starter"
"example": "starter",
"description": "The plan: free, starter, growth or scale."
}
}
},
Expand Down Expand Up @@ -19233,6 +19255,107 @@
}
}
},
"MailboxProviderMetrics": {
"type": "object",
"description": "Sent email to one mailbox provider. Each email counts once, by the domain of its first To address; a domain outside the consumer domains is classified by its MX hosts, so Google Workspace counts as gmail and Microsoft 365 as outlook. Rates are percentages with two decimals, null when there is nothing to rate yet.",
"required": [
"provider",
"name",
"sent",
"share",
"delivered",
"bounced",
"complained",
"tracked_delivered",
"opened",
"delivery_rate",
"open_rate",
"bounce_rate",
"complaint_rate"
],
"properties": {
"provider": {
"type": "string",
"enum": [
"gmail",
"outlook",
"icloud",
"yahoo",
"proton",
"other"
]
},
"name": {
"type": "string",
"example": "Gmail"
},
"sent": {
"type": "integer",
"description": "Handed to SES: sent, delivered, opened, clicked, bounced or complained.",
"example": 812
},
"share": {
"type": "number",
"description": "Percent of all sent email in the breakdown.",
"example": 67.44
},
"delivered": {
"type": "integer",
"description": "Accepted by the receiving server, complained included.",
"example": 798
},
"bounced": {
"type": "integer",
"example": 9
},
"complained": {
"type": "integer",
"example": 1
},
"tracked_delivered": {
"type": "integer",
"description": "Delivered with open tracking on: the open rate's denominator.",
"example": 790
},
"opened": {
"type": "integer",
"description": "Of tracked_delivered, opened at least once.",
"example": 402
},
"delivery_rate": {
"type": [
"number",
"null"
],
"description": "delivered / (delivered + bounced): over emails with a delivery or bounce report.",
"example": 98.88
},
"open_rate": {
"type": [
"number",
"null"
],
"description": "opened / tracked_delivered. Apple Mail Privacy Protection loads the open pixel before anyone reads the email, so the icloud rate reads high.",
"example": 50.89
},
"bounce_rate": {
"type": [
"number",
"null"
],
"description": "bounced / (delivered + bounced).",
"example": 1.12
},
"complaint_rate": {
"type": [
"number",
"null"
],
"description": "complained / delivered.",
"example": 0.13
}
}
},
"TemplateVariable": {
"type": "object",
"required": [
Expand Down Expand Up @@ -21533,7 +21656,7 @@
},
"limit": {
"type": "integer",
"example": 50000
"example": 100000
},
"sent": {
"type": "integer",
Expand Down Expand Up @@ -21564,7 +21687,7 @@
},
"effective_remaining": {
"type": "integer",
"example": 41580,
"example": 91580,
"description": "What can still be sent before a send is refused: the monthly allowance left plus credits"
}
}
Expand All @@ -21581,7 +21704,7 @@
},
"limit": {
"type": "integer",
"example": 25000
"example": 50000
}
}
},
Expand Down Expand Up @@ -21823,11 +21946,12 @@
},
"limit": {
"type": "integer",
"example": 50000
"example": 100000
},
"tier": {
"type": "string",
"example": "pro"
"example": "growth",
"description": "The plan: free, starter, growth or scale."
}
}
},
Expand Down
8 changes: 4 additions & 4 deletions src/smtpfast/_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,9 @@ def share(self, id: str, params: ShareEmailParams | None = None, *, options: Req
"""Create a share link for a sent email. `POST /v1/emails/{id}/share`"""
return self._core.request("POST", "/v1/emails/{id}/share", path_params={"id": id}, query=None, body=params, options=options) # type: ignore[no-any-return]

def get_metrics(self, *, start_date: str | None = None, end_date: str | None = None, timezone: str | None = None, granularity: Literal["hourly", "daily", "weekly", "monthly"] | None = None, metrics: str | None = None, dimensions: str | None = None, domain_id: str | None = None, email_id: str | None = None, broadcast_id: str | None = None, options: RequestOptions | None = None) -> GetEmailMetricsResponse:
def get_metrics(self, *, start_date: str | None = None, end_date: str | None = None, timezone: str | None = None, granularity: Literal["hourly", "daily", "weekly", "monthly"] | None = None, metrics: str | None = None, dimensions: str | None = None, domain_id: str | None = None, email_id: str | None = None, broadcast_id: str | None = None, include: Literal["providers"] | None = None, options: RequestOptions | None = None) -> GetEmailMetricsResponse:
"""Retrieve email metrics. `GET /v1/emails/metrics`"""
return self._core.request("GET", "/v1/emails/metrics", path_params=None, query={"start_date": start_date, "end_date": end_date, "timezone": timezone, "granularity": granularity, "metrics": metrics, "dimensions": dimensions, "domain_id": domain_id, "email_id": email_id, "broadcast_id": broadcast_id}, body=None, options=options) # type: ignore[no-any-return]
return self._core.request("GET", "/v1/emails/metrics", path_params=None, query={"start_date": start_date, "end_date": end_date, "timezone": timezone, "granularity": granularity, "metrics": metrics, "dimensions": dimensions, "domain_id": domain_id, "email_id": email_id, "broadcast_id": broadcast_id, "include": include}, body=None, options=options) # type: ignore[no-any-return]

def list_clicked_links(self, *, days: int | None = None, domain_id: str | None = None, url: str | None = None, options: RequestOptions | None = None) -> ListClickedLinksResponse:
"""List clicked links. `GET /v1/emails/clicked-links`"""
Expand Down Expand Up @@ -930,9 +930,9 @@ async def share(self, id: str, params: ShareEmailParams | None = None, *, option
"""Create a share link for a sent email. `POST /v1/emails/{id}/share`"""
return await self._core.request("POST", "/v1/emails/{id}/share", path_params={"id": id}, query=None, body=params, options=options) # type: ignore[no-any-return]

async def get_metrics(self, *, start_date: str | None = None, end_date: str | None = None, timezone: str | None = None, granularity: Literal["hourly", "daily", "weekly", "monthly"] | None = None, metrics: str | None = None, dimensions: str | None = None, domain_id: str | None = None, email_id: str | None = None, broadcast_id: str | None = None, options: RequestOptions | None = None) -> GetEmailMetricsResponse:
async def get_metrics(self, *, start_date: str | None = None, end_date: str | None = None, timezone: str | None = None, granularity: Literal["hourly", "daily", "weekly", "monthly"] | None = None, metrics: str | None = None, dimensions: str | None = None, domain_id: str | None = None, email_id: str | None = None, broadcast_id: str | None = None, include: Literal["providers"] | None = None, options: RequestOptions | None = None) -> GetEmailMetricsResponse:
"""Retrieve email metrics. `GET /v1/emails/metrics`"""
return await self._core.request("GET", "/v1/emails/metrics", path_params=None, query={"start_date": start_date, "end_date": end_date, "timezone": timezone, "granularity": granularity, "metrics": metrics, "dimensions": dimensions, "domain_id": domain_id, "email_id": email_id, "broadcast_id": broadcast_id}, body=None, options=options) # type: ignore[no-any-return]
return await self._core.request("GET", "/v1/emails/metrics", path_params=None, query={"start_date": start_date, "end_date": end_date, "timezone": timezone, "granularity": granularity, "metrics": metrics, "dimensions": dimensions, "domain_id": domain_id, "email_id": email_id, "broadcast_id": broadcast_id, "include": include}, body=None, options=options) # type: ignore[no-any-return]

async def list_clicked_links(self, *, days: int | None = None, domain_id: str | None = None, url: str | None = None, options: RequestOptions | None = None) -> ListClickedLinksResponse:
"""List clicked links. `GET /v1/emails/clicked-links`"""
Expand Down
22 changes: 22 additions & 0 deletions src/smtpfast/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -1194,6 +1194,27 @@
)


# Sent email to one mailbox provider. Each email counts once, by the domain of its first To address; a domain outside the consumer domains is classified
MailboxProviderMetrics = TypedDict(
"MailboxProviderMetrics",
{
"provider": Literal["gmail", "outlook", "icloud", "yahoo", "proton", "other"],
"name": str,
"sent": int,
"share": float,
"delivered": int,
"bounced": int,
"complained": int,
"tracked_delivered": int,
"opened": int,
"delivery_rate": Union[float, None],
"open_rate": Union[float, None],
"bounce_rate": Union[float, None],
"complaint_rate": Union[float, None],
},
)


TemplateVariable = TypedDict(
"TemplateVariable",
{
Expand Down Expand Up @@ -2188,6 +2209,7 @@
"granularity": Literal["hourly", "daily", "weekly", "monthly"],
"totals": dict[str, float],
"data": NotRequired[list[GetEmailMetricsResponseDataItem]],
"providers": NotRequired[list[MailboxProviderMetrics]],
},
)

Expand Down
Loading