Skip to content

Add Stats API#115

Merged
piobeny merged 1 commit intomainfrom
stats-api
Mar 12, 2026
Merged

Add Stats API#115
piobeny merged 1 commit intomainfrom
stats-api

Conversation

@piobeny
Copy link
Contributor

@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, byDomain, byCategory, byEmailServiceProvider, 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

How to test

  • statsClient.get() with different parameters (start_date, end_date, sending_domain_ids, sending_streams, categories, email_service_providers)
  • Test grouped endpoints (byDomain, byCategory, byEmailServiceProvider, byDate) with filters

Examples

import { MailtrapClient } from "mailtrap"

const client = new MailtrapClient({ token: "api_key", accountId: accountId })
const statsClient = client.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 category
const byCategories = await statsClient.byCategory({
  start_date: "2026-01-01",
  end_date: "2026-01-02",
})
// [{ name: "category", value: "Welcome email", stats: { ... } }, ...]

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

// Get stats grouped by domain with filters
const byDomains = await statsClient.byDomain({
  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

    • Stats API with endpoints for retrieving sending statistics: get(), byDomain(), byCategory(), byEmailServiceProvider(), and byDate().
    • Support for filtering statistics by date range, sending domains, streams, categories, and email service providers.
  • Documentation

    • Added example demonstrating Stats API usage.
  • Tests

    • Added comprehensive test coverage for Stats API endpoints and filtering capabilities.

@coderabbitai
Copy link

coderabbitai bot commented Mar 6, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This pull request introduces a new StatsApi resource to the Mailtrap client library, providing endpoints to retrieve sending statistics with optional filtering and grouping capabilities across multiple dimensions.

Changes

Cohort / File(s) Summary
New StatsApi Implementation
src/lib/api/resources/Stats.ts, src/types/api/stats.ts
Introduces StatsApi class with get, byDomain, byCategory, byEmailServiceProvider, byDate methods; defines SendingStats, SendingStatGroup, StatsFilterParams types for strongly-typed responses.
General API Integration
src/lib/api/General.ts
Adds lazy-instantiated stats getter to expose StatsApi as singleton in GeneralAPI, following existing pattern for other API resources.
Test Suite
src/__tests__/lib/api/resources/Stats.test.ts
Comprehensive Jest tests covering all StatsApi endpoints, response mapping, error handling, and array parameter serialization in query strings.
Documentation & Examples
examples/general/stats.ts, CHANGELOG.md
Adds example demonstrating StatsApi endpoint usage with error handling and updates changelog with unreleased feature entry.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • PR #103: Adds new account-scoped billing API resource with same integration pattern (lazy-instantiated getter in GeneralAPI) as this StatsApi implementation.

Suggested labels

feature request

Suggested reviewers

  • VladimirTaytor
  • mklocek
  • leonid-shevtsov

Poem

🐰 Stats are here, oh what a sight!
Grouped by domain, category, date so bright,
Filters dance through queries keen,
The finest metrics you have seen!
With tests to guard and examples to show,
This StatsApi steals the show!

🚥 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—introduction of Stats API support to the SDK.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed The pull request description covers all key required sections: motivation, changes, how to test, and includes concrete examples demonstrating API usage.

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

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

🤖 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 3-7: TEST_INBOX_ID is declared as a string but should be a number;
change the TEST_INBOX_ID constant to a numeric placeholder (e.g., 123456) and
ensure the MailtrapClient instantiation still passes testInboxId as that numeric
value so the testInboxId argument type matches what MailtrapClient expects in
its constructor.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9dedc9ac-4117-43ab-95ed-0612effa87a5

📥 Commits

Reviewing files that changed from the base of the PR and between 9b6f082 and 21b247b.

📒 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
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/api/General.ts

Comment on lines +3 to +7
const TOKEN = "<YOUR-TOKEN-HERE>";
const TEST_INBOX_ID = "<YOUR-TEST-INBOX-ID-HERE>"
const ACCOUNT_ID = 123456

const client = new MailtrapClient({ token: TOKEN, testInboxId: TEST_INBOX_ID, accountId: 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

Type mismatch: TEST_INBOX_ID should be a number.

While ACCOUNT_ID was correctly updated to a numeric placeholder, TEST_INBOX_ID remains a string. Although testInboxId is optional and not used by the Stats API, keeping consistency helps users who copy the example.

Suggested fix
 const TOKEN = "<YOUR-TOKEN-HERE>";
-const TEST_INBOX_ID = "<YOUR-TEST-INBOX-ID-HERE>"
+const TEST_INBOX_ID = 12345 // Replace with your test inbox ID
 const ACCOUNT_ID = 123456
📝 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
const TOKEN = "<YOUR-TOKEN-HERE>";
const TEST_INBOX_ID = "<YOUR-TEST-INBOX-ID-HERE>"
const ACCOUNT_ID = 123456
const client = new MailtrapClient({ token: TOKEN, testInboxId: TEST_INBOX_ID, accountId: ACCOUNT_ID });
const TOKEN = "<YOUR-TOKEN-HERE>";
const TEST_INBOX_ID = 12345 // Replace with your test inbox ID
const ACCOUNT_ID = 123456
const client = new MailtrapClient({ token: TOKEN, testInboxId: TEST_INBOX_ID, accountId: ACCOUNT_ID });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/general/stats.ts` around lines 3 - 7, TEST_INBOX_ID is declared as a
string but should be a number; change the TEST_INBOX_ID constant to a numeric
placeholder (e.g., 123456) and ensure the MailtrapClient instantiation still
passes testInboxId as that numeric value so the testInboxId argument type
matches what MailtrapClient expects in its constructor.

@piobeny piobeny force-pushed the stats-api branch 2 times, most recently from ab2c7fe to 6e5276e Compare March 10, 2026 12:32
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 (1)
src/__tests__/lib/api/resources/Stats.test.ts (1)

70-79: Consider cleaning up interceptors in afterAll.

The response interceptors added to the global axios instance in beforeAll are not removed after tests complete. This could affect other test files if they share the same axios instance and don't expect these interceptors.

♻️ Suggested cleanup
 beforeAll(() => {
   /**
    * Init Axios interceptors for handling response.data, errors.
    */
-  axios.interceptors.response.use(
+  const interceptorId = axios.interceptors.response.use(
     (response) => response.data,
     handleSendingError
   );
   mock = new AxiosMockAdapter(axios);
+  return () => {
+    axios.interceptors.response.eject(interceptorId);
+  };
 });
+
+afterAll(() => {
+  mock.restore();
+});
🤖 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 - 79, The
response interceptor added with axios.interceptors.response.use in beforeAll
must be removed in afterAll to avoid leaking into other tests: capture the
interceptor id when calling
axios.interceptors.response.use((response)=>response.data, handleSendingError)
and call axios.interceptors.response.eject(id) in afterAll; also restore the
AxiosMockAdapter mock (mock.restore()) in afterAll to fully clean up the test
fixture. Ensure the identifiers referenced are the interceptor id variable,
axios.interceptors.response.use, axios.interceptors.response.eject,
handleSendingError, and mock (AxiosMockAdapter) so the cleanup is applied to the
same objects created in beforeAll.
🤖 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/__tests__/lib/api/resources/Stats.test.ts`:
- Around line 70-79: The response interceptor added with
axios.interceptors.response.use in beforeAll must be removed in afterAll to
avoid leaking into other tests: capture the interceptor id when calling
axios.interceptors.response.use((response)=>response.data, handleSendingError)
and call axios.interceptors.response.eject(id) in afterAll; also restore the
AxiosMockAdapter mock (mock.restore()) in afterAll to fully clean up the test
fixture. Ensure the identifiers referenced are the interceptor id variable,
axios.interceptors.response.use, axios.interceptors.response.eject,
handleSendingError, and mock (AxiosMockAdapter) so the cleanup is applied to the
same objects created in beforeAll.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 446f146d-5eb8-4126-9357-874e19a6d778

📥 Commits

Reviewing files that changed from the base of the PR and between 21b247b and 495d27a.

📒 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
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/index.ts
  • src/lib/api/General.ts
  • src/types/api/stats.ts

src/index.ts Outdated
import MailtrapTransport from "./lib/transport";

export * from "./types/mailtrap";
export * from "./types/api/stats";
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, we didn't export other api/ types like this, so this looks a bit off

Copy link
Contributor Author

Choose a reason for hiding this comment

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

you are right, it was redundant - removed

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lib/api/resources/Stats.ts`:
- Around line 100-122: The Mailtrap API expects array params serialized in
bracket notation, but Axios v0.27+ serializes arrays differently; update the
HTTP client configuration to use qs to serialize arrays as brackets and add qs
to package.json dependencies. Install and import qs, then configure the axios
instance's paramsSerializer to use qs.stringify(params, { arrayFormat:
'brackets' }) (ensure this is applied where the client/axios instance is created
that uses Stats.buildQueryParams), and keep buildQueryParams returning arrays
as-is so the serializer can handle them. Ensure package.json lists "qs" and run
install.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 53a989db-6a4b-413b-a76d-58f879fee700

📥 Commits

Reviewing files that changed from the base of the PR and between 495d27a and 0a6cda9.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • examples/general/stats.ts
  • src/__tests__/lib/api/resources/Stats.test.ts
  • src/lib/api/General.ts
  • src/lib/api/resources/Stats.ts
  • src/types/api/stats.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • examples/general/stats.ts
  • src/tests/lib/api/resources/Stats.test.ts

Comment on lines +100 to +122
private static buildQueryParams(
params: StatsFilterParams
): Record<string, unknown> {
const query: Record<string, unknown> = {
start_date: params.start_date,
end_date: params.end_date,
};

if (params.sending_domain_ids) {
query.sending_domain_ids = params.sending_domain_ids;
}
if (params.sending_streams) {
query.sending_streams = params.sending_streams;
}
if (params.categories) {
query.categories = params.categories;
}
if (params.email_service_providers) {
query.email_service_providers = params.email_service_providers;
}

return query;
}
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 if there's a custom paramsSerializer configured in the MailtrapClient or axios instance setup
rg -n "paramsSerializer" --type ts

# Check how other APIs in this codebase handle array parameters
rg -n -A5 "params:" src/lib/api/resources/*.ts | head -80

# Check if qs is already a dependency
cat package.json | jq '.dependencies, .devDependencies' | grep -i "qs"

Repository: mailtrap/mailtrap-nodejs

Length of output: 4681


🏁 Script executed:

# Check axios version and if qs is a dependency
cat package.json | grep -A20 '"dependencies"' | head -30
cat package.json | grep -A20 '"devDependencies"' | head -30

Repository: mailtrap/mailtrap-nodejs

Length of output: 1513


🏁 Script executed:

# Check the actual test file to see if it's passing or what it expects
fd -n "Stats.test.ts" -x cat -n {}

Repository: mailtrap/mailtrap-nodejs

Length of output: 298


🏁 Script executed:

# Check if there's any axios configuration in the client setup
fd -n "MailtrapClient\|AxiosInstance\|createClient" -x rg -l {} \; 2>/dev/null | head -5

Repository: mailtrap/mailtrap-nodejs

Length of output: 50


🏁 Script executed:

cat -n src/lib/api/resources/Stats.test.ts

Repository: mailtrap/mailtrap-nodejs

Length of output: 135


🏁 Script executed:

# Also check the MailtrapClient setup to see how axios is configured
fd "MailtrapClient" -type f -x cat -n {}

Repository: mailtrap/mailtrap-nodejs

Length of output: 238


🏁 Script executed:

# Find Stats test file
find . -name "*Stats*test*" -o -name "*test*Stats*" 2>/dev/null | head -10

Repository: mailtrap/mailtrap-nodejs

Length of output: 115


🏁 Script executed:

# Find MailtrapClient file
find . -name "*MailtrapClient*" -o -name "*client*" -type f | grep -E "\.(ts|js)$" | head -10

Repository: mailtrap/mailtrap-nodejs

Length of output: 426


🏁 Script executed:

# List test directory structure
find . -path "*/test*" -name "*.ts" | head -20

Repository: mailtrap/mailtrap-nodejs

Length of output: 1131


🏁 Script executed:

cat -n ./src/__tests__/lib/api/resources/Stats.test.ts

Repository: mailtrap/mailtrap-nodejs

Length of output: 10925


🏁 Script executed:

cat -n ./src/lib/MailtrapClient.ts

Repository: mailtrap/mailtrap-nodejs

Length of output: 8738


🏁 Script executed:

# Check for any base API class that might configure axios
cat -n ./src/lib/api/*.ts | grep -A20 "paramsSerializer\|constructor"

Repository: mailtrap/mailtrap-nodejs

Length of output: 8207


🏁 Script executed:

# Search for any global axios configuration or setup
grep -r "paramsSerializer" ./src --include="*.ts"

Repository: mailtrap/mailtrap-nodejs

Length of output: 50


🏁 Script executed:

# Check if axios's getUri method has any special behavior documented
# Let's also look at how the Stats class receives axios instance
cat -n ./src/lib/api/resources/Stats.ts | head -40

Repository: mailtrap/mailtrap-nodejs

Length of output: 1254


Configure custom array parameter serialization for Mailtrap API compatibility.

The test expects bracket notation (sending_domain_ids[]=1) for array parameters, but Axios's default serializer (v0.27+) produces indexed notation (sending_domain_ids[0]=1) or comma-separated values. Without explicit configuration, API requests with array filters will be malformed.

Add qs as a dependency and configure a custom paramsSerializer in the axios instance:

Required implementation
+import qs from "qs";
+
 // In MailtrapClient.ts, line 69:
 this.axios = axios.create({
   httpAgent: new http.Agent({ keepAlive: true }),
   httpsAgent: new https.Agent({ keepAlive: true }),
   headers: {
     Authorization: `Bearer ${token}`,
     Connection: "keep-alive",
     "User-Agent": userAgent || USER_AGENT,
   },
   maxRedirects: MAX_REDIRECTS,
   timeout: TIMEOUT,
+  paramsSerializer: (params) =>
+    qs.stringify(params, { arrayFormat: "brackets" }),
 });

Also add qs to package.json dependencies.

🤖 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 100 - 122, The Mailtrap API
expects array params serialized in bracket notation, but Axios v0.27+ serializes
arrays differently; update the HTTP client configuration to use qs to serialize
arrays as brackets and add qs to package.json dependencies. Install and import
qs, then configure the axios instance's paramsSerializer to use
qs.stringify(params, { arrayFormat: 'brackets' }) (ensure this is applied where
the client/axios instance is created that uses Stats.buildQueryParams), and keep
buildQueryParams returning arrays as-is so the serializer can handle them.
Ensure package.json lists "qs" and run install.

@piobeny piobeny merged commit ef3cf27 into main Mar 12, 2026
4 checks passed
@piobeny piobeny deleted the stats-api branch March 12, 2026 11:16
@coderabbitai coderabbitai bot mentioned this pull request Mar 12, 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.

3 participants