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
26 changes: 18 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
## [2.5.0] - 2026-03-06

- Add StatsApi with get, by_domains, by_categories, by_email_service_providers, by_date endpoints
- Add api_query_params to RequestParams for automatic [] serialization of list query params

## [2.4.0] - 2025-12-04
* Fix issue #52: Update README.md using new guideline by @Ihor-Bilous in https://github.com/mailtrap/mailtrap-python/pull/55
* Fix issue #53: Add full usage in all examples by @Ihor-Bilous in https://github.com/mailtrap/mailtrap-python/pull/56
* Merge functionality and examples in Readme by @yanchuk in https://github.com/mailtrap/mailtrap-python/pull/57
* Fix issue #54: Add SendingDomainsApi, related models, tests, examples by @Ihor-Bilous in https://github.com/mailtrap/mailtrap-python/pull/58

- Fix issue #52: Update README.md using new guideline by @Ihor-Bilous in https://github.com/mailtrap/mailtrap-python/pull/55
- Fix issue #53: Add full usage in all examples by @Ihor-Bilous in https://github.com/mailtrap/mailtrap-python/pull/56
- Merge functionality and examples in Readme by @yanchuk in https://github.com/mailtrap/mailtrap-python/pull/57
- Fix issue #54: Add SendingDomainsApi, related models, tests, examples by @Ihor-Bilous in https://github.com/mailtrap/mailtrap-python/pull/58

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

- Fix issue #24: Add batch_send method to SendingApi, add models by @Ihor-Bilous in https://github.com/mailtrap/mailtrap-python/pull/47
- Fix issue #42: Add GeneralApi, related models, examples, tests. by @Ihor-Bilous in https://github.com/mailtrap/mailtrap-python/pull/48
- Fix issue #41: Add ContactExportsApi, related models, tests and examples by @Ihor-Bilous in https://github.com/mailtrap/mailtrap-python/pull/49
- Fix issue #45: Add ContactEventsApi, related models, tests and examples by @Ihor-Bilous in https://github.com/mailtrap/mailtrap-python/pull/51

## [2.2.0] - 2025-09-18

- Potential fix for code scanning alert no. 1: Workflow does not contain permissions by @mklocek in https://github.com/railsware/mailtrap-python/pull/15
- Fix issue #29. Add support of Emails Sandbox (Testing) API: Projects by @Ihor-Bilous in https://github.com/railsware/mailtrap-python/pull/31
- Issue 25 by @Ihor-Bilous in https://github.com/railsware/mailtrap-python/pull/33
Expand All @@ -25,13 +33,15 @@
- Fix issue #28: Add AttachmentsApi, related models, tests, examples by @Ihor-Bilous in https://github.com/railsware/mailtrap-python/pull/44

## [2.1.0] - 2025-05-12

- Add sandbox mode support in MailtrapClient
- It requires inbox_id parameter to be set
- Add bulk mode support in MailtrapClient
- Drop support python 3.6 - 3.8
- Add support for python 3.12 - 3.13

## [2.0.1] - 2023-05-18

- Add User-Agent header to all requests

## [2.0.0] - 2023-03-11
Expand Down
67 changes: 67 additions & 0 deletions examples/general/stats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import mailtrap as mt
from mailtrap.models.stats import SendingStatGroup
from mailtrap.models.stats import SendingStats
from mailtrap.models.stats import StatsFilterParams

API_TOKEN = "YOUR_API_TOKEN"
ACCOUNT_ID = "YOUR_ACCOUNT_ID"
Comment on lines +6 to +7
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Type mismatch between ACCOUNT_ID and function signatures.

ACCOUNT_ID is defined as a string, but all helper functions declare account_id: int. Users copying this example may inadvertently pass a string when an integer is expected.

Proposed fix
 API_TOKEN = "YOUR_API_TOKEN"
-ACCOUNT_ID = "YOUR_ACCOUNT_ID"
+ACCOUNT_ID = 123456  # Replace with your actual account ID
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
API_TOKEN = "YOUR_API_TOKEN"
ACCOUNT_ID = "YOUR_ACCOUNT_ID"
API_TOKEN = "YOUR_API_TOKEN"
ACCOUNT_ID = 123456 # Replace with your actual account ID
🧰 Tools
🪛 Ruff (0.15.4)

[error] 6-6: Possible hardcoded password assigned to: "API_TOKEN"

(S105)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/general/stats.py` around lines 6 - 7, The example defines ACCOUNT_ID
as a string while helper functions expect account_id: int; update the example to
provide an integer ACCOUNT_ID (replace "YOUR_ACCOUNT_ID" with a numeric literal)
or explicitly cast/convert it before passing to functions so the value matches
the account_id: int signatures (look for the ACCOUNT_ID constant and any
functions/methods that declare account_id: int).


client = mt.MailtrapClient(token=API_TOKEN)
stats_api = client.general_api.stats


def get_stats(account_id: int) -> SendingStats:
params = StatsFilterParams(start_date="2026-01-01", end_date="2026-01-31")
return stats_api.get(account_id=account_id, params=params)


def get_stats_by_domains(account_id: int) -> list[SendingStatGroup]:
params = StatsFilterParams(start_date="2026-01-01", end_date="2026-01-31")
return stats_api.by_domains(account_id=account_id, params=params)


def get_stats_by_categories(account_id: int) -> list[SendingStatGroup]:
params = StatsFilterParams(start_date="2026-01-01", end_date="2026-01-31")
return stats_api.by_categories(account_id=account_id, params=params)


def get_stats_by_email_service_providers(account_id: int) -> list[SendingStatGroup]:
params = StatsFilterParams(start_date="2026-01-01", end_date="2026-01-31")
return stats_api.by_email_service_providers(account_id=account_id, params=params)


def get_stats_by_date(account_id: int) -> list[SendingStatGroup]:
params = StatsFilterParams(start_date="2026-01-01", end_date="2026-01-31")
return stats_api.by_date(account_id=account_id, params=params)


def get_stats_with_filters(account_id: int) -> SendingStats:
params = StatsFilterParams(
start_date="2026-01-01",
end_date="2026-01-31",
sending_domain_ids=[1, 2],
sending_streams=["transactional"],
categories=["Transactional", "Marketing"],
email_service_providers=["Gmail", "Yahoo"],
)
return stats_api.get(account_id=account_id, params=params)


def get_stats_by_domains_with_filters(account_id: int) -> list[SendingStatGroup]:
params = StatsFilterParams(
start_date="2026-01-01",
end_date="2026-01-31",
sending_streams=["transactional"],
categories=["Transactional"],
)
return stats_api.by_domains(account_id=account_id, params=params)


if __name__ == "__main__":
print(get_stats(ACCOUNT_ID))
print(get_stats_by_domains(ACCOUNT_ID))
print(get_stats_by_categories(ACCOUNT_ID))
print(get_stats_by_email_service_providers(ACCOUNT_ID))
print(get_stats_by_date(ACCOUNT_ID))
print(get_stats_with_filters(ACCOUNT_ID))
print(get_stats_by_domains_with_filters(ACCOUNT_ID))
1 change: 1 addition & 0 deletions mailtrap/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,6 @@
from .models.projects import ProjectParams
from .models.sending_domains import CreateSendingDomainParams
from .models.sending_domains import SendSetupInstructionsParams
from .models.stats import StatsFilterParams
from .models.templates import CreateEmailTemplateParams
from .models.templates import UpdateEmailTemplateParams
5 changes: 5 additions & 0 deletions mailtrap/api/general.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from mailtrap.api.resources.accounts import AccountsApi
from mailtrap.api.resources.billing import BillingApi
from mailtrap.api.resources.permissions import PermissionsApi
from mailtrap.api.resources.stats import StatsApi
from mailtrap.http import HttpClient


Expand All @@ -24,3 +25,7 @@ def billing(self) -> BillingApi:
@property
def permissions(self) -> PermissionsApi:
return PermissionsApi(client=self._client)

@property
def stats(self) -> StatsApi:
return StatsApi(client=self._client)
69 changes: 69 additions & 0 deletions mailtrap/api/resources/stats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from mailtrap.http import HttpClient
from mailtrap.models.stats import SendingStatGroup
from mailtrap.models.stats import SendingStats
from mailtrap.models.stats import StatsFilterParams

GROUP_KEYS = {
"domains": "sending_domain_id",
"categories": "category",
"email_service_providers": "email_service_provider",
"date": "date",
}


class StatsApi:
def __init__(self, client: HttpClient) -> None:
self._client = client

def get(self, account_id: int, params: StatsFilterParams) -> SendingStats:
"""Get aggregated sending stats."""
response = self._client.get(
self._base_path(account_id),
params=params.api_query_params,
)
return SendingStats(**response)

def by_domains(
self, account_id: int, params: StatsFilterParams
) -> list[SendingStatGroup]:
"""Get sending stats grouped by domains."""
return self._grouped_stats(account_id, "domains", params)

def by_categories(
self, account_id: int, params: StatsFilterParams
) -> list[SendingStatGroup]:
"""Get sending stats grouped by categories."""
return self._grouped_stats(account_id, "categories", params)

def by_email_service_providers(
self, account_id: int, params: StatsFilterParams
) -> list[SendingStatGroup]:
"""Get sending stats grouped by email service providers."""
return self._grouped_stats(account_id, "email_service_providers", params)

def by_date(
self, account_id: int, params: StatsFilterParams
) -> list[SendingStatGroup]:
"""Get sending stats grouped by date."""
return self._grouped_stats(account_id, "date", params)

def _grouped_stats(
self, account_id: int, group: str, params: StatsFilterParams
) -> list[SendingStatGroup]:
response = self._client.get(
f"{self._base_path(account_id)}/{group}", params=params.api_query_params
)
group_key = GROUP_KEYS[group]

return [
SendingStatGroup(
name=group_key,
value=item[group_key],
stats=SendingStats(**item["stats"]),
)
for item in response
]

@staticmethod
def _base_path(account_id: int) -> str:
return f"/api/accounts/{account_id}/stats"
8 changes: 8 additions & 0 deletions mailtrap/models/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ def api_data(self: T) -> dict[str, Any]:
TypeAdapter(type(self)).dump_python(self, by_alias=True, exclude_none=True),
)

@property
def api_query_params(self: T) -> dict[str, Any]:
data = self.api_data
for key, value in list(data.items()):
if isinstance(value, list):
data[f"{key}[]"] = data.pop(key)
return data


@dataclass
class DeletedObject:
Expand Down
37 changes: 37 additions & 0 deletions mailtrap/models/stats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from typing import Optional
from typing import Union

from pydantic.dataclasses import dataclass

from mailtrap.models.common import RequestParams


@dataclass
class SendingStats:
delivery_count: int
delivery_rate: float
bounce_count: int
bounce_rate: float
open_count: int
open_rate: float
click_count: int
click_rate: float
spam_count: int
spam_rate: float


@dataclass
class SendingStatGroup:
name: str
value: Union[str, int]
stats: SendingStats


@dataclass
class StatsFilterParams(RequestParams):
start_date: Optional[str] = None
end_date: Optional[str] = None
sending_domain_ids: Optional[list[int]] = None
sending_streams: Optional[list[str]] = None
categories: Optional[list[str]] = None
email_service_providers: Optional[list[str]] = None
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "mailtrap"
version = "2.4.0"
version = "2.5.0"
description = "Official mailtrap.io API client"
readme = "README.md"
license = {file = "LICENSE.txt"}
Expand All @@ -23,7 +23,7 @@ dynamic = ["dependencies"]
Homepage = "https://mailtrap.io/"
Documentation = "https://github.com/railsware/mailtrap-python"
Repository = "https://github.com/railsware/mailtrap-python.git"
"API documentation" = "https://api-docs.mailtrap.io/"
"API documentation" = "https://docs.mailtrap.io/developers"

[build-system]
requires = ["setuptools"]
Expand Down
Loading