Skip to content

Commit 852a1a7

Browse files
committed
Add Stats API
1 parent 750af1d commit 852a1a7

File tree

11 files changed

+505
-9
lines changed

11 files changed

+505
-9
lines changed

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

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,9 @@ The same situation applies to both `client.batch_send()` and `client.sending_api
247247
### Suppressions API:
248248
- Suppressions (find & delete) – [`suppressions/suppressions.py`](examples/suppressions/suppressions.py)
249249

250+
### Stats API:
251+
- Sending stats – [`stats/stats.py`](examples/stats/stats.py)
252+
250253
### General API:
251254
- Account Accesses management – [`general/account_accesses.py`](examples/general/account_accesses.py)
252255
- Accounts info – [`general/accounts.py`](examples/general/accounts.py)

examples/stats/stats.py

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

mailtrap/api/resources/stats.py

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

mailtrap/client.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from mailtrap.api.contacts import ContactsBaseApi
1010
from mailtrap.api.general import GeneralApi
11+
from mailtrap.api.resources.stats import StatsApi
1112
from mailtrap.api.sending import SendingApi
1213
from mailtrap.api.sending_domains import SendingDomainsBaseApi
1314
from mailtrap.api.suppressions import SuppressionsBaseApi
@@ -116,6 +117,12 @@ def sending_api(self) -> SendingApi:
116117
http_client = HttpClient(host=self._sending_api_host, headers=self.headers)
117118
return SendingApi(client=http_client, inbox_id=self.inbox_id)
118119

120+
@property
121+
def stats_api(self) -> StatsApi:
122+
return StatsApi(
123+
client=HttpClient(host=GENERAL_HOST, headers=self.headers),
124+
)
125+
119126
def send(self, mail: BaseMail) -> SEND_ENDPOINT_RESPONSE:
120127
sending_response = self.sending_api.send(mail)
121128
return cast(

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)