Skip to content

Add Stats API#96

Open
piobeny wants to merge 3 commits intomainfrom
MT-20967-ruby-sdk-add-stats-functionality
Open

Add Stats API#96
piobeny wants to merge 3 commits intomainfrom
MT-20967-ruby-sdk-add-stats-functionality

Conversation

@piobeny
Copy link

@piobeny piobeny commented Mar 4, 2026

Motivation

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

Changes

  • Add SendingStats DTO struct with delivery, bounce, open, click, and spam counts/rates
  • Add StatsAPI class with 5 methods: get, by_domains, by_categories, by_email_service_providers, by_date
  • Add query param handling for array filters (sending_domain_ids[], sending_streams[], categories[], email_service_providers[])
  • Add usage example in examples/stats_api.rb
  • Update README with Stats API reference
  • Update CHANGELOG with unreleased entry

How to test

  • Mailtrap::StatsAPI.new(account_id, client).get method 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

require 'mailtrap'

account_id = id
client = Mailtrap::Client.new(api_key: 'api_key')
stats = Mailtrap::StatsAPI.new(account_id, client)

# With optional filters
stats.get(
  start_date: '2026-01-01',
  end_date: '2026-01-31',
  categories: ['Welcome email']
)

#<struct Mailtrap::SendingStats
  delivery_count=11349,
  delivery_rate=0.974665063552044,
  bounce_count=295,
  bounce_rate=0.02533493644795603,
  open_count=5531,
  open_rate=0.4873557141598379,
  click_count=2052,
  click_rate=0.1808088818398097,
  spam_count=12,
  spam_rate=0.00105736188210415
>

# Get stats grouped by date 
stats.by_date(start_date: '2026-01-01', end_date: '2026-01-02')
[
  #<struct Mailtrap::SendingStatGroup
    name=:date,
    value="2026-01-01",
    stats=
    #<struct Mailtrap::SendingStats
      delivery_count=2220,
      delivery_rate=0.9749670619235836,
      bounce_count=57,
      bounce_rate=0.02503293807641634,
      open_count=1066,
      open_rate=0.4801801801801802,
      click_count=416,
      click_rate=0.1873873873873874,
      spam_count=0,
      spam_rate=0.0>
      >,
  #<struct Mailtrap::SendingStatGroup
    name=:date,
    value="2026-01-02",
    stats=
    #<struct Mailtrap::SendingStats
      delivery_count=1152,
      delivery_rate=0.9770992366412213,
      bounce_count=27,
      bounce_rate=0.02290076335877863,
      open_count=524,
      open_rate=0.4548611111111111,
      click_count=162,
      click_rate=0.140625,
      spam_count=0,
      spam_rate=0.0>
    >
]

# Get stats grouped by categories
stats.by_categories(start_date: '2026-01-01', end_date: '2026-01-02')
[
  #<struct Mailtrap::SendingStatGroup
    name=:category,
    value="Welcome email",
    stats=
    #<struct Mailtrap::SendingStats
      delivery_count=580,
      delivery_rate=0.9682804674457429,
      bounce_count=19,
      bounce_rate=0.03171953255425709,
      open_count=278,
      open_rate=0.4793103448275862,
      click_count=105,
      click_rate=0.1810344827586207,
      spam_count=0,
      spam_rate=0.0
      >
    >,
  #<struct Mailtrap::SendingStatGroup
    name=:category,
    value="Invoice email",
    stats=
    #<struct Mailtrap::SendingStats
      delivery_count=571,
      delivery_rate=0.9710884353741497,
      bounce_count=17,
      bounce_rate=0.02891156462585034,
      open_count=270,
      open_rate=0.4728546409807355,
      click_count=99,
      click_rate=0.1733800350262697,
      spam_count=0,
      spam_rate=0.0>
    >,
  ...
]

# Get stats grouped by email service providers
stats.by_email_service_providers(start_date: '2026-01-01', end_date: '2026-01-02', categories: ['Welcome email'])
[
  #<struct Mailtrap::SendingStatGroup
    name=:email_service_provider,
    value="Google",
    stats=
    #<struct Mailtrap::SendingStats
      delivery_count=197,
      delivery_rate=0.9752475247524752,
      bounce_count=5,
      bounce_rate=0.02475247524752475,
      open_count=96,
      open_rate=0.4873096446700508,
      click_count=41,
      click_rate=0.2081218274111675,
      spam_count=0,
      spam_rate=0.0
      >
    >,
  #<struct Mailtrap::SendingStatGroup
    name=:email_service_provider,
    value="Google Workspace",
    stats=
    #<struct Mailtrap::SendingStats
      delivery_count=146,
      delivery_rate=0.9864864864864865,
      bounce_count=2,
      bounce_rate=0.01351351351351351,
      open_count=74,
      open_rate=0.5068493150684932,
      click_count=18,
      click_rate=0.1232876712328767,
      spam_count=0,
      spam_rate=0.0
      >
    >,
    ...
]

# Get stats grouped by domains
stats.by_domains(start_date: '2026-01-01', end_date: '2026-01-02', categories: ['Welcome email'], email_service_providers: ['Google'])
[
  #<struct Mailtrap::SendingStatGroup
    name=:sending_domain_id,
    value=75581,
    stats=
    #<struct Mailtrap::SendingStats
      delivery_count=197,
      delivery_rate=0.9752475247524752,
      bounce_count=5,
      bounce_rate=0.02475247524752475,
      open_count=96,
      open_rate=0.4873096446700508,
      click_count=41,
      click_rate=0.2081218274111675,
      spam_count=0,
      spam_rate=0.0
    >
  >
]

Summary by CodeRabbit

  • New Features

    • Added Sending Stats API to fetch aggregated and grouped email sending statistics with flexible filters.
  • Documentation

    • Updated changelog and README to document the Sending Stats API and reference an example.
  • Examples

    • Added a new example demonstrating usage of the Sending Stats API.
  • Tests

    • Added test suite and recorded fixtures covering aggregated, grouped, filtered, and error scenarios.
  • Chores

    • Bumped library version to 2.9.0.

@coderabbitai
Copy link

coderabbitai bot commented Mar 4, 2026

📝 Walkthrough

Walkthrough

Introduces a new Sending Stats API: adds Mailtrap::StatsAPI, DTOs for SendingStats and SendingStatGroup, example usage, VCR fixtures, and RSpec coverage; loads new modules and bumps gem version to 2.9.0.

Changes

Cohort / File(s) Summary
Core API & DTOs
lib/mailtrap/stats_api.rb, lib/mailtrap/sending_stats.rb, lib/mailtrap/sending_stat_group.rb
Adds Mailtrap::StatsAPI with methods get, by_domains, by_categories, by_email_service_providers, by_date; introduces Mailtrap::SendingStats and Mailtrap::SendingStatGroup structs.
Entrypoint Registration
lib/mailtrap.rb
Requires new stats files so the API and DTOs are loaded by the gem.
Examples & Docs
examples/stats_api.rb, README.md, CHANGELOG.md
Adds a Ruby example for the Stats API, documents it in README, and inserts an "Unreleased" CHANGELOG entry "Add Sending Stats API" (plus minor reflow formatting).
Tests & Fixtures
spec/mailtrap/stats_api_spec.rb, spec/spec_helper.rb, spec/fixtures/vcr_cassettes/Mailtrap_StatsAPI/*
Adds comprehensive RSpec tests for StatsAPI (including auth error handling) and multiple VCR cassettes for aggregated and grouped endpoints; enhances match_struct matcher to accept objects responding to matches?.
Version Bump
lib/mailtrap/version.rb
Increments Mailtrap::VERSION from 2.8.0 to 2.9.0.

Sequence Diagram

sequenceDiagram
    participant Client as Client
    participant StatsAPI as StatsAPI
    participant Query as QueryBuilder
    participant HTTP as HTTPClient
    participant Remote as Remote API
    participant Parser as ResponseParser

    Client->>StatsAPI: get(start_date, end_date, filters)
    StatsAPI->>Query: build_query_params(dates, filters)
    Query-->>StatsAPI: query_params
    StatsAPI->>HTTP: GET /api/accounts/{id}/stats?... 
    HTTP->>Remote: HTTP GET request
    Remote-->>HTTP: 200 JSON response
    HTTP-->>StatsAPI: response body
    StatsAPI->>Parser: parse to SendingStats
    Parser-->>StatsAPI: SendingStats
    StatsAPI-->>Client: SendingStats

    Client->>StatsAPI: by_domains(start_date, end_date, filters)
    StatsAPI->>Query: build_query_params(dates, filters)
    Query-->>StatsAPI: query_params
    StatsAPI->>HTTP: GET /api/accounts/{id}/stats/domains?...
    HTTP->>Remote: HTTP GET request
    Remote-->>HTTP: 200 JSON array
    HTTP-->>StatsAPI: response body
    StatsAPI->>Parser: map to SendingStatGroup[]
    Parser-->>StatsAPI: SendingStatGroup array
    StatsAPI-->>Client: grouped stats array
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Suggested Reviewers

  • i7an
  • IgorDobryn

Poem

🐰 In a burrow bright I tap the keys,
Counting sends and bounces with ease,
Groups and totals hop in line,
Stats served fresh, concise, and fine,
Hooray — a carrot for analytics please! 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.50% 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 pull request title 'Add Stats API' is concise, clear, and directly describes the main change: introducing a new Stats API feature to the SDK.
Description check ✅ Passed The pull request description includes all key sections: Motivation, Changes, How to test, and comprehensive Examples demonstrating usage of the new API with various filtering options.

✏️ 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 MT-20967-ruby-sdk-add-stats-functionality

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.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@piobeny piobeny force-pushed the MT-20967-ruby-sdk-add-stats-functionality branch from c5a2318 to bd40917 Compare March 4, 2026 10:10
@piobeny piobeny marked this pull request as ready for review March 4, 2026 10:14
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 (1)
examples/stats_api.rb (1)

3-5: Use ENV-based placeholders for runnable credentials.

For executable examples, prefer ENV.fetch for account_id and api_key to reduce accidental hardcoded-secret edits in local copies.

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

In `@examples/stats_api.rb` around lines 3 - 5, Replace hardcoded credentials with
ENV-based placeholders: use ENV.fetch to obtain the API key and account id and
convert the account id to an integer before constructing Mailtrap::StatsAPI.
Update the account_id variable (currently "account_id = 3229"), the
Mailtrap::Client.new call that passes api_key: 'your-api-key', and the
Mailtrap::StatsAPI.new(account_id, client) usage to read
ENV.fetch('MAILTRAP_ACCOUNT_ID') (to_i) and ENV.fetch('MAILTRAP_API_KEY') so the
example is runnable without hardcoded secrets.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@spec/mailtrap/stats_api_spec.rb`:
- Around line 12-159: Add a cassette-backed spec that exercises the optional
array filters and [] serialization by calling the relevant API method (e.g.,
stats_api.get or a grouping method like stats_api.by_categories) with filter
params sending_domain_ids, sending_streams, categories, and
email_service_providers set to arrays; assert the request was recorded/played
(VCR cassette) and that the response mapping still succeeds, and additionally
verify the request query string uses the [] form (e.g.,
"sending_domain_ids[]=1") or that the client sent multiple param[] entries—use
the existing examples (subject(:stats) { ... }) and test helpers (match_struct)
to validate the returned stats while exercising these filters so query param
construction/regression is covered.

---

Nitpick comments:
In `@examples/stats_api.rb`:
- Around line 3-5: Replace hardcoded credentials with ENV-based placeholders:
use ENV.fetch to obtain the API key and account id and convert the account id to
an integer before constructing Mailtrap::StatsAPI. Update the account_id
variable (currently "account_id = 3229"), the Mailtrap::Client.new call that
passes api_key: 'your-api-key', and the Mailtrap::StatsAPI.new(account_id,
client) usage to read ENV.fetch('MAILTRAP_ACCOUNT_ID') (to_i) and
ENV.fetch('MAILTRAP_API_KEY') so the example is runnable without hardcoded
secrets.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 123b0e56-2fa0-4011-b3ed-cec1ba30ca8d

📥 Commits

Reviewing files that changed from the base of the PR and between af396c8 and bd40917.

📒 Files selected for processing (15)
  • CHANGELOG.md
  • README.md
  • examples/stats_api.rb
  • lib/mailtrap.rb
  • lib/mailtrap/sending_stat_group.rb
  • lib/mailtrap/sending_stats.rb
  • lib/mailtrap/stats_api.rb
  • spec/fixtures/vcr_cassettes/Mailtrap_StatsAPI/_by_categories/returns_stats_grouped_by_categories.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_StatsAPI/_by_date/returns_stats_grouped_by_date.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_StatsAPI/_by_domains/returns_stats_grouped_by_domains.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_StatsAPI/_by_email_service_providers/returns_stats_grouped_by_email_service_providers.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_StatsAPI/_get/returns_aggregated_sending_stats.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_StatsAPI/_get/when_api_key_is_incorrect/raises_authorization_error.yml
  • spec/mailtrap/stats_api_spec.rb
  • spec/spec_helper.rb

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

♻️ Duplicate comments (1)
spec/mailtrap/stats_api_spec.rb (1)

27-49: ⚠️ Potential issue | 🟡 Minor

Still missing an assertion for [] query serialization.

This example exercises the filters, but it only checks response mapping. The PR’s main risk is the outgoing query shape (sending_domain_ids[], sending_streams[], etc.), and that still is not asserted here.

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

In `@spec/mailtrap/stats_api_spec.rb` around lines 27 - 49, The test exercises
stats_api.get with filter arrays but doesn't assert that array query params are
serialized with trailing brackets (e.g. sending_domain_ids[], sending_streams[],
categories[], email_service_providers[]); update the spec for stats_api.get to
also assert the outgoing request/query string includes those bracketed keys —
either by inspecting the stubbed HTTP request made by the client or by asserting
the full generated query params on the request double used in the test so the
spec ensures correct "[]"-style serialization for sending_domain_ids,
sending_streams, categories and email_service_providers.
🧹 Nitpick comments (1)
spec/mailtrap/stats_api_spec.rb (1)

68-92: Avoid coupling grouped-spec assertions to response order.

These examples all pin first/last. If the API returns the same groups in a different order, the SDK is still correct but the suite fails. Prefer sorting by value first or using order-insensitive expectations.

Also applies to: 98-123, 129-153, 159-183

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

In `@spec/mailtrap/stats_api_spec.rb` around lines 68 - 92, The test assumes a
fixed response order by using stats.first/stats.last which makes it flaky;
update the assertions to be order-insensitive by either sorting the stats array
by the grouping key (e.g., sort by element[:value]) before asserting, or locate
each group by its value and assert its contents (use stats.find { |s| s[:value]
== 1 } and stats.find { |s| s[:value] == 2 }), and keep the existing
match_struct checks (name: :sending_domain_id, value: ..., stats: ...) unchanged
so the test validates group contents regardless of order.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@spec/mailtrap/stats_api_spec.rb`:
- Around line 27-49: The test exercises stats_api.get with filter arrays but
doesn't assert that array query params are serialized with trailing brackets
(e.g. sending_domain_ids[], sending_streams[], categories[],
email_service_providers[]); update the spec for stats_api.get to also assert the
outgoing request/query string includes those bracketed keys — either by
inspecting the stubbed HTTP request made by the client or by asserting the full
generated query params on the request double used in the test so the spec
ensures correct "[]"-style serialization for sending_domain_ids,
sending_streams, categories and email_service_providers.

---

Nitpick comments:
In `@spec/mailtrap/stats_api_spec.rb`:
- Around line 68-92: The test assumes a fixed response order by using
stats.first/stats.last which makes it flaky; update the assertions to be
order-insensitive by either sorting the stats array by the grouping key (e.g.,
sort by element[:value]) before asserting, or locate each group by its value and
assert its contents (use stats.find { |s| s[:value] == 1 } and stats.find { |s|
s[:value] == 2 }), and keep the existing match_struct checks (name:
:sending_domain_id, value: ..., stats: ...) unchanged so the test validates
group contents regardless of order.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2ffca3dc-28fe-4244-b8d2-5be950b6e72f

📥 Commits

Reviewing files that changed from the base of the PR and between bd40917 and 6f96204.

⛔ Files ignored due to path filters (1)
  • Gemfile.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • CHANGELOG.md
  • lib/mailtrap/version.rb
  • spec/fixtures/vcr_cassettes/Mailtrap_StatsAPI/_get/with_optional_filters/returns_filtered_sending_stats.yml
  • spec/mailtrap/stats_api_spec.rb
✅ Files skipped from review due to trivial changes (1)
  • spec/fixtures/vcr_cassettes/Mailtrap_StatsAPI/_get/with_optional_filters/returns_filtered_sending_stats.yml
🚧 Files skipped from review as they are similar to previous changes (1)
  • CHANGELOG.md

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