Skip to content

Commit 908dbe5

Browse files
committed
Add Stats API
1 parent 8821498 commit 908dbe5

9 files changed

Lines changed: 495 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
1+
## [Unreleased]
2+
3+
- Add StatsApi with get, by_domain, by_category, by_email_service_provider, by_date endpoints
4+
- Add api_query_params to RequestParams for automatic [] serialization of list query params
5+
16
## [2.4.0] - 2025-12-04
2-
* Fix issue #52: Update README.md using new guideline by @Ihor-Bilous in https://github.com/mailtrap/mailtrap-python/pull/55
3-
* Fix issue #53: Add full usage in all examples by @Ihor-Bilous in https://github.com/mailtrap/mailtrap-python/pull/56
4-
* Merge functionality and examples in Readme by @yanchuk in https://github.com/mailtrap/mailtrap-python/pull/57
5-
* Fix issue #54: Add SendingDomainsApi, related models, tests, examples by @Ihor-Bilous in https://github.com/mailtrap/mailtrap-python/pull/58
7+
8+
- Fix issue #52: Update README.md using new guideline by @Ihor-Bilous in https://github.com/mailtrap/mailtrap-python/pull/55
9+
- Fix issue #53: Add full usage in all examples by @Ihor-Bilous in https://github.com/mailtrap/mailtrap-python/pull/56
10+
- Merge functionality and examples in Readme by @yanchuk in https://github.com/mailtrap/mailtrap-python/pull/57
11+
- Fix issue #54: Add SendingDomainsApi, related models, tests, examples by @Ihor-Bilous in https://github.com/mailtrap/mailtrap-python/pull/58
612

713
## [2.3.0] - 2025-10-24
8-
* Fix issue #24: Add batch_send method to SendingApi, add models by @Ihor-Bilous in https://github.com/mailtrap/mailtrap-python/pull/47
9-
* Fix issue #42: Add GeneralApi, related models, examples, tests. by @Ihor-Bilous in https://github.com/mailtrap/mailtrap-python/pull/48
10-
* Fix issue #41: Add ContactExportsApi, related models, tests and examples by @Ihor-Bilous in https://github.com/mailtrap/mailtrap-python/pull/49
11-
* Fix issue #45: Add ContactEventsApi, related models, tests and examples by @Ihor-Bilous in https://github.com/mailtrap/mailtrap-python/pull/51
14+
15+
- Fix issue #24: Add batch_send method to SendingApi, add models by @Ihor-Bilous in https://github.com/mailtrap/mailtrap-python/pull/47
16+
- Fix issue #42: Add GeneralApi, related models, examples, tests. by @Ihor-Bilous in https://github.com/mailtrap/mailtrap-python/pull/48
17+
- Fix issue #41: Add ContactExportsApi, related models, tests and examples by @Ihor-Bilous in https://github.com/mailtrap/mailtrap-python/pull/49
18+
- Fix issue #45: Add ContactEventsApi, related models, tests and examples by @Ihor-Bilous in https://github.com/mailtrap/mailtrap-python/pull/51
1219

1320
## [2.2.0] - 2025-09-18
21+
1422
- Potential fix for code scanning alert no. 1: Workflow does not contain permissions by @mklocek in https://github.com/railsware/mailtrap-python/pull/15
1523
- Fix issue #29. Add support of Emails Sandbox (Testing) API: Projects by @Ihor-Bilous in https://github.com/railsware/mailtrap-python/pull/31
1624
- Issue 25 by @Ihor-Bilous in https://github.com/railsware/mailtrap-python/pull/33
@@ -25,13 +33,15 @@
2533
- Fix issue #28: Add AttachmentsApi, related models, tests, examples by @Ihor-Bilous in https://github.com/railsware/mailtrap-python/pull/44
2634

2735
## [2.1.0] - 2025-05-12
36+
2837
- Add sandbox mode support in MailtrapClient
2938
- It requires inbox_id parameter to be set
3039
- Add bulk mode support in MailtrapClient
3140
- Drop support python 3.6 - 3.8
3241
- Add support for python 3.12 - 3.13
3342

3443
## [2.0.1] - 2023-05-18
44+
3545
- Add User-Agent header to all requests
3646

3747
## [2.0.0] - 2023-03-11

examples/general/stats.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import mailtrap as mt
2+
from mailtrap.models.stats import SendingStatGroup, SendingStats, StatsFilterParams
3+
4+
API_TOKEN = "YOUR_API_TOKEN"
5+
ACCOUNT_ID = 123456
6+
7+
client = mt.MailtrapClient(token=API_TOKEN)
8+
stats_api = client.general_api.stats
9+
10+
11+
def get_stats(account_id: int) -> SendingStats:
12+
params = StatsFilterParams(start_date="2026-01-01", end_date="2026-01-31")
13+
return stats_api.get(account_id=account_id, params=params)
14+
15+
16+
def get_stats_by_domain(account_id: int) -> list[SendingStatGroup]:
17+
params = StatsFilterParams(start_date="2026-01-01", end_date="2026-01-31")
18+
return stats_api.by_domain(account_id=account_id, params=params)
19+
20+
21+
def get_stats_by_category(account_id: int) -> list[SendingStatGroup]:
22+
params = StatsFilterParams(start_date="2026-01-01", end_date="2026-01-31")
23+
return stats_api.by_category(account_id=account_id, params=params)
24+
25+
26+
def get_stats_by_email_service_provider(account_id: int) -> list[SendingStatGroup]:
27+
params = StatsFilterParams(start_date="2026-01-01", end_date="2026-01-31")
28+
return stats_api.by_email_service_provider(account_id=account_id, params=params)
29+
30+
31+
def get_stats_by_date(account_id: int) -> list[SendingStatGroup]:
32+
params = StatsFilterParams(start_date="2026-01-01", end_date="2026-01-31")
33+
return stats_api.by_date(account_id=account_id, params=params)
34+
35+
36+
def get_stats_with_filters(account_id: int) -> SendingStats:
37+
params = StatsFilterParams(
38+
start_date="2026-01-01",
39+
end_date="2026-01-31",
40+
sending_domain_ids=[1, 2],
41+
sending_streams=["transactional"],
42+
categories=["Welcome email", "Marketing"],
43+
email_service_providers=["Gmail", "Yahoo"],
44+
)
45+
return stats_api.get(account_id=account_id, params=params)
46+
47+
48+
def get_stats_by_domain_with_filters(account_id: int) -> list[SendingStatGroup]:
49+
params = StatsFilterParams(
50+
start_date="2026-01-01",
51+
end_date="2026-01-31",
52+
sending_streams=["transactional"],
53+
categories=["Welcome email"],
54+
)
55+
return stats_api.by_domain(account_id=account_id, params=params)
56+
57+
58+
if __name__ == "__main__":
59+
print(get_stats(ACCOUNT_ID))
60+
print(get_stats_by_domain(ACCOUNT_ID))
61+
print(get_stats_by_category(ACCOUNT_ID))
62+
print(get_stats_by_email_service_provider(ACCOUNT_ID))
63+
print(get_stats_by_date(ACCOUNT_ID))
64+
print(get_stats_with_filters(ACCOUNT_ID))
65+
print(get_stats_by_domain_with_filters(ACCOUNT_ID))

mailtrap/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,6 @@
3232
from .models.projects import ProjectParams
3333
from .models.sending_domains import CreateSendingDomainParams
3434
from .models.sending_domains import SendSetupInstructionsParams
35+
from .models.stats import StatsFilterParams
3536
from .models.templates import CreateEmailTemplateParams
3637
from .models.templates import UpdateEmailTemplateParams

mailtrap/api/general.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from mailtrap.api.resources.accounts import AccountsApi
33
from mailtrap.api.resources.billing import BillingApi
44
from mailtrap.api.resources.permissions import PermissionsApi
5+
from mailtrap.api.resources.stats import StatsApi
56
from mailtrap.http import HttpClient
67

78

@@ -24,3 +25,7 @@ def billing(self) -> BillingApi:
2425
@property
2526
def permissions(self) -> PermissionsApi:
2627
return PermissionsApi(client=self._client)
28+
29+
@property
30+
def stats(self) -> StatsApi:
31+
return StatsApi(client=self._client)

mailtrap/api/resources/stats.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from typing import Literal
2+
3+
from mailtrap.http import HttpClient
4+
from mailtrap.models.stats import SendingStatGroup, SendingStats, StatsFilterParams
5+
6+
GroupKey = Literal["domains", "categories", "email_service_providers", "date"]
7+
8+
_GROUP_KEYS = {
9+
"domains": "sending_domain_id",
10+
"categories": "category",
11+
"email_service_providers": "email_service_provider",
12+
"date": "date",
13+
}
14+
15+
16+
class StatsApi:
17+
def __init__(self, client: HttpClient) -> None:
18+
self._client = client
19+
20+
def get(self, account_id: int, params: StatsFilterParams) -> SendingStats:
21+
"""Get aggregated sending stats."""
22+
response = self._client.get(
23+
self._base_path(account_id),
24+
params=params.api_query_params,
25+
)
26+
return SendingStats(**response)
27+
28+
def by_domain(
29+
self, account_id: int, params: StatsFilterParams
30+
) -> list[SendingStatGroup]:
31+
"""Get sending stats grouped by domains."""
32+
return self._grouped_stats(account_id, "domains", params)
33+
34+
def by_category(
35+
self, account_id: int, params: StatsFilterParams
36+
) -> list[SendingStatGroup]:
37+
"""Get sending stats grouped by categories."""
38+
return self._grouped_stats(account_id, "categories", params)
39+
40+
def by_email_service_provider(
41+
self, account_id: int, params: StatsFilterParams
42+
) -> list[SendingStatGroup]:
43+
"""Get sending stats grouped by email service providers."""
44+
return self._grouped_stats(account_id, "email_service_providers", params)
45+
46+
def by_date(
47+
self, account_id: int, params: StatsFilterParams
48+
) -> list[SendingStatGroup]:
49+
"""Get sending stats grouped by date."""
50+
return self._grouped_stats(account_id, "date", params)
51+
52+
def _grouped_stats(
53+
self, account_id: int, group: GroupKey, params: StatsFilterParams
54+
) -> list[SendingStatGroup]:
55+
response = self._client.get(
56+
f"{self._base_path(account_id)}/{group}", params=params.api_query_params
57+
)
58+
group_key = _GROUP_KEYS[group]
59+
60+
return [
61+
SendingStatGroup(
62+
name=group_key,
63+
value=item[group_key],
64+
stats=SendingStats(**item["stats"]),
65+
)
66+
for item in response
67+
]
68+
69+
@staticmethod
70+
def _base_path(account_id: int) -> str:
71+
return f"/api/accounts/{account_id}/stats"

mailtrap/models/common.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ def api_data(self: T) -> dict[str, Any]:
1818
TypeAdapter(type(self)).dump_python(self, by_alias=True, exclude_none=True),
1919
)
2020

21+
@property
22+
def api_query_params(self: T) -> dict[str, Any]:
23+
data = self.api_data
24+
for key, value in list(data.items()):
25+
if isinstance(value, list):
26+
data[f"{key}[]"] = data.pop(key)
27+
return data
28+
2129

2230
@dataclass
2331
class DeletedObject:

mailtrap/models/stats.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from typing import Optional
2+
from typing import Union
3+
4+
from pydantic.dataclasses import dataclass
5+
6+
from mailtrap.models.common import RequestParams
7+
8+
9+
@dataclass
10+
class SendingStats:
11+
delivery_count: int
12+
delivery_rate: float
13+
bounce_count: int
14+
bounce_rate: float
15+
open_count: int
16+
open_rate: float
17+
click_count: int
18+
click_rate: float
19+
spam_count: int
20+
spam_rate: float
21+
22+
23+
@dataclass
24+
class SendingStatGroup:
25+
name: str
26+
value: Union[str, int]
27+
stats: SendingStats
28+
29+
30+
@dataclass
31+
class StatsFilterParams(RequestParams):
32+
start_date: Optional[str] = None
33+
end_date: Optional[str] = None
34+
sending_domain_ids: Optional[list[int]] = None
35+
sending_streams: Optional[list[str]] = None
36+
categories: Optional[list[str]] = None
37+
email_service_providers: Optional[list[str]] = None

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ dynamic = ["dependencies"]
2323
Homepage = "https://mailtrap.io/"
2424
Documentation = "https://github.com/railsware/mailtrap-python"
2525
Repository = "https://github.com/railsware/mailtrap-python.git"
26-
"API documentation" = "https://api-docs.mailtrap.io/"
26+
"API documentation" = "https://docs.mailtrap.io/developers"
2727

2828
[build-system]
2929
requires = ["setuptools"]

0 commit comments

Comments
 (0)