Skip to content

Add Stats API#115

Closed
piobeny wants to merge 2 commits intomainfrom
stats-api
Closed

Add Stats API#115
piobeny wants to merge 2 commits 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 Node.js SDK, enabling users to retrieve aggregated email sending statistics.

Changes

  • Add SendingStats, SendingStatGroup, and StatsFilterParams types in src/types/api/stats
  • Add StatsApi class with 5 methods: get, byDomains, byCategories, byEmailServiceProviders, byDate
  • Add query param handling for array filters (sending_domain_ids[], sending_streams[], categories[], email_service_providers[])
  • Add usage example in examples/general/stats.ts
  • Update README with Stats API reference
  • Update CHANGELOG with v4.5.0 entry

How to test

  • statsClient.get() with different parameters (start_date, end_date, sending_domain_ids, sending_streams, categories, email_service_providers)
  • Test grouped endpoints (byDomains, byCategories, byEmailServiceProviders, byDate) with filters

Examples

import { MailtrapClient } from "mailtrap"

const client = new MailtrapClient({ token: "api_key", accountId: accountId })
const statsClient = client.general.stats

// Get aggregated stats
const result = await statsClient.get({
  start_date: "2026-01-01",
  end_date: "2026-01-31",
})
// { delivery_count: 11349, delivery_rate: 0.974, bounce_count: 295, ... }

// Get stats with optional filters
const filtered = await statsClient.get({
  start_date: "2026-01-01",
  end_date: "2026-01-31",
  categories: ["Welcome email"],
})

// Get stats grouped by date
const byDate = await statsClient.byDate({
  start_date: "2026-01-01",
  end_date: "2026-01-02",
})
// [{ name: "date", value: "2026-01-01", stats: { delivery_count: 2220, ... } }, ...]

// Get stats grouped by categories
const byCategories = await statsClient.byCategories({
  start_date: "2026-01-01",
  end_date: "2026-01-02",
})
// [{ name: "category", value: "Welcome email", stats: { ... } }, ...]

// Get stats grouped by email service providers with filters
const byEsp = await statsClient.byEmailServiceProviders({
  start_date: "2026-01-01",
  end_date: "2026-01-02",
  categories: ["Welcome email"],
})
// [{ name: "email_service_provider", value: "Google", stats: { ... } }, ...]

// Get stats grouped by domains with filters
const byDomains = await statsClient.byDomains({
  start_date: "2026-01-01",
  end_date: "2026-01-02",
  categories: ["Welcome email"],
  email_service_providers: ["Google"],
})
// [{ name: "sending_domain_id", value: 75581, stats: { ... } }]

Summary by CodeRabbit

  • New Features

    • Added a Stats API to retrieve overall and grouped sending statistics (by domain, category, ESP, and date) with metrics for delivery, bounce, open, click, and spam; supports date-range and optional filters.
  • Documentation

    • Added examples and CHANGELOG entry for the new Stats API.
  • Tests

    • Added comprehensive tests covering Stats endpoints, responses, serialization, and error handling.
  • Chores

    • Bumped package version to 4.5.0.

…ers, byDate endpoints

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

coderabbitai bot commented Mar 6, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 86cb58e2-336d-4147-a37a-b507e19ee92d

📥 Commits

Reviewing files that changed from the base of the PR and between 8497f1e and 9b6f082.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • examples/general/stats.ts
  • package.json
  • src/__tests__/lib/api/resources/Stats.test.ts
  • src/lib/api/resources/Stats.ts
✅ Files skipped from review due to trivial changes (1)
  • package.json
🚧 Files skipped from review as they are similar to previous changes (4)
  • CHANGELOG.md
  • src/lib/api/resources/Stats.ts
  • examples/general/stats.ts
  • src/tests/lib/api/resources/Stats.test.ts

📝 Walkthrough

Walkthrough

Adds a new Stats API to the client library: types, implementation (aggregated and grouped endpoints), integration into GeneralAPI and exports, tests, an example, and changelog and package version updates.

Changes

Cohort / File(s) Summary
Types
src/types/api/stats.ts
Adds SendingStats, SendingStatGroup, and StatsFilterParams type definitions.
API Implementation
src/lib/api/resources/Stats.ts
Introduces StatsApi class with methods get, byDomains, byCategories, byEmailServiceProviders, byDate, plus helpers groupedStats and buildQueryParams.
API Integration
src/lib/api/General.ts, src/index.ts
Adds a stats singleton getter to GeneralAPI and re-exports stats types from the package entry.
Tests
src/__tests__/lib/api/resources/Stats.test.ts
Adds comprehensive tests covering methods, param serialization (array[] suffix), grouped mappings, baseURL construction, and error handling (401/404).
Examples & Changelog
examples/general/stats.ts, CHANGELOG.md, package.json
Adds an example demonstrating usage of all stats endpoints, updates CHANGELOG with 4.5.0 entry, and bumps package version to 4.5.0.

Sequence Diagram(s)

sequenceDiagram
    participant App as Application
    participant Client as MailtrapClient
    participant GeneralAPI as GeneralAPI
    participant StatsApi as StatsApi
    participant Axios as Axios Instance
    participant Server as Stats Endpoint

    App->>Client: access .general.stats
    Client->>GeneralAPI: get stats property
    GeneralAPI->>StatsApi: instantiate (lazy init)
    GeneralAPI-->>Client: return StatsApi instance

    App->>StatsApi: .get(filterParams)
    StatsApi->>StatsApi: buildQueryParams()
    StatsApi->>Axios: GET /stats?start_date=...&end_date=...
    Axios->>Server: HTTP request
    Server-->>Axios: SendingStats response
    Axios-->>StatsApi: parsed response
    StatsApi-->>App: SendingStats

    App->>StatsApi: .byDomains(filterParams)
    StatsApi->>StatsApi: groupedStats('domains', params)
    StatsApi->>Axios: GET /stats/domains?start_date=...
    Axios->>Server: HTTP request
    Server-->>Axios: grouped stats response
    Axios-->>StatsApi: parsed response
    StatsApi->>StatsApi: map to SendingStatGroup[]
    StatsApi-->>App: SendingStatGroup[] results
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

feature request

Suggested reviewers

  • narekhovhannisyan
  • VladimirTaytor
  • mklocek

Poem

🐰 A stats API hops into view,
Domains, dates, providers — all in queue,
Queries built neatly, grouped outputs align,
Tests and examples make the flow fine,
Hop! — the new version rings version 4.5.0 🎉

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add Stats API' directly and clearly summarizes the main change—introducing the Stats API to the SDK with all related endpoints and types.
Description check ✅ Passed The description includes all required sections: Motivation, Changes, and How to test. It provides practical code examples and covers the new API methods comprehensively.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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 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: 1

🧹 Nitpick comments (3)
examples/general/stats.ts (1)

87-92: Consider awaiting function calls or using an async IIFE.

The example functions are called without await, meaning errors won't be handled at the top level and execution order isn't guaranteed. For a cleaner example:

Suggested fix
-testGetStats()
-testGetStatsWithFilters()
-testGetStatsByDomains()
-testGetStatsByCategories()
-testGetStatsByEmailServiceProviders()
-testGetStatsByDate()
+(async () => {
+  await testGetStats()
+  await testGetStatsWithFilters()
+  await testGetStatsByDomains()
+  await testGetStatsByCategories()
+  await testGetStatsByEmailServiceProviders()
+  await testGetStatsByDate()
+})()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/general/stats.ts` around lines 87 - 92, The example calls to
testGetStats, testGetStatsWithFilters, testGetStatsByDomains,
testGetStatsByCategories, testGetStatsByEmailServiceProviders, and
testGetStatsByDate are invoked without awaiting, so wrap these calls in an async
IIFE or await them from an async function and handle errors; update the example
to either (1) make the top-level module run an immediately-invoked async
function that sequentially awaits each of the listed functions (testGetStats,
testGetStatsWithFilters, testGetStatsByDomains, testGetStatsByCategories,
testGetStatsByEmailServiceProviders, testGetStatsByDate) with try/catch around
the sequence to surface errors, or (2) export/use an async main() that awaits
each function and catches/logs errors before exiting.
src/__tests__/lib/api/resources/Stats.test.ts (1)

70-91: Consider moving initialization tests after beforeAll.

The describe("class Stats(): ") block (lines 70-80) appears before beforeAll (lines 82-91). While this works because Jest hoists beforeAll, it's unconventional and could confuse readers. Consider moving beforeAll to the top of the outer describe block for clarity.

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

In `@src/__tests__/lib/api/resources/Stats.test.ts` around lines 70 - 91, Move the
test setup so initialization runs before the spec declarations: relocate the
beforeAll block (which initializes axios.interceptors.response.use,
handleSendingError, and creates the AxiosMockAdapter mock) to the top of the
outer describe that contains the "class Stats(): " tests so that beforeAll
executes visually before the describe("class Stats(): ") block and the statsAPI
initialization tests (expect(...).toHaveProperty(...)) appear after the test
setup; keep references to axios.interceptors.response.use, handleSendingError,
AxiosMockAdapter, mock, and statsAPI intact.
src/lib/api/resources/Stats.ts (1)

75-94: Consider adding a guard for unknown group keys.

While groupedStats is private and only called with valid keys, adding a guard would make the code more defensive against future changes:

Optional defensive check
 private async groupedStats(
   group: string,
   params: StatsFilterParams
 ): Promise<SendingStatGroup[]> {
   const url = `${this.statsURL}/${group}`;
   const groupKey = GROUP_KEYS[group];
+
+  if (!groupKey) {
+    throw new Error(`Unknown stats group: ${group}`);
+  }

   const response = await this.client.get<
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/api/resources/Stats.ts` around lines 75 - 94, Add a defensive guard
in groupedStats: after computing const groupKey = GROUP_KEYS[group], verify
groupKey is defined and handle the unknown case (e.g., throw a clear Error
mentioning the invalid group or return an empty array) before using it to build
the response; update groupedStats (and any callers if needed) so the function
fails fast with a descriptive message referencing groupedStats and GROUP_KEYS
when an invalid group is supplied.
🤖 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.ts`:
- Around line 4-5: The example defines ACCOUNT_ID as a string placeholder but
MailtrapClient expects a numeric accountId; change the placeholder to a numeric
literal (e.g., ACCOUNT_ID = 123456) or update usage to coerce/parse it to a
number before passing to MailtrapClient (reference ACCOUNT_ID and the
MailtrapClient constructor/accountId parameter) so the example shows a number
type rather than a string.

---

Nitpick comments:
In `@examples/general/stats.ts`:
- Around line 87-92: The example calls to testGetStats, testGetStatsWithFilters,
testGetStatsByDomains, testGetStatsByCategories,
testGetStatsByEmailServiceProviders, and testGetStatsByDate are invoked without
awaiting, so wrap these calls in an async IIFE or await them from an async
function and handle errors; update the example to either (1) make the top-level
module run an immediately-invoked async function that sequentially awaits each
of the listed functions (testGetStats, testGetStatsWithFilters,
testGetStatsByDomains, testGetStatsByCategories,
testGetStatsByEmailServiceProviders, testGetStatsByDate) with try/catch around
the sequence to surface errors, or (2) export/use an async main() that awaits
each function and catches/logs errors before exiting.

In `@src/__tests__/lib/api/resources/Stats.test.ts`:
- Around line 70-91: Move the test setup so initialization runs before the spec
declarations: relocate the beforeAll block (which initializes
axios.interceptors.response.use, handleSendingError, and creates the
AxiosMockAdapter mock) to the top of the outer describe that contains the "class
Stats(): " tests so that beforeAll executes visually before the describe("class
Stats(): ") block and the statsAPI initialization tests
(expect(...).toHaveProperty(...)) appear after the test setup; keep references
to axios.interceptors.response.use, handleSendingError, AxiosMockAdapter, mock,
and statsAPI intact.

In `@src/lib/api/resources/Stats.ts`:
- Around line 75-94: Add a defensive guard in groupedStats: after computing
const groupKey = GROUP_KEYS[group], verify groupKey is defined and handle the
unknown case (e.g., throw a clear Error mentioning the invalid group or return
an empty array) before using it to build the response; update groupedStats (and
any callers if needed) so the function fails fast with a descriptive message
referencing groupedStats and GROUP_KEYS when an invalid group is supplied.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b8d29381-21db-4838-bd09-f8a2d0532f47

📥 Commits

Reviewing files that changed from the base of the PR and between 484d833 and 8497f1e.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • examples/general/stats.ts
  • src/__tests__/lib/api/resources/Stats.test.ts
  • src/index.ts
  • src/lib/api/General.ts
  • src/lib/api/resources/Stats.ts
  • src/types/api/stats.ts

@piobeny piobeny closed this Mar 9, 2026
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.

1 participant