Skip to content

Add Stats API#224

Open
piobeny wants to merge 1 commit intomainfrom
stats-api
Open

Add Stats API#224
piobeny wants to merge 1 commit intomainfrom
stats-api

Conversation

@piobeny
Copy link

@piobeny piobeny commented Mar 6, 2026

Motivation

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

Changes

  • Add SendingStats record with delivery, bounce, open, click, and spam counts/rates (with JsonPropertyName attributes)
  • Add SendingStatGroup record with Name, Value, and Stats properties
  • Add StatsFilter record with StartDate, EndDate, and optional list filters (SendingDomainIds, SendingStreams, Categories, EmailServiceProviders)
  • Add IStatsResource interface with 5 methods: GetStats, ByDomains, ByCategories, ByEmailServiceProviders, ByDate
  • Add StatsResource implementation with query param serialization for array filters ([] suffix)
  • Add usage example in examples/Mailtrap.Example.Stats/Stats.cs
  • Add unit and integration tests
  • Update README with Stats API reference

How to test

  • statsResource.GetStats(filter) with different filter parameters (StartDate, EndDate, SendingDomainIds, SendingStreams, Categories, EmailServiceProviders)
  • Test grouped endpoints (ByDomains, ByCategories, ByEmailServiceProviders, ByDate) with filters

Examples

using Mailtrap.Stats;
using Mailtrap.Stats.Models;

IStatsResource statsResource = mailtrapClient
    .Account(accountId)
    .Stats();

// Get aggregated stats
var filter = new StatsFilter
{
    StartDate = "2026-01-01",
    EndDate = "2026-01-31"
};
SendingStats stats = await statsResource.GetStats(filter);

// Get stats grouped by date
IList<SendingStatGroup> dateStats = await statsResource.ByDate(filter);

// Get stats grouped by categories
IList<SendingStatGroup> categoryStats = await statsResource.ByCategories(filter);

// Get stats grouped by email service providers
IList<SendingStatGroup> espStats = await statsResource.ByEmailServiceProviders(filter);

// Get stats grouped by domains
IList<SendingStatGroup> domainStats = await statsResource.ByDomains(filter);

// Get stats with filters
var filteredFilter = new StatsFilter
{
    StartDate = "2026-01-01",
    EndDate = "2026-01-31"
};
filteredFilter.SendingDomainIds.Add(1);
filteredFilter.SendingStreams.Add("transactional");
filteredFilter.Categories.Add("Welcome email");
filteredFilter.EmailServiceProviders.Add("Gmail");

SendingStats filteredStats = await statsResource.GetStats(filteredFilter);

Summary by CodeRabbit

  • New Features

    • Stats API to retrieve aggregated email sending metrics (delivery, bounce, open, click, spam counts and rates)
    • Retrieve statistics grouped by domains, categories, email service providers, and date
    • Included example demonstrating Stats API usage
  • Documentation

    • README and CHANGELOG updated with a Stats API entry and usage link

@piobeny piobeny requested a review from mklocek as a code owner March 6, 2026 13:56
@coderabbitai
Copy link

coderabbitai bot commented Mar 6, 2026

Warning

Rate limit exceeded

@piobeny has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 12 minutes and 30 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: feab04b4-4ce8-47ee-9fd1-8378e26e9470

📥 Commits

Reviewing files that changed from the base of the PR and between 87f2268 and 224bb41.

📒 Files selected for processing (29)
  • CHANGELOG.md
  • Mailtrap.sln
  • README.md
  • examples/Mailtrap.Example.Stats/Mailtrap.Example.Stats.csproj
  • examples/Mailtrap.Example.Stats/Properties/launchSettings.json
  • examples/Mailtrap.Example.Stats/Stats.cs
  • examples/Mailtrap.Example.Stats/appsettings.json
  • src/Mailtrap.Abstractions/Accounts/IAccountResource.cs
  • src/Mailtrap.Abstractions/GlobalUsings.cs
  • src/Mailtrap.Abstractions/Stats/IStatsResource.cs
  • src/Mailtrap.Abstractions/Stats/Models/SendingStatGroup.cs
  • src/Mailtrap.Abstractions/Stats/Models/SendingStats.cs
  • src/Mailtrap.Abstractions/Stats/Models/StatsFilter.cs
  • src/Mailtrap/Accounts/AccountResource.cs
  • src/Mailtrap/Core/Constants/UrlSegments.cs
  • src/Mailtrap/GlobalUsings.cs
  • src/Mailtrap/Stats/StatsResource.cs
  • tests/Mailtrap.IntegrationTests/GlobalUsings.cs
  • tests/Mailtrap.IntegrationTests/Stats/ByCategory_Success.json
  • tests/Mailtrap.IntegrationTests/Stats/ByDate_Success.json
  • tests/Mailtrap.IntegrationTests/Stats/ByDomain_Success.json
  • tests/Mailtrap.IntegrationTests/Stats/ByEmailServiceProvider_Success.json
  • tests/Mailtrap.IntegrationTests/Stats/Get_Forbidden.json
  • tests/Mailtrap.IntegrationTests/Stats/Get_Success.json
  • tests/Mailtrap.IntegrationTests/Stats/StatsIntegrationTests.cs
  • tests/Mailtrap.IntegrationTests/TestConstants/UrlSegmentsTestConstants.cs
  • tests/Mailtrap.UnitTests/GlobalUsings.cs
  • tests/Mailtrap.UnitTests/Stats/StatsResourceTests.cs
  • tests/Mailtrap.UnitTests/TestConstants/UrlSegmentsTestConstants.cs
📝 Walkthrough

Walkthrough

Adds a Stats API: new resource, interfaces, models, implementation, tests, example project, fixtures, and README/CHANGELOG updates to support fetching and grouping email sending statistics.

Changes

Cohort / File(s) Summary
Changelog & Readme
CHANGELOG.md, README.md
Added an Unreleased changelog entry and a "Stats API" README subsection linking the example.
Solution & Example Project
Mailtrap.sln, examples/Mailtrap.Example.Stats/...
Added new example project Mailtrap.Example.Stats with csproj and launch settings; included example appsettings and Stats example code.
Public Abstractions
src/Mailtrap.Abstractions/Accounts/IAccountResource.cs, src/Mailtrap.Abstractions/Stats/IStatsResource.cs, src/Mailtrap.Abstractions/GlobalUsings.cs
Added IStatsResource interface and Stats() accessor on IAccountResource; added global usings for Mailtrap.Stats and Mailtrap.Stats.Models.
Models
src/Mailtrap.Abstractions/Stats/Models/SendingStats.cs, .../SendingStatGroup.cs, .../StatsFilter.cs
Introduced SendingStats, SendingStatGroup, and StatsFilter record types for stats payloads and filters.
Implementation
src/Mailtrap/Stats/StatsResource.cs, src/Mailtrap/Accounts/AccountResource.cs, src/Mailtrap/Core/Constants/UrlSegments.cs, src/Mailtrap/GlobalUsings.cs
Implemented internal StatsResource (URI/query construction, GET calls, JSON → domain mapping), added AccountResource.Stats() and UrlSegments.StatsSegment; added global usings.
Tests — Integration
tests/Mailtrap.IntegrationTests/Stats/StatsIntegrationTests.cs, tests/Mailtrap.IntegrationTests/Stats/*.json, tests/.../GlobalUsings.cs, tests/.../TestConstants/UrlSegmentsTestConstants.cs
Added integration tests covering Get, grouped endpoints and a forbidden response; added JSON fixtures and test constants/global using.
Tests — Unit
tests/Mailtrap.UnitTests/Stats/StatsResourceTests.cs, tests/Mailtrap.UnitTests/GlobalUsings.cs, tests/.../TestConstants/UrlSegmentsTestConstants.cs
Added unit tests validating StatsResource constructor and ResourceUri; added global using and UrlSegments test constant.
Test Fixtures & Helpers
tests/.../Stats/*.json
Added fixtures for ByDomains, ByCategories, ByEmailServiceProviders, ByDate, Get_Success, Get_Forbidden used by integration tests.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Account as AccountResource
    participant Stats as StatsResource
    participant HTTP as HttpClient
    participant JSON as JsonParser

    Client->>Account: Stats()
    Account-->>Client: IStatsResource

    Client->>Stats: GetStats(filter)
    Stats->>Stats: BuildFilterUri(baseUri, filter)
    Stats->>HTTP: GET /accounts/{id}/stats?...
    HTTP-->>Stats: 200 OK + JSON
    Stats->>JSON: Deserialize to SendingStats
    JSON-->>Stats: SendingStats
    Stats-->>Client: SendingStats

    Client->>Stats: ByDomains(filter)
    Stats->>HTTP: GET /accounts/{id}/stats/domains?...
    HTTP-->>Stats: 200 OK + JSON array
    Stats->>JSON: Map items -> SendingStatGroup[]
    JSON-->>Stats: IList<SendingStatGroup>
    Stats-->>Client: IList<SendingStatGroup>
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • zhaparoff
  • mklocek

Poem

🐰
Hops I take through stats and streams,
Counting opens, clicks, and dreams.
Domains and dates in tidy rows,
Delivery counts where the carrot grows,
A tiny hop — the metrics glow.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% 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 'Add Stats API' clearly and concisely summarizes the main change—introducing Stats API support to the .NET SDK.
Description check ✅ Passed The PR description is comprehensive, covering motivation, detailed changes, testing approach, and practical code examples. It aligns well with the template structure despite minor formatting differences.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch stats-api

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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
README.md (1)

267-310: ⚠️ Potential issue | 🟡 Minor

Stats API missing from "Supported functionality & Examples" section.

The PR adds Stats API functionality (per PR objectives and AI summary), but the README's "Supported functionality & Examples" section doesn't include a Stats API entry or link to the Stats example (examples/Mailtrap.Example.Stats). This creates an inconsistency with the AI summary which states "README updated with Stats API reference."

Consider adding an entry similar to:

### Stats API

- Email sending statistics – [`examples/Mailtrap.Example.Stats`](examples/Mailtrap.Example.Stats/)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 267 - 310, Add a "Stats API" entry to the "Supported
functionality & Examples" section of README.md to reflect the new functionality;
specifically, insert a header "### Stats API" and an item like "Email sending
statistics –
[`examples/Mailtrap.Example.Stats`](examples/Mailtrap.Example.Stats/)" alongside
the other example links so the README lists examples/Mailtrap.Example.Stats and
matches the PR summary. Ensure the new entry follows the same formatting as
adjacent sections (e.g., other "###" headers and bullet link style).
🧹 Nitpick comments (2)
src/Mailtrap/Stats/StatsResource.cs (1)

67-74: Duplicate property access and missing error handling.

item.GetProperty(groupKey) is called twice per iteration, and GetProperty("stats") will throw KeyNotFoundException if the JSON is malformed.

Consider caching the property value and adding defensive handling:

♻️ Proposed refactor
         return response.Select(item =>
         {
+            var groupValue = item.GetProperty(groupKey);
             return new SendingStatGroup
             {
                 Name = groupKey,
-                Value = item.GetProperty(groupKey).ValueKind == JsonValueKind.Number
-                    ? item.GetProperty(groupKey).GetInt64()
-                    : item.GetProperty(groupKey).GetString() ?? string.Empty,
-                Stats = item.GetProperty("stats").Deserialize<SendingStats>() ?? new SendingStats()
+                Value = groupValue.ValueKind == JsonValueKind.Number
+                    ? groupValue.GetInt64()
+                    : groupValue.GetString() ?? string.Empty,
+                Stats = item.TryGetProperty("stats", out var statsElement)
+                    ? statsElement.Deserialize<SendingStats>() ?? new SendingStats()
+                    : new SendingStats()
             };
         }).ToList<SendingStatGroup>();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Mailtrap/Stats/StatsResource.cs` around lines 67 - 74, Cache the result
of item.GetProperty(groupKey) into a local variable to avoid duplicate lookups
when building each SendingStatGroup and use item.TryGetProperty("stats", out var
statsProp) to defensively handle missing or malformed "stats" (fall back to new
SendingStats()). Also ensure the value extraction checks prop.ValueKind once and
reads either GetInt64() or GetString() with a string.Empty fallback; update the
LINQ projection that constructs SendingStatGroup to use these cached locals.
tests/Mailtrap.IntegrationTests/Stats/StatsIntegrationTests.cs (1)

29-68: Consider testing full query string or additional filter parameters.

The test sets both StartDate and EndDate but the mock only verifies the start_date parameter via partial matching. If BuildFilterUri produced an incorrect key for end_date (or other parameters), this test would still pass.

Consider either:

  1. Using exact query string matching to validate all parameters
  2. Adding test cases that exercise the array filter parameters (SendingDomainIds, SendingStreams, Categories, EmailServiceProviders)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/Mailtrap.IntegrationTests/Stats/StatsIntegrationTests.cs` around lines
29 - 68, The test Get_Success only asserts the presence of the start_date query
parameter via ConfigureWithQueryAndCreateClient, leaving EndDate and other
filter fields unverified; update the test to either (a) use exact query-string
matching for the request URI returned by BuildFilterUri so both start_date and
end_date (and any other keys) are validated, or (b) add additional test cases in
StatsIntegrationTests (e.g., new tests calling Account(...).Stats().GetStats
with StatsFilter values for SendingDomainIds, SendingStreams, Categories,
EmailServiceProviders) that assert the full expected query string or parameters
are present, using MockHttpMessageHandler/ConfigureWithQueryAndCreateClient to
verify the complete set of query parameters rather than just the single
start_date key.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@README.md`:
- Line 41: The README command referencing "dotnet add package Mailtrap -v 3.1.0
-s github-mailtrap" will fail unless a git tag matching the MinVerTagPrefix (v)
exists; create and push the git tag v3.1.0 before or at merge time, or update
the README to use an existing published version (or remove the explicit -v flag)
so the "dotnet add package" command resolves; look for the README line
containing the dotnet command and the project MinVer configuration
(MinVerTagPrefix = v) to make the change.

In `@src/Mailtrap.Abstractions/Stats/Models/StatsFilter.cs`:
- Around line 12-17: StatsFilter is constructible with empty StartDate/EndDate
which later breaks BuildFilterUri when it calls AppendQueryParameter; make the
date range required or validate it before building the URI. Update the
StatsFilter class so StartDate and EndDate cannot be empty (e.g., remove the
string.Empty defaults and force values via constructor or mark them required),
or add explicit validation in BuildFilterUri to throw a clear exception if
StartDate/EndDate are null/empty, and reference StartDate, EndDate, StatsFilter
and BuildFilterUri/AppendQueryParameter to locate where to enforce the check.
- Around line 27-37: The string filter collections (SendingStreams, Categories,
EmailServiceProviders) can contain characters that must be percent-encoded
before being appended to a query; update StatsResource.BuildFilterUri to
percent-encode each value passed to UriExtensions.AppendQueryParameter (e.g.,
via Uri.EscapeDataString or by calling an encoding helper) so spaces, +, &, etc.
are encoded, or alternatively update UriExtensions.AppendQueryParameter to
URL-encode the value it receives; locate usages in StatsResource.BuildFilterUri
and ensure every value from SendingStreams, Categories, and
EmailServiceProviders is encoded before appending.
- Around line 22-23: The SendingDomainIds property in StatsFilter is typed as
IList<int> which can't represent IDs above Int32.MaxValue; change the property
signature to use IList<long> and initialize it with a long-capable collection
(e.g., new List<long>()) so it matches the rest of the SDK (see
AccountResource/SendingDomain(long)) and preserves compatibility with existing
long domain IDs.

---

Outside diff comments:
In `@README.md`:
- Around line 267-310: Add a "Stats API" entry to the "Supported functionality &
Examples" section of README.md to reflect the new functionality; specifically,
insert a header "### Stats API" and an item like "Email sending statistics –
[`examples/Mailtrap.Example.Stats`](examples/Mailtrap.Example.Stats/)" alongside
the other example links so the README lists examples/Mailtrap.Example.Stats and
matches the PR summary. Ensure the new entry follows the same formatting as
adjacent sections (e.g., other "###" headers and bullet link style).

---

Nitpick comments:
In `@src/Mailtrap/Stats/StatsResource.cs`:
- Around line 67-74: Cache the result of item.GetProperty(groupKey) into a local
variable to avoid duplicate lookups when building each SendingStatGroup and use
item.TryGetProperty("stats", out var statsProp) to defensively handle missing or
malformed "stats" (fall back to new SendingStats()). Also ensure the value
extraction checks prop.ValueKind once and reads either GetInt64() or GetString()
with a string.Empty fallback; update the LINQ projection that constructs
SendingStatGroup to use these cached locals.

In `@tests/Mailtrap.IntegrationTests/Stats/StatsIntegrationTests.cs`:
- Around line 29-68: The test Get_Success only asserts the presence of the
start_date query parameter via ConfigureWithQueryAndCreateClient, leaving
EndDate and other filter fields unverified; update the test to either (a) use
exact query-string matching for the request URI returned by BuildFilterUri so
both start_date and end_date (and any other keys) are validated, or (b) add
additional test cases in StatsIntegrationTests (e.g., new tests calling
Account(...).Stats().GetStats with StatsFilter values for SendingDomainIds,
SendingStreams, Categories, EmailServiceProviders) that assert the full expected
query string or parameters are present, using
MockHttpMessageHandler/ConfigureWithQueryAndCreateClient to verify the complete
set of query parameters rather than just the single start_date key.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: be8d7ef4-308f-4141-bb71-4bffecc0f6f3

📥 Commits

Reviewing files that changed from the base of the PR and between e42cc69 and ca14ac6.

📒 Files selected for processing (29)
  • CHANGELOG.md
  • Mailtrap.sln
  • README.md
  • examples/Mailtrap.Example.Stats/Mailtrap.Example.Stats.csproj
  • examples/Mailtrap.Example.Stats/Properties/launchSettings.json
  • examples/Mailtrap.Example.Stats/Stats.cs
  • examples/Mailtrap.Example.Stats/appsettings.json
  • src/Mailtrap.Abstractions/Accounts/IAccountResource.cs
  • src/Mailtrap.Abstractions/GlobalUsings.cs
  • src/Mailtrap.Abstractions/Stats/IStatsResource.cs
  • src/Mailtrap.Abstractions/Stats/Models/SendingStatGroup.cs
  • src/Mailtrap.Abstractions/Stats/Models/SendingStats.cs
  • src/Mailtrap.Abstractions/Stats/Models/StatsFilter.cs
  • src/Mailtrap/Accounts/AccountResource.cs
  • src/Mailtrap/Core/Constants/UrlSegments.cs
  • src/Mailtrap/GlobalUsings.cs
  • src/Mailtrap/Stats/StatsResource.cs
  • tests/Mailtrap.IntegrationTests/GlobalUsings.cs
  • tests/Mailtrap.IntegrationTests/Stats/ByCategories_Success.json
  • tests/Mailtrap.IntegrationTests/Stats/ByDate_Success.json
  • tests/Mailtrap.IntegrationTests/Stats/ByDomains_Success.json
  • tests/Mailtrap.IntegrationTests/Stats/ByEmailServiceProviders_Success.json
  • tests/Mailtrap.IntegrationTests/Stats/Get_Forbidden.json
  • tests/Mailtrap.IntegrationTests/Stats/Get_Success.json
  • tests/Mailtrap.IntegrationTests/Stats/StatsIntegrationTests.cs
  • tests/Mailtrap.IntegrationTests/TestConstants/UrlSegmentsTestConstants.cs
  • tests/Mailtrap.UnitTests/GlobalUsings.cs
  • tests/Mailtrap.UnitTests/Stats/StatsResourceTests.cs
  • tests/Mailtrap.UnitTests/TestConstants/UrlSegmentsTestConstants.cs

README.md Outdated

```bash
dotnet add package Mailtrap -v 3.0.0 -s github-mailtrap
dotnet add package Mailtrap -v 3.1.0 -s github-mailtrap
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for version 3.1.0 tag
echo "=== Checking for version tags ==="
git tag -l | grep -E '^v?3\.1\.0$' && echo "Tag found" || echo "Tag not found"

echo -e "\n=== Recent tags (last 10) ==="
git tag -l --sort=-v:refname | head -10

echo -e "\n=== Checking for project files with version/MinVer config ==="
fd -e "csproj" -e "props" -e "Directory.Build.props" | head -20

Repository: mailtrap/mailtrap-dotnet

Length of output: 1166


🏁 Script executed:

#!/bin/bash
# Find and examine project files for MinVer configuration
echo "=== Searching for MinVer configuration ==="
rg -i "minver" --type xml --type csproj -l

Repository: mailtrap/mailtrap-dotnet

Length of output: 158


🏁 Script executed:

#!/bin/bash
# Look at the README to see what's being documented
echo "=== README.md content around lines 41-47 ==="
sed -n '35,50p' README.md

Repository: mailtrap/mailtrap-dotnet

Length of output: 461


🏁 Script executed:

#!/bin/bash
# Check for release/CI workflow files
echo "=== Looking for release/CI processes ==="
fd -e "yml" -e "yaml" | xargs grep -l "3.1.0\|release\|tag" 2>/dev/null | head -10

Repository: mailtrap/mailtrap-dotnet

Length of output: 108


🏁 Script executed:

#!/bin/bash
# Check Directory.Build.props for MinVer or versioning configuration
echo "=== Directory.Build.props ==="
cat Directory.Build.props | head -50

Repository: mailtrap/mailtrap-dotnet

Length of output: 544


🏁 Script executed:

#!/bin/bash
# Check the NuGet-related build props
echo "=== build/mailtrap-nuget.props ==="
cat build/mailtrap-nuget.props

Repository: mailtrap/mailtrap-dotnet

Length of output: 2066


🏁 Script executed:

#!/bin/bash
# Search for any version-related configuration
echo "=== Searching for Version property ==="
rg -i "version|minver" --type xml | head -30

Repository: mailtrap/mailtrap-dotnet

Length of output: 272


Ensure git tag v3.1.0 is created before or simultaneously with merging this README.

The project uses MinVer with MinVerTagPrefix set to v, which derives package versions from git tags. Since no tags currently exist in the repository, the version 3.1.0 will not be available on NuGet when users attempt to install via the commands documented in lines 41 and 47. The installation will fail with a "package not found" error unless the v3.1.0 tag exists at or before the time this README is published.

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

In `@README.md` at line 41, The README command referencing "dotnet add package
Mailtrap -v 3.1.0 -s github-mailtrap" will fail unless a git tag matching the
MinVerTagPrefix (v) exists; create and push the git tag v3.1.0 before or at
merge time, or update the README to use an existing published version (or remove
the explicit -v flag) so the "dotnet add package" command resolves; look for the
README line containing the dotnet command and the project MinVer configuration
(MinVerTagPrefix = v) to make the change.

Comment on lines +12 to +17
public string StartDate { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the end date for the stats period.
/// </summary>
public string EndDate { get; set; } = string.Empty;
Copy link

@coderabbitai coderabbitai bot Mar 6, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

StatsFilter is constructible in an invalid state.

new StatsFilter() succeeds, but every stats call will fail later because BuildFilterUri() forwards StartDate and EndDate into AppendQueryParameter(), which rejects empty strings. Please make the date range required or validate it explicitly before URI construction.

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

In `@src/Mailtrap.Abstractions/Stats/Models/StatsFilter.cs` around lines 12 - 17,
StatsFilter is constructible with empty StartDate/EndDate which later breaks
BuildFilterUri when it calls AppendQueryParameter; make the date range required
or validate it before building the URI. Update the StatsFilter class so
StartDate and EndDate cannot be empty (e.g., remove the string.Empty defaults
and force values via constructor or mark them required), or add explicit
validation in BuildFilterUri to throw a clear exception if StartDate/EndDate are
null/empty, and reference StartDate, EndDate, StatsFilter and
BuildFilterUri/AppendQueryParameter to locate where to enforce the check.

Copy link
Contributor

Choose a reason for hiding this comment

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

We could do as 🐰 suggests

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd -e cs StatsFilter.cs --exec cat {}
echo "---"
fd -e cs StatsResource.cs --exec cat {}

Length of output: 8543


@mklocek, BuildFilterUri already has Ensure.NotNullOrEmpty guards for both fields, so at least a clear runtime exception is thrown. The remaining gap is that new StatsFilter() compiles without supplying either date. The cleanest fix is to mark them as required (C# 11+, already used elsewhere in the project via collection expressions):

src/Mailtrap.Abstractions/Stats/Models/StatsFilter.cs

public sealed record StatsFilter
{
    /// <summary>
    /// Gets or sets the start date for the stats period.
    /// </summary>
    public required string StartDate { get; set; }

    /// <summary>
    /// Gets or sets the end date for the stats period.
    /// </summary>
    public required string EndDate { get; set; }

    // ... rest unchanged
}

With required, any callsite that omits StartDate or EndDate in the object initializer gets a compile-time error instead of a runtime exception. The Ensure.NotNullOrEmpty checks in BuildFilterUri can stay as a defence-in-depth safety net.

The Stats.cs example already uses an object initialiser that provides both dates, so no breaking change there.

Copy link

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Author

Choose a reason for hiding this comment

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

@mklocek tried to do it but:

The required keyword can't be used here because the library targets netstandard2.0, which doesn't support it (missing SetsRequiredMembersAttribute). The C# 11 required modifier needs at least .NET 7+ runtime types.

we are targeting v2
<TargetFrameworks>netstandard2.0</TargetFrameworks>

which seems old, or am I missing something?

Comment on lines +22 to +23
public IList<int> SendingDomainIds { get; } = [];

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use long for SendingDomainIds.

The rest of the SDK already models sending-domain identifiers as long (src/Mailtrap/Accounts/AccountResource.cs, SendingDomain(long domainId)), so this new filter narrows the contract and cannot represent valid IDs above Int32.MaxValue.

Suggested change
-    public IList<int> SendingDomainIds { get; } = [];
+    public IList<long> SendingDomainIds { get; } = [];
📝 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
public IList<int> SendingDomainIds { get; } = [];
public IList<long> SendingDomainIds { get; } = [];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Mailtrap.Abstractions/Stats/Models/StatsFilter.cs` around lines 22 - 23,
The SendingDomainIds property in StatsFilter is typed as IList<int> which can't
represent IDs above Int32.MaxValue; change the property signature to use
IList<long> and initialize it with a long-capable collection (e.g., new
List<long>()) so it matches the rest of the SDK (see
AccountResource/SendingDomain(long)) and preserves compatibility with existing
long domain IDs.

Comment on lines +27 to +37
public IList<string> SendingStreams { get; } = [];

/// <summary>
/// Gets categories to filter by.
/// </summary>
public IList<string> Categories { get; } = [];

/// <summary>
/// Gets email service providers to filter by.
/// </summary>
public IList<string> EmailServiceProviders { get; } = [];
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

String filters need query-value escaping.

These collections accept arbitrary user input, but StatsResource.BuildFilterUri() currently passes each entry to UriExtensions.AppendQueryParameter(), which concatenates raw key=value pairs. Values containing spaces, +, &, or similar characters will alter or break the generated query string.

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

In `@src/Mailtrap.Abstractions/Stats/Models/StatsFilter.cs` around lines 27 - 37,
The string filter collections (SendingStreams, Categories,
EmailServiceProviders) can contain characters that must be percent-encoded
before being appended to a query; update StatsResource.BuildFilterUri to
percent-encode each value passed to UriExtensions.AppendQueryParameter (e.g.,
via Uri.EscapeDataString or by calling an encoding helper) so spaces, +, &, etc.
are encoded, or alternatively update UriExtensions.AppendQueryParameter to
URL-encode the value it receives; locate usages in StatsResource.BuildFilterUri
and ensure every value from SendingStreams, Categories, and
EmailServiceProviders is encoded before appending.

@piobeny piobeny changed the title Stats api Add Stats API Mar 9, 2026
@piobeny piobeny requested a review from IgorDobryn March 9, 2026 10:01
Comment on lines +12 to +17
public string StartDate { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the end date for the stats period.
/// </summary>
public string EndDate { get; set; } = string.Empty;
Copy link
Contributor

Choose a reason for hiding this comment

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

We could do as 🐰 suggests

@@ -0,0 +1,17 @@
[
{
"category": "Transactional",
Copy link
Contributor

Choose a reason for hiding this comment

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

That's an unfortunate category name. Might be confused with sending stream

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.

🧹 Nitpick comments (2)
src/Mailtrap/Stats/StatsResource.cs (2)

88-90: Consider escaping StartDate and EndDate for consistency.

The string filter values are properly escaped with Uri.EscapeDataString, but StartDate and EndDate are appended raw. While date strings typically don't contain special characters, escaping them would provide consistency and guard against unexpected formats.

💡 Suggested change
         var uri = baseUri
-            .AppendQueryParameter(StartDateParam, filter.StartDate)
-            .AppendQueryParameter(EndDateParam, filter.EndDate);
+            .AppendQueryParameter(StartDateParam, Uri.EscapeDataString(filter.StartDate))
+            .AppendQueryParameter(EndDateParam, Uri.EscapeDataString(filter.EndDate));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Mailtrap/Stats/StatsResource.cs` around lines 88 - 90, The StartDate and
EndDate values are appended unescaped; update the call that builds the query
(the var uri = baseUri... block using AppendQueryParameter with StartDateParam
and EndDateParam) to pass escaped values by wrapping filter.StartDate and
filter.EndDate with Uri.EscapeDataString (i.e., use
Uri.EscapeDataString(filter.StartDate) and Uri.EscapeDataString(filter.EndDate))
so the query parameters are consistently URL-encoded; keep AppendQueryParameter,
StartDateParam, EndDateParam and baseUri unchanged otherwise.

67-80: GetProperty throws if the grouping key is missing from the response.

If the API response lacks the expected grouping key (e.g., "sending_domain_id"), item.GetProperty(groupKey) will throw KeyNotFoundException. Consider using TryGetProperty for defensive handling:

💡 Suggested defensive handling
 return response.Select(item =>
 {
-    var groupProp = item.GetProperty(groupKey);
+    if (!item.TryGetProperty(groupKey, out var groupProp))
+    {
+        throw new InvalidOperationException($"Expected property '{groupKey}' not found in API response.");
+    }
     return new SendingStatGroup
     {

This provides a clearer error message if the API contract changes or returns unexpected data.

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

In `@src/Mailtrap/Stats/StatsResource.cs` around lines 67 - 80, The code calls
item.GetProperty(groupKey) which throws when the response lacks that key; update
the projection in StatsResource.cs to use item.TryGetProperty(groupKey, out var
groupProp) instead of GetProperty, handle the missing-case by setting
SendingStatGroup.Name/Value to an explicit fallback (e.g., empty string or a
sentinel) or throw a clearer exception with context, and keep the existing logic
for numeric/string conversion and the existing stats deserialization into
SendingStats; reference the SendingStatGroup type, the groupKey variable, and
the stats property handling so the change is localized to the Func used in the
response.Select(...) projection.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/Mailtrap/Stats/StatsResource.cs`:
- Around line 88-90: The StartDate and EndDate values are appended unescaped;
update the call that builds the query (the var uri = baseUri... block using
AppendQueryParameter with StartDateParam and EndDateParam) to pass escaped
values by wrapping filter.StartDate and filter.EndDate with Uri.EscapeDataString
(i.e., use Uri.EscapeDataString(filter.StartDate) and
Uri.EscapeDataString(filter.EndDate)) so the query parameters are consistently
URL-encoded; keep AppendQueryParameter, StartDateParam, EndDateParam and baseUri
unchanged otherwise.
- Around line 67-80: The code calls item.GetProperty(groupKey) which throws when
the response lacks that key; update the projection in StatsResource.cs to use
item.TryGetProperty(groupKey, out var groupProp) instead of GetProperty, handle
the missing-case by setting SendingStatGroup.Name/Value to an explicit fallback
(e.g., empty string or a sentinel) or throw a clearer exception with context,
and keep the existing logic for numeric/string conversion and the existing stats
deserialization into SendingStats; reference the SendingStatGroup type, the
groupKey variable, and the stats property handling so the change is localized to
the Func used in the response.Select(...) projection.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 646cf366-7785-4277-9d90-921e38b62c95

📥 Commits

Reviewing files that changed from the base of the PR and between ca14ac6 and 87f2268.

📒 Files selected for processing (29)
  • CHANGELOG.md
  • Mailtrap.sln
  • README.md
  • examples/Mailtrap.Example.Stats/Mailtrap.Example.Stats.csproj
  • examples/Mailtrap.Example.Stats/Properties/launchSettings.json
  • examples/Mailtrap.Example.Stats/Stats.cs
  • examples/Mailtrap.Example.Stats/appsettings.json
  • src/Mailtrap.Abstractions/Accounts/IAccountResource.cs
  • src/Mailtrap.Abstractions/GlobalUsings.cs
  • src/Mailtrap.Abstractions/Stats/IStatsResource.cs
  • src/Mailtrap.Abstractions/Stats/Models/SendingStatGroup.cs
  • src/Mailtrap.Abstractions/Stats/Models/SendingStats.cs
  • src/Mailtrap.Abstractions/Stats/Models/StatsFilter.cs
  • src/Mailtrap/Accounts/AccountResource.cs
  • src/Mailtrap/Core/Constants/UrlSegments.cs
  • src/Mailtrap/GlobalUsings.cs
  • src/Mailtrap/Stats/StatsResource.cs
  • tests/Mailtrap.IntegrationTests/GlobalUsings.cs
  • tests/Mailtrap.IntegrationTests/Stats/ByCategories_Success.json
  • tests/Mailtrap.IntegrationTests/Stats/ByDate_Success.json
  • tests/Mailtrap.IntegrationTests/Stats/ByDomains_Success.json
  • tests/Mailtrap.IntegrationTests/Stats/ByEmailServiceProviders_Success.json
  • tests/Mailtrap.IntegrationTests/Stats/Get_Forbidden.json
  • tests/Mailtrap.IntegrationTests/Stats/Get_Success.json
  • tests/Mailtrap.IntegrationTests/Stats/StatsIntegrationTests.cs
  • tests/Mailtrap.IntegrationTests/TestConstants/UrlSegmentsTestConstants.cs
  • tests/Mailtrap.UnitTests/GlobalUsings.cs
  • tests/Mailtrap.UnitTests/Stats/StatsResourceTests.cs
  • tests/Mailtrap.UnitTests/TestConstants/UrlSegmentsTestConstants.cs
🚧 Files skipped from review as they are similar to previous changes (20)
  • tests/Mailtrap.UnitTests/TestConstants/UrlSegmentsTestConstants.cs
  • tests/Mailtrap.IntegrationTests/TestConstants/UrlSegmentsTestConstants.cs
  • tests/Mailtrap.IntegrationTests/Stats/StatsIntegrationTests.cs
  • src/Mailtrap/Core/Constants/UrlSegments.cs
  • examples/Mailtrap.Example.Stats/Mailtrap.Example.Stats.csproj
  • examples/Mailtrap.Example.Stats/appsettings.json
  • tests/Mailtrap.IntegrationTests/GlobalUsings.cs
  • tests/Mailtrap.UnitTests/GlobalUsings.cs
  • README.md
  • Mailtrap.sln
  • src/Mailtrap.Abstractions/GlobalUsings.cs
  • examples/Mailtrap.Example.Stats/Stats.cs
  • src/Mailtrap.Abstractions/Stats/Models/SendingStats.cs
  • tests/Mailtrap.IntegrationTests/Stats/ByDate_Success.json
  • src/Mailtrap.Abstractions/Accounts/IAccountResource.cs
  • tests/Mailtrap.IntegrationTests/Stats/ByDomains_Success.json
  • tests/Mailtrap.IntegrationTests/Stats/Get_Success.json
  • tests/Mailtrap.IntegrationTests/Stats/ByCategories_Success.json
  • src/Mailtrap.Abstractions/Stats/Models/SendingStatGroup.cs
  • src/Mailtrap/GlobalUsings.cs

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.

3 participants