Skip to content

Release 2.5.0 - Add Stats API#63

Open
piobeny wants to merge 3 commits intomainfrom
release-2.5.0
Open

Release 2.5.0 - Add Stats API#63
piobeny wants to merge 3 commits intomainfrom
release-2.5.0

Conversation

@piobeny
Copy link

@piobeny piobeny commented Mar 9, 2026

Motivation

  • Add support for the Email Sending Stats API (/api/accounts/{account_id}/stats) to the Python SDK, enabling users to retrieve aggregated email sending statistics.

Changes

  • Add SendingStats and SendingStatGroup pydantic dataclasses with delivery, bounce, open, click, and spam counts/rates
  • Add StatsFilterParams dataclass extending RequestParams for filter handling
  • Add StatsApi class with 5 methods: get, by_domains, by_categories, by_email_service_providers, by_date
  • Add api_query_params to RequestParams for automatic [] serialization of list query params
  • Add usage example in examples/general/stats.py
  • Update README with Stats API reference
  • Update CHANGELOG with entry for v2.5.0

How to test

  • stats_api.get(account_id, params) with different parameters (start_date, end_date, sending_domain_ids, sending_streams, categories, email_service_providers)
  • Test grouped endpoints (by_domains, by_categories, by_email_service_providers, by_date) with filters

Examples

import mailtrap as mt                                                                                                                                                                              
from mailtrap.models.stats import StatsFilterParams                                                                                                                                                
                                                                                                                                                                                                   
client = mt.MailtrapClient(token="api_key")                                                                                                                                                        
stats_api = client.general_api.stats                                                                                                                                                               
                                                                                                                                                                                                   
# Get aggregated stats with optional filters                                                                                                                                                       
params = StatsFilterParams(                                                                                                                                                                        
    start_date="2026-01-01",                                                                                                                                                                       
    end_date="2026-01-31",                                                                                                                                                                         
    categories=["Welcome email"],                                                                                                                                                                  
)                                                                                                                                                                                                  
result = stats_api.get(account_id=account_id, params=params)

Summary by CodeRabbit

  • New Features

    • Introduces stats API endpoints to retrieve sending statistics with filtering and grouping capabilities (by domains, categories, email service providers, or date).
    • Adds automatic list parameter serialization for query handling.
    • Includes example usage documentation.
  • Tests

    • Adds comprehensive unit tests for stats API functionality.

piobeny and others added 3 commits March 4, 2026 12:57
…roviders, by_date endpoints

Also adds api_query_params to RequestParams for automatic [] serialization of list query params.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Mar 9, 2026

📝 Walkthrough

Walkthrough

This PR introduces a new StatsApi feature to the Mailtrap Python SDK for fetching and aggregating sending statistics with filtering capabilities. It includes new API endpoints, data models, automatic list parameter serialization, example usage, and comprehensive tests. Version bumped to 2.5.0.

Changes

Cohort / File(s) Summary
Version and Documentation
pyproject.toml, CHANGELOG.md
Version bumped to 2.5.0 with API docs URL updated. Changelog documents new StatsApi endpoints and list parameter serialization feature.
API Resource Implementation
mailtrap/api/resources/stats.py, mailtrap/api/general.py
Introduces StatsApi class with public methods: get(), by_domains(), by_categories(), by_email_service_providers(), by_date(). General API exposes stats as a new property.
Data Models
mailtrap/models/stats.py, mailtrap/models/common.py
Adds SendingStats and SendingStatGroup dataclasses for stats responses. Introduces StatsFilterParams extending RequestParams with optional filter fields. Adds api_query_params property to RequestParams for automatic list serialization (appending [] to list keys).
Module Exports
mailtrap/__init__.py
Exports StatsFilterParams for public SDK usage.
Examples
examples/general/stats.py
New example script demonstrating all stats API endpoints with filter parameters, including basic retrieval and filtered queries.
Tests
tests/unit/api/general/test_stats.py
Comprehensive unit tests covering successful data retrieval, error handling for various HTTP status codes, parameter serialization, and grouped stats across all endpoints.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client Code
    participant MailtrapClient as MailtrapClient
    participant GeneralApi as GeneralApi
    participant StatsApi as StatsApi
    participant HttpClient as HttpClient
    participant MtApi as Mailtrap API

    Client->>MailtrapClient: initialize with API token
    Client->>MailtrapClient: general_api.stats
    MailtrapClient->>GeneralApi: access stats property
    GeneralApi->>StatsApi: create/return StatsApi instance

    Client->>StatsApi: get(account_id, params)
    activate StatsApi
    StatsApi->>StatsApi: _base_path(account_id)
    StatsApi->>HttpClient: GET /api/accounts/{id}/stats?params
    deactivate StatsApi
    HttpClient->>MtApi: GET request
    MtApi-->>HttpClient: SendingStats JSON response
    HttpClient-->>StatsApi: parsed response
    StatsApi->>StatsApi: construct SendingStats(**data)
    StatsApi-->>Client: SendingStats instance

    Client->>StatsApi: by_domains(account_id, params)
    activate StatsApi
    StatsApi->>StatsApi: _grouped_stats(account_id, "domains", params)
    StatsApi->>HttpClient: GET /api/accounts/{id}/stats/domains?params
    deactivate StatsApi
    HttpClient->>MtApi: GET request
    MtApi-->>HttpClient: grouped stats JSON array
    HttpClient-->>StatsApi: parsed response
    loop For each item in response
        StatsApi->>StatsApi: map to SendingStatGroup(name, value, stats)
    end
    StatsApi-->>Client: list[SendingStatGroup]
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • IgorDobryn
  • i7an
  • VladimirTaytor
  • andrii-porokhnavets

Poem

🐰 Hops of joy for stats divine,
Grouped by domains, dates align,
Filters flourish, brackets bloom,
Stats illuminate the room! 📊

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: releasing version 2.5.0 with the new Stats API feature.
Description check ✅ Passed The description covers motivation, changes, and how to test with a practical code example, though it omits the template's Images/GIFs section and has minor formatting inconsistencies.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch release-2.5.0

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
tests/unit/api/general/test_stats.py (1)

165-169: Assert the serialized list values, not just the key names.

These checks still pass if the request encodes each list under the right key but with the wrong shape. Since this PR adds list-param serialization, it would be better to assert the exact repeated values in the query string.

Proposed test tightening
+from urllib.parse import parse_qs
+from urllib.parse import urlparse
+
 from typing import Any
 
 import pytest
 import responses
@@
-        request_params = responses.calls[0].request.params
-        assert "sending_domain_ids[]" in request_params
-        assert "sending_streams[]" in request_params
-        assert "categories[]" in request_params
-        assert "email_service_providers[]" in request_params
+        request_params = parse_qs(urlparse(responses.calls[0].request.url).query)
+        assert request_params["sending_domain_ids[]"] == ["1", "2"]
+        assert request_params["sending_streams[]"] == ["transactional"]
+        assert request_params["categories[]"] == ["Transactional"]
+        assert request_params["email_service_providers[]"] == ["Gmail"]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/api/general/test_stats.py` around lines 165 - 169, Update the
assertions in the test that inspect responses.calls[0].request.params so they
assert the serialized list values (not just the presence of keys); for example,
replace the four assert "key in request_params" checks with assertions that
request_params["sending_domain_ids[]"], request_params["sending_streams[]"],
request_params["categories[]"], and request_params["email_service_providers[]"]
equal the expected lists (or expected repeated values) used in the test input —
this ensures the serialized shape and values produced by the list-param
serialization logic (as observed via request_params) are exactly what the call
should send.
mailtrap/api/resources/stats.py (3)

1-4: Consider consolidating imports from the same module.

The three imports from mailtrap.models.stats can be combined into a single statement for cleaner code.

♻️ Suggested consolidation
 from mailtrap.http import HttpClient
-from mailtrap.models.stats import SendingStatGroup
-from mailtrap.models.stats import SendingStats
-from mailtrap.models.stats import StatsFilterParams
+from mailtrap.models.stats import SendingStatGroup, SendingStats, StatsFilterParams
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mailtrap/api/resources/stats.py` around lines 1 - 4, Consolidate the three
separate imports from mailtrap.models.stats into a single import statement:
replace the individual imports of SendingStatGroup, SendingStats, and
StatsFilterParams with one grouped import from mailtrap.models.stats so the top
of the file imports HttpClient from mailtrap.http and imports SendingStatGroup,
SendingStats, StatsFilterParams together from mailtrap.models.stats.

6-11: Consider making GROUP_KEYS a private constant.

Since GROUP_KEYS is an internal implementation detail used only by _grouped_stats, prefixing it with an underscore (_GROUP_KEYS) would signal this intent and prevent external reliance on the mapping.

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

In `@mailtrap/api/resources/stats.py` around lines 6 - 11, Rename the public
constant GROUP_KEYS to a private constant named _GROUP_KEYS and update all
references to it (notably inside the _grouped_stats function) so the
implementation detail is not exposed; ensure any import/usage within the same
module uses _GROUP_KEYS and run tests to confirm there are no external
references that need updating.

50-65: Consider stricter typing for the group parameter.

The group parameter only accepts values from GROUP_KEYS. Using a Literal type would provide better type safety and IDE support.

♻️ Suggested type improvement
+from typing import Literal
+
+GroupType = Literal["domains", "categories", "email_service_providers", "date"]
+
     def _grouped_stats(
-        self, account_id: int, group: str, params: StatsFilterParams
+        self, account_id: int, group: GroupType, params: StatsFilterParams
     ) -> list[SendingStatGroup]:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mailtrap/api/resources/stats.py` around lines 50 - 65, The group parameter to
_grouped_stats only accepts keys from GROUP_KEYS, so declare a stricter Literal
alias (e.g. GroupKey = Literal['keyA', 'keyB', ...] where the string literals
enumerate the keys in GROUP_KEYS), replace the method signature to def
_grouped_stats(self, account_id: int, group: GroupKey, params:
StatsFilterParams) -> list[SendingStatGroup], and update any callers/type hints
accordingly; keep the runtime behavior unchanged (still use GROUP_KEYS[group]
and response parsing into SendingStatGroup).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/general/stats.py`:
- Line 7: ACCOUNT_ID is declared as a string but passed to helper functions
typed to accept int; change the declaration of ACCOUNT_ID from a string to an
integer literal (e.g., ACCOUNT_ID = 123456) and update any other sample
declarations/usages (the block around the later occurrences) so they pass an int
to the helper functions referenced in this file; ensure references to ACCOUNT_ID
in functions like the stats helpers now receive an int to keep types consistent.

---

Nitpick comments:
In `@mailtrap/api/resources/stats.py`:
- Around line 1-4: Consolidate the three separate imports from
mailtrap.models.stats into a single import statement: replace the individual
imports of SendingStatGroup, SendingStats, and StatsFilterParams with one
grouped import from mailtrap.models.stats so the top of the file imports
HttpClient from mailtrap.http and imports SendingStatGroup, SendingStats,
StatsFilterParams together from mailtrap.models.stats.
- Around line 6-11: Rename the public constant GROUP_KEYS to a private constant
named _GROUP_KEYS and update all references to it (notably inside the
_grouped_stats function) so the implementation detail is not exposed; ensure any
import/usage within the same module uses _GROUP_KEYS and run tests to confirm
there are no external references that need updating.
- Around line 50-65: The group parameter to _grouped_stats only accepts keys
from GROUP_KEYS, so declare a stricter Literal alias (e.g. GroupKey =
Literal['keyA', 'keyB', ...] where the string literals enumerate the keys in
GROUP_KEYS), replace the method signature to def _grouped_stats(self,
account_id: int, group: GroupKey, params: StatsFilterParams) ->
list[SendingStatGroup], and update any callers/type hints accordingly; keep the
runtime behavior unchanged (still use GROUP_KEYS[group] and response parsing
into SendingStatGroup).

In `@tests/unit/api/general/test_stats.py`:
- Around line 165-169: Update the assertions in the test that inspect
responses.calls[0].request.params so they assert the serialized list values (not
just the presence of keys); for example, replace the four assert "key in
request_params" checks with assertions that
request_params["sending_domain_ids[]"], request_params["sending_streams[]"],
request_params["categories[]"], and request_params["email_service_providers[]"]
equal the expected lists (or expected repeated values) used in the test input —
this ensures the serialized shape and values produced by the list-param
serialization logic (as observed via request_params) are exactly what the call
should send.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ae6b307f-d0d4-4d77-8389-241e8f13796b

📥 Commits

Reviewing files that changed from the base of the PR and between 8821498 and a8a254b.

📒 Files selected for processing (9)
  • CHANGELOG.md
  • examples/general/stats.py
  • mailtrap/__init__.py
  • mailtrap/api/general.py
  • mailtrap/api/resources/stats.py
  • mailtrap/models/common.py
  • mailtrap/models/stats.py
  • pyproject.toml
  • tests/unit/api/general/test_stats.py

from mailtrap.models.stats import StatsFilterParams

API_TOKEN = "YOUR_API_TOKEN"
ACCOUNT_ID = "YOUR_ACCOUNT_ID"
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

Keep ACCOUNT_ID typed consistently with the helper signatures.

The sample declares ACCOUNT_ID as a string, then passes it unchanged into helpers that are typed as int. That makes the example internally inconsistent and noisy for type checkers.

Minimal fix
-ACCOUNT_ID = "YOUR_ACCOUNT_ID"
+ACCOUNT_ID = 123456  # replace with your account ID

Also applies to: 60-67

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

In `@examples/general/stats.py` at line 7, ACCOUNT_ID is declared as a string but
passed to helper functions typed to accept int; change the declaration of
ACCOUNT_ID from a string to an integer literal (e.g., ACCOUNT_ID = 123456) and
update any other sample declarations/usages (the block around the later
occurrences) so they pass an int to the helper functions referenced in this
file; ensure references to ACCOUNT_ID in functions like the stats helpers now
receive an int to keep types consistent.

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"
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants