Skip to content

Add support for Email Logs API#62

Open
mklocek wants to merge 2 commits intomainfrom
email-logs-api
Open

Add support for Email Logs API#62
mklocek wants to merge 2 commits intomainfrom
email-logs-api

Conversation

@mklocek
Copy link
Contributor

@mklocek mklocek commented Mar 17, 2026

Changes

  • Add support for Email Logs API
  • Fix autoload.php path in examples

Summary by CodeRabbit

  • New Features

    • Added Email Logs API: list and retrieve individual email logs with rich filtering and pagination.
  • Documentation

    • Email Logs documented under Email API; Suppressions removed from General API; added example demonstrating listing, filtering, pagination, and fetching a message by ID.
  • Tests

    • New tests covering Email Logs behavior and query parameter normalization.
  • Chores

    • Updated PHP example bootstrap paths.

@coderabbitai
Copy link

coderabbitai bot commented Mar 17, 2026

Warning

Rate limit exceeded

@mklocek has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 7 minutes and 37 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: af83742d-6a4e-4696-bd82-8d76c09acdb4

📥 Commits

Reviewing files that changed from the base of the PR and between 514bd21 and aa1cd7f.

📒 Files selected for processing (38)
  • README.md
  • examples/batch/bulk.php
  • examples/batch/bulk_template.php
  • examples/batch/sandbox.php
  • examples/batch/sandbox_template.php
  • examples/batch/transactional.php
  • examples/batch/transactional_template.php
  • examples/bulk/bulk.php
  • examples/bulk/bulk_template.php
  • examples/config/all.php
  • examples/contact-fields/all.php
  • examples/contact-lists/all.php
  • examples/contacts/all.php
  • examples/general/accounts.php
  • examples/general/billing.php
  • examples/general/permissions.php
  • examples/general/users.php
  • examples/sending/all.php
  • examples/sending/email-logs.php
  • examples/sending/minimal.php
  • examples/sending/suppressions.php
  • examples/sending/template.php
  • examples/templates/all.php
  • examples/testing/attachments.php
  • examples/testing/inboxes.php
  • examples/testing/messages.php
  • examples/testing/projects.php
  • examples/testing/send-mail.php
  • examples/testing/template.php
  • src/Api/Sending/EmailLogs.php
  • src/DTO/Request/EmailLogs/EmailLogsFilterOperator.php
  • src/DTO/Request/EmailLogs/EmailLogsFilterValue.php
  • src/DTO/Request/EmailLogs/EmailLogsListFilters.php
  • src/DTO/Request/EmailLogs/FilterCriterion.php
  • src/MailtrapSendingClient.php
  • tests/Api/AbstractApiTest.php
  • tests/Api/Sending/EmailLogsTest.php
  • tests/MailtrapSendingClientTest.php
📝 Walkthrough

Walkthrough

Adds Email Logs API support (list and get message) to the SDK, registers it on the sending client, introduces DTOs and filters for listing, updates many example scripts' autoload paths, and adds tests for the new API and query-parameter normalization.

Changes

Cohort / File(s) Summary
Documentation
README.md
Moved Suppressions from General to Email API and added Email Logs entries under Email API.
Examples (autoload path changes)
examples/batch/*, examples/bulk/*, examples/config/*, examples/contact-fields/*, examples/contact-lists/*, examples/contacts/*, examples/general/*, examples/sending/*, examples/templates/*, examples/testing/*
Updated Composer autoload require paths from __DIR__ . '/../vendor/autoload.php' to __DIR__ . '/../../vendor/autoload.php' across example scripts.
Examples (new demo)
examples/sending/email-logs.php
Added example demonstrating listing email logs (filters, pagination) and fetching a single message by ID.
API Implementation
src/Api/Sending/EmailLogs.php
New EmailLogs API class with `getList(array
Client Mapping
src/MailtrapSendingClient.php
Registered emailLogs => Api\Sending\EmailLogs::class and added corresponding @method annotation for emailLogs(int $accountId).
DTOs / Request Models
src/DTO/Request/EmailLogs/...
EmailLogsListFilters.php, FilterCriterion.php, EmailLogsFilterOperator.php, EmailLogsFilterValue.php
New immutable request models and constants for constructing typed email-logs filters and serializing them to API query payloads.
Tests (API + client)
tests/Api/Sending/EmailLogsTest.php, tests/MailtrapSendingClientTest.php
Added EmailLogs API tests (list, filters, search_after, single message, param normalization) and updated client mapping provider to include emailLogs.
Tests (abstract utility)
tests/Api/AbstractApiTest.php
New test validating normalization of array parameters into bracket notation for query strings used by APIs.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client Code
    participant SendingClient as MailtrapSendingClient
    participant EmailLogs as EmailLogs API
    participant HTTP as HTTP Handler
    participant API as Mailtrap API Host

    Client->>SendingClient: emailLogs(accountId)
    SendingClient->>EmailLogs: instantiate(config, accountId)

    rect rgba(100,150,200,0.5)
    Note over Client,API: List email logs (filters, pagination)
    Client->>EmailLogs: getList(filters, searchAfter)
    EmailLogs->>EmailLogs: serialize filters -> query params
    EmailLogs->>HTTP: httpGet("/api/accounts/{id}/email_logs", params)
    HTTP->>API: GET /api/accounts/{id}/email_logs
    API-->>HTTP: HTTP response
    HTTP-->>EmailLogs: response body
    EmailLogs->>EmailLogs: handleResponse()
    EmailLogs-->>Client: ResponseInterface
    end

    rect rgba(150,100,200,0.5)
    Note over Client,API: Get single message by ID
    Client->>EmailLogs: getMessage(messageId)
    EmailLogs->>HTTP: httpGet("/api/accounts/{id}/email_logs/{messageId}")
    HTTP->>API: GET /api/accounts/{id}/email_logs/{messageId}
    API-->>HTTP: HTTP response
    HTTP-->>EmailLogs: response body
    EmailLogs->>EmailLogs: handleResponse()
    EmailLogs-->>Client: ResponseInterface
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • IgorDobryn
  • i7an
  • leonid-shevtsov
  • yanchuk
  • VladimirTaytor

Poem

🐰 I hopped through logs both near and far,
Filters in paw and a shining star,
Examples shifted two levels down,
New endpoints planted in client town,
I nibble tests — all green, huzzah!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is incomplete compared to the template. It lacks required sections: Motivation, How to test, and Images/GIFs. Only a minimal 'Changes' section is provided without proper formatting or detail. Expand the description to include Motivation section explaining why this feature is needed, How to test section with checkbox items for testing steps, and fill in relevant sections from the template structure.
Docstring Coverage ⚠️ Warning Docstring coverage is 16.13% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add support for Email Logs API' is clear, concise, and directly reflects the primary change in the PR - introducing Email Logs API functionality across multiple files.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch email-logs-api
📝 Coding Plan
  • Generate coding plan for human review comments

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.

@mklocek
Copy link
Contributor Author

mklocek commented Mar 17, 2026

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Mar 17, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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/Api/Sending/EmailLogs.php (1)

65-72: Consider adding empty string validation for sendingMessageId.

If an empty string is passed, the resulting URL would be malformed (ending with /email_logs/). While the API would return an error, early validation provides a clearer developer experience.

🛡️ Optional defensive validation
 public function getMessage(string $sendingMessageId): ResponseInterface
 {
+    if ($sendingMessageId === '') {
+        throw new \InvalidArgumentException('sendingMessageId cannot be empty');
+    }
+
     return $this->handleResponse(
         $this->httpGet(
             sprintf('%s/%s', $this->getBasePath(), $sendingMessageId)
         )
     );
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Api/Sending/EmailLogs.php` around lines 65 - 72, The getMessage method
should validate the sendingMessageId parameter to prevent an empty string
building a malformed URL; add a check at the start of getMessage (e.g., if
trim($sendingMessageId) === '') and throw an InvalidArgumentException (or other
existing app-specific exception) with a clear message before calling
httpGet/handleResponse so callers get immediate feedback instead of a downstream
HTTP error.
tests/Api/Sending/EmailLogsTest.php (1)

106-131: Consider moving reflection-based test to AbstractApi test class.

This test verifies normalizeArrayParams behavior, which is a method in AbstractApi. While it works here, testing inherited protected/private methods through reflection in a child class test can make it harder to maintain. Consider whether this test belongs in an AbstractApiTest class instead, or document why it's specifically needed here for EmailLogs.

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

In `@tests/Api/Sending/EmailLogsTest.php` around lines 106 - 131, The test
testGetListQueryParamsUseBracketNotationForMultipleValues is exercising
AbstractApi::normalizeArrayParams via reflection from the EmailLogs test class;
move this reflection-based test into a dedicated AbstractApiTest (or a common
parent test) to keep unit tests centered on the class that owns the logic, or
alternatively add a clarifying comment in the EmailLogs test explaining why
normalizeArrayParams must be validated here; update/remove the reflection usage
from EmailLogsTest and create a new test method in AbstractApiTest that calls
normalizeArrayParams via reflection to assert bracket notation behavior,
referencing the same behavior and assertions currently present in
testGetListQueryParamsUseBracketNotationForMultipleValues.
🤖 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/Api/Sending/EmailLogs.php`:
- Around line 65-72: The getMessage method should validate the sendingMessageId
parameter to prevent an empty string building a malformed URL; add a check at
the start of getMessage (e.g., if trim($sendingMessageId) === '') and throw an
InvalidArgumentException (or other existing app-specific exception) with a clear
message before calling httpGet/handleResponse so callers get immediate feedback
instead of a downstream HTTP error.

In `@tests/Api/Sending/EmailLogsTest.php`:
- Around line 106-131: The test
testGetListQueryParamsUseBracketNotationForMultipleValues is exercising
AbstractApi::normalizeArrayParams via reflection from the EmailLogs test class;
move this reflection-based test into a dedicated AbstractApiTest (or a common
parent test) to keep unit tests centered on the class that owns the logic, or
alternatively add a clarifying comment in the EmailLogs test explaining why
normalizeArrayParams must be validated here; update/remove the reflection usage
from EmailLogsTest and create a new test method in AbstractApiTest that calls
normalizeArrayParams via reflection to assert bracket notation behavior,
referencing the same behavior and assertions currently present in
testGetListQueryParamsUseBracketNotationForMultipleValues.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9a48a200-c8d6-4ff8-a733-cd3af6175da1

📥 Commits

Reviewing files that changed from the base of the PR and between 306d117 and 49639b8.

📒 Files selected for processing (33)
  • README.md
  • examples/batch/bulk.php
  • examples/batch/bulk_template.php
  • examples/batch/sandbox.php
  • examples/batch/sandbox_template.php
  • examples/batch/transactional.php
  • examples/batch/transactional_template.php
  • examples/bulk/bulk.php
  • examples/bulk/bulk_template.php
  • examples/config/all.php
  • examples/contact-fields/all.php
  • examples/contact-lists/all.php
  • examples/contacts/all.php
  • examples/general/accounts.php
  • examples/general/billing.php
  • examples/general/permissions.php
  • examples/general/users.php
  • examples/sending/all.php
  • examples/sending/email-logs.php
  • examples/sending/minimal.php
  • examples/sending/suppressions.php
  • examples/sending/template.php
  • examples/templates/all.php
  • examples/testing/attachments.php
  • examples/testing/inboxes.php
  • examples/testing/messages.php
  • examples/testing/projects.php
  • examples/testing/send-mail.php
  • examples/testing/template.php
  • src/Api/Sending/EmailLogs.php
  • src/MailtrapSendingClient.php
  • tests/Api/Sending/EmailLogsTest.php
  • tests/MailtrapSendingClientTest.php

@mklocek mklocek force-pushed the email-logs-api branch 3 times, most recently from df34d92 to 514bd21 Compare March 17, 2026 08:39
@mklocek mklocek marked this pull request as ready for review March 17, 2026 08:39
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: 2

🧹 Nitpick comments (2)
tests/Api/Sending/EmailLogsTest.php (1)

117-120: Avoid tautological assertions on local fixtures.

At Line 117-120 and Line 145-147, assertions validate $expectedParams (locally constructed data) rather than runtime output. Since ->with($basePath, $expectedParams) already checks request params, these can be removed or replaced with assertions on parsed response content.

Also applies to: 145-147

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

In `@tests/Api/Sending/EmailLogsTest.php` around lines 117 - 120, The test
contains tautological assertions against the locally constructed $expectedParams
(in EmailLogsTest, the variables around the ->with($basePath, $expectedParams)
expectation), e.g. checking $expectedParams['filters']['sent_after'] and subject
operator/value; remove these redundant assertions and instead assert runtime
behavior: either remove lines asserting $expectedParams fields or replace them
with assertions on the parsed response or on the mock client request that is
actually executed (the object passed into ->with(...)). Update the assertions at
both locations (around the existing ->with($basePath, $expectedParams) usage and
the similar block at lines ~145-147) to verify the response or the real request
payload rather than the local fixture.
src/DTO/Request/EmailLogs/EmailLogsListFilters.php (1)

66-71: Fail fast on unknown criterion keys in withCriterion().

Currently, invalid keys are silently ignored in toArray(), which can mask typos and send broader-than-expected queries. Validate in withCriterion() and throw early.

💡 Proposed fix
 use Mailtrap\DTO\Request\RequestInterface;
+use Mailtrap\Exception\InvalidArgumentException;
@@
     public function withCriterion(string $name, FilterCriterion $criterion): self
     {
+        if (!\in_array($name, self::VALID_CRITERIA_KEYS, true)) {
+            throw new InvalidArgumentException(sprintf('Unknown email logs criterion: %s', $name));
+        }
+
         $criteria = $this->criteria;
         $criteria[$name] = $criterion;
 
         return new self($this->sentAfter, $this->sentBefore, $criteria);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/DTO/Request/EmailLogs/EmailLogsListFilters.php` around lines 66 - 71, The
withCriterion method in EmailLogsListFilters should fail fast for unknown
criterion keys: before adding $name to $criteria, validate $name against the set
of allowed criterion keys used by toArray (e.g. a class-level allowed keys array
or the keys expected in toArray), and if $name is not recognised throw an
InvalidArgumentException with a clear message; update withCriterion to perform
this check (referencing EmailLogsListFilters::withCriterion, FilterCriterion,
and toArray) so typos are rejected early.
🤖 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/Api/Sending/EmailLogs.php`:
- Around line 45-47: Trim the $searchAfter input before validating and adding it
to query params: update the logic around $searchAfter (the variable used to set
$params['search_after']) to call trim() on the value and only set
$params['search_after'] when the trimmed value is not an empty string (i.e., not
null and not whitespace). This ensures whitespace-only cursors are ignored and
only meaningful cursors are sent downstream.
- Around line 67-76: The getMessage method currently only checks for empty
sendingMessageId; update it to validate that sendingMessageId matches the
expected UUID pattern and throw InvalidArgumentException if not, then URL-encode
the validated sendingMessageId before composing the path passed to httpGet (use
the existing getBasePath(), httpGet(), and handleResponse() calls) to prevent
reserved/path characters from producing malformed endpoints.

---

Nitpick comments:
In `@src/DTO/Request/EmailLogs/EmailLogsListFilters.php`:
- Around line 66-71: The withCriterion method in EmailLogsListFilters should
fail fast for unknown criterion keys: before adding $name to $criteria, validate
$name against the set of allowed criterion keys used by toArray (e.g. a
class-level allowed keys array or the keys expected in toArray), and if $name is
not recognised throw an InvalidArgumentException with a clear message; update
withCriterion to perform this check (referencing
EmailLogsListFilters::withCriterion, FilterCriterion, and toArray) so typos are
rejected early.

In `@tests/Api/Sending/EmailLogsTest.php`:
- Around line 117-120: The test contains tautological assertions against the
locally constructed $expectedParams (in EmailLogsTest, the variables around the
->with($basePath, $expectedParams) expectation), e.g. checking
$expectedParams['filters']['sent_after'] and subject operator/value; remove
these redundant assertions and instead assert runtime behavior: either remove
lines asserting $expectedParams fields or replace them with assertions on the
parsed response or on the mock client request that is actually executed (the
object passed into ->with(...)). Update the assertions at both locations (around
the existing ->with($basePath, $expectedParams) usage and the similar block at
lines ~145-147) to verify the response or the real request payload rather than
the local fixture.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: eda3a315-719d-4bed-956a-ae74afbfc384

📥 Commits

Reviewing files that changed from the base of the PR and between 49639b8 and 514bd21.

📒 Files selected for processing (38)
  • README.md
  • examples/batch/bulk.php
  • examples/batch/bulk_template.php
  • examples/batch/sandbox.php
  • examples/batch/sandbox_template.php
  • examples/batch/transactional.php
  • examples/batch/transactional_template.php
  • examples/bulk/bulk.php
  • examples/bulk/bulk_template.php
  • examples/config/all.php
  • examples/contact-fields/all.php
  • examples/contact-lists/all.php
  • examples/contacts/all.php
  • examples/general/accounts.php
  • examples/general/billing.php
  • examples/general/permissions.php
  • examples/general/users.php
  • examples/sending/all.php
  • examples/sending/email-logs.php
  • examples/sending/minimal.php
  • examples/sending/suppressions.php
  • examples/sending/template.php
  • examples/templates/all.php
  • examples/testing/attachments.php
  • examples/testing/inboxes.php
  • examples/testing/messages.php
  • examples/testing/projects.php
  • examples/testing/send-mail.php
  • examples/testing/template.php
  • src/Api/Sending/EmailLogs.php
  • src/DTO/Request/EmailLogs/EmailLogsFilterOperator.php
  • src/DTO/Request/EmailLogs/EmailLogsFilterValue.php
  • src/DTO/Request/EmailLogs/EmailLogsListFilters.php
  • src/DTO/Request/EmailLogs/FilterCriterion.php
  • src/MailtrapSendingClient.php
  • tests/Api/AbstractApiTest.php
  • tests/Api/Sending/EmailLogsTest.php
  • tests/MailtrapSendingClientTest.php
🚧 Files skipped from review as they are similar to previous changes (21)
  • examples/testing/inboxes.php
  • examples/testing/send-mail.php
  • examples/general/accounts.php
  • examples/batch/bulk.php
  • examples/batch/transactional_template.php
  • examples/sending/email-logs.php
  • examples/general/billing.php
  • examples/contact-fields/all.php
  • examples/contact-lists/all.php
  • examples/templates/all.php
  • examples/testing/attachments.php
  • examples/testing/projects.php
  • examples/contacts/all.php
  • examples/bulk/bulk_template.php
  • examples/general/permissions.php
  • examples/sending/suppressions.php
  • examples/batch/bulk_template.php
  • examples/testing/messages.php
  • examples/config/all.php
  • examples/sending/minimal.php
  • examples/batch/sandbox.php

mklocek added 2 commits March 18, 2026 14:17
The README says to run examples from the project root, e.g. php examples/sending/minimal.php.
Scripts under examples/<subdir>/ (e.g. examples/batch/, examples/sending/) used:
require __DIR__ . '/../vendor/autoload.php';
That resolves to examples/vendor/autoload.php, while vendor/ actually lives at the project root, so the path was incorrect.

From examples/<subdir>/, you need to go up two levels to reach the project root, then into vendor/:
require __DIR__ . '/../../vendor/autoload.php';
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants