Skip to content

Add Sandbox messages API#82

Open
DagonWat wants to merge 2 commits intomainfrom
MT-19857-sandbox-messages
Open

Add Sandbox messages API#82
DagonWat wants to merge 2 commits intomainfrom
MT-19857-sandbox-messages

Conversation

@DagonWat
Copy link
Contributor

@DagonWat DagonWat commented Jan 5, 2026

Motivation

Add Sandbox Messages API

Changes

  • Adding new Sandbox::Message class with SandboxMessageAPI endpoints

Summary by CodeRabbit

Release Notes

  • New Features
    • Added Sandbox Messages API supporting message retrieval, updates, and deletion
    • Message forwarding to email addresses
    • Access to message content in multiple formats (HTML, plain text, raw, EML)
    • HTML analysis and spam score evaluation
    • Message header inspection

@coderabbitai
Copy link

coderabbitai bot commented Jan 5, 2026

📝 Walkthrough

Walkthrough

This PR introduces a new SandboxMessagesAPI to the Mailtrap Ruby client library, enabling message management for sandbox accounts. It adds a SandboxMessage data model, a complete API wrapper class with CRUD and message-specific operations, custom parser support to the HTTP client, comprehensive VCR test fixtures, and test specifications.

Changes

Cohort / File(s) Summary
Core Client Infrastructure
lib/mailtrap.rb, lib/mailtrap/client.rb
Added require for SandboxMessagesAPI. Extended client's get, perform_request, and handle_response methods to accept an optional custom_parser parameter, enabling flexible response parsing strategies beyond default JSON parsing.
Data Models
lib/mailtrap/sandbox_message.rb
New Struct-based data class with 22 attributes representing a sandbox message, including id, inbox_id, subject, sender/recipient details, size metrics, body paths, and SMTP/blacklist metadata. Includes to_h method for hash conversion.
API Implementation
lib/mailtrap/sandbox_messages_api.rb
New API wrapper class providing 13 public methods: standard CRUD operations (get, update, delete, list), message actions (forward_message, get_spam_score, get_html_analysis), and specialized getters (get_text_message, get_raw_message, get_html_source, get_html_message, get_message_as_eml, get_mail_headers). Delegates to BaseAPI mixin and leverages custom_parser for non-JSON responses.
Test Fixtures (VCR Cassettes)
spec/fixtures/vcr_cassettes/Mailtrap_SandboxMessagesAPI/*
33 YAML cassette files recording HTTP interactions for all API methods, covering success paths (200 OK with realistic message payloads) and error scenarios (404 Not Found, 401 Unauthorized). Enable test replay without live API calls.
Test Specifications
spec/mailtrap/sandbox_message_spec.rb, spec/mailtrap/sandbox_messages_api_spec.rb
Comprehensive RSpec test suite (533 lines) validating SandboxMessage initialization, all API methods, response mapping to data models, parameter handling, and error conditions. Uses VCR for HTTP mocking.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Suggested reviewers

  • IgorDobryn
  • i7an

Poem

🐰 Hop along, dear API friends,
New sandbox messages blend,
Custom parsers thread with care,
Through requests floating in the air!
CRUD and actions, all in place,
Mailtrap's growth at rapid pace!

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The description is largely incomplete. It lacks comprehensive details about changes, testing instructions, and only provides high-level motivation without sufficient documentation of implementation details. Expand the description to include detailed change breakdown, comprehensive testing instructions with checkboxes, and consider adding visual documentation or links to related issues.
Docstring Coverage ⚠️ Warning Docstring coverage is 72.73% 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 Sandbox messages API' clearly and directly describes the main change - introducing a new Sandbox Messages API to the codebase.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch MT-19857-sandbox-messages

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.

@i7an i7an marked this pull request as draft January 7, 2026 16:36
Base automatically changed from add-projects-api to main January 13, 2026 12:35
@DagonWat DagonWat marked this pull request as ready for review February 4, 2026 16:21
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: 19

🤖 Fix all issues with AI agents
In `@lib/mailtrap/client.rb`:
- Around line 281-284: The handle_response method triggers a RuboCop
Metrics/AbcSize violation; either add AbcSize to the existing RuboCop disable
comment for handle_response or refactor the success branch into a small helper
to reduce ABC complexity—specifically extract the success handling (the when
Net::HTTPOK, Net::HTTPCreated branch that chooses between
json_response(response.body) and custom_parser.call(response.body)) into a
private method (e.g., parse_success_response(response, custom_parser)) and call
that from handle_response to bring AbcSize under the threshold.
- Line 248: The method perform_request currently has six parameters and triggers
Metrics/ParameterLists; refactor it to accept a single options hash or a Request
object to reduce parameter count: replace the multiple keyword params (method:,
host:, path:, query_params:, body:, custom_parser:) with one param (e.g.,
request_options or request) and update callers to pass a hash or instantiated
Request (containing method, host, path, query_params, body, custom_parser);
alternatively, if refactoring callers is impractical, add a scoped RuboCop
directive to disable Metrics/ParameterLists for perform_request only. Ensure you
update the method signature, usages, and any documentation to match the new
options object name.

In `@lib/mailtrap/sandbox_message.rb`:
- Line 54: The YARD doc comment incorrectly references "SendingDomain" for the
SandboxMessage class; update the comment on the SandboxMessage
attribute/hash-returning method so it describes SandboxMessage (e.g., "The
SandboxMessage attributes as a hash") instead of "SendingDomain" and keep the
return type as [Hash]; locate the comment in class SandboxMessage and replace
the incorrect identifier.

In `@lib/mailtrap/sandbox_messages_api.rb`:
- Around line 128-134: Update the YARD docstring for get_message_as_eml: replace
the incorrect "Get mail headers" description and the `@return` type with a correct
description indicating it retrieves the full message in EML format (e.g., "Get
message as EML") and adjust the `@return` to reflect the raw EML response (e.g.,
String or raw response) while keeping the `@param` and @!macro api_errors intact;
locate the doc above def get_message_as_eml(message_id) which calls
client.get("#{base_path}/#{message_id}/body.eml", custom_parser: ->(response) {
response }).

In
`@spec/fixtures/vcr_cassettes/Mailtrap_SandboxMessagesAPI/_delete/returns_deleted_project_id.yml`:
- Around line 78-81: The recorded fixture JSON string contains PII in the
to_email field ("to_email":"yahor.vaitsiakhouski@railsware.com"); replace that
real address with a safe placeholder (e.g., "to_email":"user@example.com")
inside the string value and scan the same JSON blob for any other personal
identifiers (from_email, from_name, any human-readable names within the string)
and substitute with non-identifying placeholders, ensuring you preserve valid
JSON escaping and the overall string format (the surrounding "string": '...') so
the cassette remains valid for tests.

In
`@spec/fixtures/vcr_cassettes/Mailtrap_SandboxMessagesAPI/_forward_message/returns_success.yml`:
- Around line 6-8: Replace the real-company test email in the cassette payload
with an RFC 2606 reserved address: update the YAML body's string value
'{"email":"example@railsware.com"}' to use a reserved domain such as
'{"email":"test@example.com"}' (and similarly replace any other occurrences of
"railsware.com" in the same file) so test fixtures use a non-production/reserved
test email.

In
`@spec/fixtures/vcr_cassettes/Mailtrap_SandboxMessagesAPI/_forward_message/when_sandbox_message_does_not_exist/raises_not_found_error.yml`:
- Around line 6-8: The cassette's request body string contains a non-generic
email "example@railsware.com"; update the YAML value under the body -> string
key to use a generic placeholder like "test@example.com" so the fixture does not
expose organizational email patterns (update the string value in the cassette
where body.encoding and body.string are defined).

In
`@spec/fixtures/vcr_cassettes/Mailtrap_SandboxMessagesAPI/_get_mail_headers/returns_headers.yml`:
- Line 77: The fixture contains PII: the JSON string value under the YAML
"string" that includes headers.to contains the real email
"yahor.vaitsiakhouski@railsware.com"; replace that address with a neutral
placeholder (e.g., test@example.com) inside the embedded JSON (the
'{"headers":...}' value) so the headers.to field is masked, ensure any necessary
escaping/quoting in the YAML string is preserved and update the cassette
accordingly.

In
`@spec/fixtures/vcr_cassettes/Mailtrap_SandboxMessagesAPI/_get_message_as_eml/returns_html_message.yml`:
- Around line 79-85: The EML fixture contains a real recipient in the "To:"
header inside the YAML value for the "string" field (the long MIME string);
replace the real address yahor.vaitsiakhouski@railsware.com with a non-sensitive
placeholder such as test@example.com (or recipient@example.org) in that "string"
value so the cassette no longer contains PII, preserving the surrounding MIME
formatting and boundaries (e.g., the existing boundary=... and multipart
content).

In
`@spec/fixtures/vcr_cassettes/Mailtrap_SandboxMessagesAPI/_get_message_as_eml/when_sandbox_message_does_not_exist/raises_not_found_error.yml`:
- Around line 57-68: Replace sensitive operational header values in the VCR
fixture YAML by scrubbing the X-Request-Id, X-Cloud-Trace-Context, and Cf-Ray
header values and replacing them with deterministic placeholders (e.g.
<X-REQUEST-ID>, <X-CLOUD-TRACE-CONTEXT>, <CF-RAY>) in the recorded response
headers block; apply the same replacement to the other occurrence mentioned (the
headers at the other record location). Ensure the cassette’s headers now only
contain placeholder values for those three keys so the fixture contains no real
request/trace identifiers.

In
`@spec/fixtures/vcr_cassettes/Mailtrap_SandboxMessagesAPI/_get_raw_message/returns_raw_message.yml`:
- Around line 75-81: The recorded cassette contains a real email in the YAML
string value (the "string" field) — replace the PII
`yahor.vaitsiakhouski@railsware.com` with a sanitized test address like
`test@example.com` (or `user@mailtrap.io`) inside the raw message string, ensure
any other real-identifying data in that same "string" value is similarly
sanitized, then save the updated fixtures file so the VCR cassette no longer
contains real user PII.

In
`@spec/fixtures/vcr_cassettes/Mailtrap_SandboxMessagesAPI/_get/maps_response_data_to_SandboxMessage_object.yml`:
- Around line 78-81: Replace the real recipient address found in the fixture
string: locate the "to_email" field value that currently contains
"alex.b@railsware.com" in the YAML cassette content and change it to a non-PII
placeholder such as "test@example.com" or "<RECIPIENT_EMAIL>" so the recorded
response no longer contains personal data.

In
`@spec/fixtures/vcr_cassettes/Mailtrap_SandboxMessagesAPI/_list/maps_response_data_to_SandboxMessage_objects.yml`:
- Around line 78-97: The VCR cassette contains real email PII inside the JSON
string value under the YAML key "string" in
maps_response_data_to_SandboxMessage_objects.yml; open that file and replace the
actual addresses (e.g. alex.d@railsware.com, yahor.vaitsiakhouski@railsware.com)
with generic placeholders (e.g. user1@example.com, user2@example.com) everywhere
they appear in the JSON payload string, ensure other similar PII in that
"string" block (if any) is also sanitized, save the updated cassette and run the
test suite to confirm no fixtures break.

In
`@spec/fixtures/vcr_cassettes/Mailtrap_SandboxMessagesAPI/_list/with_parameters/maps_response_data_to_SandboxMessage_objects.yml`:
- Line 5: Replace the real email and search term in the cassette with
placeholders: update the URI query parameter "search=alex.d" to a non-PII
placeholder (e.g., "search=REDACTED") and replace any "to_email" values like
"alex.d@railsware.com" with a placeholder (e.g., "user@example.com" or
"REDACTED_EMAIL"); apply the same replacements for the other occurrences
referenced (lines 78-81) so no real email addresses or personal names remain in
the recorded HTTP interactions or URL query strings.

In
`@spec/fixtures/vcr_cassettes/Mailtrap_SandboxMessagesAPI/_list/with_parameters/when_last_id_includes_more_messages/maps_response_data_to_SandboxMessage_objects.yml`:
- Around line 78-85: The YAML cassette's recorded JSON in the string field
contains real PII emails (alex.d@railsware.com and
yahor.vaitsiakhouski@railsware.com); locate the large "string" value in the
fixture and replace those addresses with generic placeholders (e.g.,
user1@example.com, user2@example.com) preserving the JSON structure/escaping and
all other fields (ids, paths, timestamps) so tests remain valid; ensure you
update every occurrence of those two addresses inside the "string" value.
- Line 5: The URI in the cassette contains a PII-bearing query parameter value
("search=alex.d"); update the recorded URI value under the uri key so the search
parameter is anonymized (e.g., replace "search=alex.d" with "search=REDACTED" or
a generic non-identifying value like "search=sample") while keeping other
parameters (last_id, account/inbox ids) unchanged; edit the YAML entry where uri
is defined to use the sanitized query string so tests still map responses but no
actual username fragments are stored.

In
`@spec/fixtures/vcr_cassettes/Mailtrap_SandboxMessagesAPI/_update/maps_response_data_to_SandboxMessage_object.yml`:
- Around line 78-81: The cassette contains real PII in the JSON string under the
"to_email" field (value "alex.b@railsware.com"); open the YAML fixture and
replace that email with a generic placeholder (e.g., "user@example.com")
preserving the surrounding JSON quoting/escaping and any adjacent
whitespace/newlines so the string remains valid; ensure no other occurrences of
the same real address remain in this fixture (search for "alex.b@railsware.com")
and update them similarly.

In `@spec/mailtrap/sandbox_messages_api_spec.rb`:
- Line 25: Replace the real email literal 'alex.b@railsware.com' used as the
to_email expectation with a non-PII placeholder (e.g., 'user@example.com') and
do the same for the other occurrences of that literal elsewhere in the spec;
locate the string by searching for the to_email key and the exact email literal
(and any identical string occurrences) in sandbox_messages_api_spec and swap
them for a placeholder or generated test email (e.g., using a factory or Faker)
so tests no longer contain real PII.
- Around line 391-395: Update the test descriptions to use consistent
terminology: replace occurrences of "project" with "message" in the example
block that asserts deletion and in the following context; specifically update
the it description that currently reads 'returns deleted project id' to 'returns
deleted message id' and change the context label 'when project does not exist'
to 'when message does not exist' so the text matches the API under test
(references: the example using delete[:id] and sandbox_message_id).
🧹 Nitpick comments (2)
spec/mailtrap/sandbox_message_spec.rb (1)

29-35: Consider using reserved IP for test data.

The IP address 193.62.62.184 appears to be a real routable IP. For test fixtures, consider using a reserved documentation IP like 192.0.2.1 (RFC 5737 TEST-NET-1) to avoid any potential conflicts.

This is a minor nit and not blocking.

lib/mailtrap/sandbox_messages_api.rb (1)

100-133: Consider extracting the repeated identity lambda.

The identity lambda ->(response) { response } is duplicated across five methods. Extracting it to a constant would improve maintainability and make the intent clearer.

♻️ Suggested refactor

Add a constant at the class level:

PASSTHROUGH_PARSER = ->(response) { response }

Then use it in each method:

     def get_text_message(message_id)
-      client.get("#{base_path}/#{message_id}/body.txt", custom_parser: ->(response) { response })
+      client.get("#{base_path}/#{message_id}/body.txt", custom_parser: PASSTHROUGH_PARSER)
     end

end

def perform_request(method:, host:, path:, query_params: {}, body: nil)
def perform_request(method:, host:, path:, query_params: {}, body: nil, custom_parser: nil)
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

Rubocop violation: Parameter list exceeds limit.

The CI pipeline flags Metrics/ParameterLists: Avoid parameter lists longer than 5 parameters [6/5]. Consider grouping related parameters into an options hash or extracting a request object to reduce the parameter count.

♻️ Potential refactor using options hash
-    def perform_request(method:, host:, path:, query_params: {}, body: nil, custom_parser: nil)
+    def perform_request(method:, host:, path:, **options)
+      query_params = options.fetch(:query_params, {})
+      body = options[:body]
+      custom_parser = options[:custom_parser]
       http_client = http_client_for(host)

Alternatively, add an inline rubocop disable comment if this is acceptable for the codebase:

-    def perform_request(method:, host:, path:, query_params: {}, body: nil, custom_parser: nil)
+    def perform_request(method:, host:, path:, query_params: {}, body: nil, custom_parser: nil) # rubocop:disable Metrics/ParameterLists
📝 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
def perform_request(method:, host:, path:, query_params: {}, body: nil, custom_parser: nil)
def perform_request(method:, host:, path:, query_params: {}, body: nil, custom_parser: nil) # rubocop:disable Metrics/ParameterLists
🧰 Tools
🪛 GitHub Actions: Ruby

[error] 248-248: rubocop: Metrics/ParameterLists: Avoid parameter lists longer than 5 parameters. [6/5]

🤖 Prompt for AI Agents
In `@lib/mailtrap/client.rb` at line 248, The method perform_request currently has
six parameters and triggers Metrics/ParameterLists; refactor it to accept a
single options hash or a Request object to reduce parameter count: replace the
multiple keyword params (method:, host:, path:, query_params:, body:,
custom_parser:) with one param (e.g., request_options or request) and update
callers to pass a hash or instantiated Request (containing method, host, path,
query_params, body, custom_parser); alternatively, if refactoring callers is
impractical, add a scoped RuboCop directive to disable Metrics/ParameterLists
for perform_request only. Ensure you update the method signature, usages, and
any documentation to match the new options object name.

Comment on lines +281 to +284
def handle_response(response, custom_parser: nil) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
case response
when Net::HTTPOK, Net::HTTPCreated
json_response(response.body)
custom_parser.nil? ? json_response(response.body) : custom_parser.call(response.body)
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

Rubocop violation: AbcSize too high.

The CI pipeline flags Metrics/AbcSize: Assignment Branch Condition size for handle_response is too high [26.4/25]. The existing disable comment only covers CyclomaticComplexity and MethodLength. You may need to add AbcSize to the disable list, or consider extracting the success case handling into a helper method.

♻️ Quick fix: extend rubocop disable
-    def handle_response(response, custom_parser: nil) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
+    def handle_response(response, custom_parser: nil) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/AbcSize
📝 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
def handle_response(response, custom_parser: nil) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
case response
when Net::HTTPOK, Net::HTTPCreated
json_response(response.body)
custom_parser.nil? ? json_response(response.body) : custom_parser.call(response.body)
def handle_response(response, custom_parser: nil) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/AbcSize
case response
when Net::HTTPOK, Net::HTTPCreated
custom_parser.nil? ? json_response(response.body) : custom_parser.call(response.body)
🧰 Tools
🪛 GitHub Actions: Ruby

[error] 281-281: rubocop: Metrics/AbcSize: Assignment Branch Condition size for handle_response is too high. [<0, 24, 11> 26.4/25]

🤖 Prompt for AI Agents
In `@lib/mailtrap/client.rb` around lines 281 - 284, The handle_response method
triggers a RuboCop Metrics/AbcSize violation; either add AbcSize to the existing
RuboCop disable comment for handle_response or refactor the success branch into
a small helper to reduce ABC complexity—specifically extract the success
handling (the when Net::HTTPOK, Net::HTTPCreated branch that chooses between
json_response(response.body) and custom_parser.call(response.body)) into a
private method (e.g., parse_success_response(response, custom_parser)) and call
that from handle_response to bring AbcSize under the threshold.

:smtp_information,
keyword_init: true
) do
# @return [Hash] The SendingDomain attributes as a hash
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

Incorrect documentation comment.

The YARD comment references "SendingDomain" but this is the SandboxMessage class. This appears to be a copy-paste error.

📝 Proposed fix
-    # `@return` [Hash] The SendingDomain attributes as a hash
+    # `@return` [Hash] The SandboxMessage attributes as a hash
📝 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
# @return [Hash] The SendingDomain attributes as a hash
# `@return` [Hash] The SandboxMessage attributes as a hash
🤖 Prompt for AI Agents
In `@lib/mailtrap/sandbox_message.rb` at line 54, The YARD doc comment incorrectly
references "SendingDomain" for the SandboxMessage class; update the comment on
the SandboxMessage attribute/hash-returning method so it describes
SandboxMessage (e.g., "The SandboxMessage attributes as a hash") instead of
"SendingDomain" and keep the return type as [Hash]; locate the comment in class
SandboxMessage and replace the incorrect identifier.

Comment on lines +128 to +134
# Get mail headers
# @param message_id [Integer] The Sandbox message ID
# @return [Hash] mail headers of the message.
# @!macro api_errors
def get_message_as_eml(message_id)
client.get("#{base_path}/#{message_id}/body.eml", custom_parser: ->(response) { response })
end
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

Incorrect documentation for get_message_as_eml.

The YARD comment says "Get mail headers" but this method retrieves the message as EML format. This appears to be a copy-paste error.

📝 Proposed fix
-    # Get mail headers
+    # Get message as EML
     # `@param` message_id [Integer] The Sandbox message ID
-    # `@return` [Hash] mail headers of the message.
+    # `@return` [String] message in EML format.
     # @!macro api_errors
     def get_message_as_eml(message_id)
       client.get("#{base_path}/#{message_id}/body.eml", custom_parser: ->(response) { response })
     end
🤖 Prompt for AI Agents
In `@lib/mailtrap/sandbox_messages_api.rb` around lines 128 - 134, Update the YARD
docstring for get_message_as_eml: replace the incorrect "Get mail headers"
description and the `@return` type with a correct description indicating it
retrieves the full message in EML format (e.g., "Get message as EML") and adjust
the `@return` to reflect the raw EML response (e.g., String or raw response) while
keeping the `@param` and @!macro api_errors intact; locate the doc above def
get_message_as_eml(message_id) which calls
client.get("#{base_path}/#{message_id}/body.eml", custom_parser: ->(response) {
response }).

Comment on lines +78 to +81
string: '{"id":5273448410,"inbox_id":4288340,"subject":"Hello from Mailtrap","sent_at":"2026-01-04T16:55:50.867Z","from_email":"reply@demomailtrap.co","from_name":"","to_email":"yahor.vaitsiakhouski@railsware.com","to_name":"","email_size":691,"is_read":true,"created_at":"2026-01-04T16:55:50.871Z","updated_at":"2026-01-04T23:24:26.317Z","template_id":0,"template_variables":null,"html_body_size":29,"text_body_size":20,"human_size":"691
Bytes","html_path":"/api/testing_message_parts/QEVuQwFAEAAEGQjR4zBj6UG33mPU+7iLYIJibHfQmLEEl2ga65S+owaKeN%2FHzvFB5Tu91Fd6wM9R3XzZFr8GCQc%2F1PCUoXODfH1JT8IXq9ugoIoZmwNHwF6iSghUWZqmBoWjzGHR66M=/body.html","txt_path":"/api/testing_message_parts/QEVuQwFAEAAEGQjR4zBj6UG33mPU+7iLYIJibHfQmLEEl2ga65S+owaKeN%2FHzvFB5Tu91Fd6wM9R3XzZFr8GCQc%2F1PCUoXODfH1JT8IXq9ugoIoZmwNHwF6iSghUWZqmBoWjzGHR66M=/body.txt","raw_path":"/api/testing_message_parts/QEVuQwFAEAAEGQjR4zBj6UG33mPU+7iLYIJibHfQmLEEl2ga65S+owaKeN%2FHzvFB5Tu91Fd6wM9R3XzZFr8GCQc%2F1PCUoXODfH1JT8IXq9ugoIoZmwNHwF6iSghUWZqmBoWjzGHR66M=/body.raw","download_path":"/api/testing_message_parts/QEVuQwFAEAAEGQjR4zBj6UG33mPU+7iLYIJibHfQmLEEl2ga65S+owaKeN%2FHzvFB5Tu91Fd6wM9R3XzZFr8GCQc%2F1PCUoXODfH1JT8IXq9ugoIoZmwNHwF6iSghUWZqmBoWjzGHR66M=/body.eml","html_source_path":"/api/testing_message_parts/QEVuQwFAEAAEGQjR4zBj6UG33mPU+7iLYIJibHfQmLEEl2ga65S+owaKeN%2FHzvFB5Tu91Fd6wM9R3XzZFr8GCQc%2F1PCUoXODfH1JT8IXq9ugoIoZmwNHwF6iSghUWZqmBoWjzGHR66M=/body.htmlsource","blacklists_report_info":{"result":"success","domain":"demomailtrap.co","ip":"172.67.134.80","report":[{"name":"BACKSCATTERER","url":"http://www.backscatterer.org/index.php","in_black_list":false},{"name":"BARRACUDA","url":"http://barracudacentral.org/rbl","in_black_list":false},{"name":"Spamrbl
IMP-SPAM","url":"http://antispam.imp.ch/?lng=1","in_black_list":false},{"name":"Wormrbl
IMP-SPAM","url":"http://antispam.imp.ch/?lng=1","in_black_list":false},{"name":"LASHBACK","url":"http://blacklist.lashback.com/","in_black_list":false},{"name":"NIXSPAM","url":"https://www.heise.de/ix/NiX-Spam-DNSBL-and-blacklist-for-download-499637.html","in_black_list":false},{"name":"PSBL","url":"https://psbl.org/","in_black_list":false},{"name":"SORBS-SPAM","url":"http://www.sorbs.net/lookup.shtml","in_black_list":false},{"name":"SPAMCOP","url":"http://spamcop.net/bl.shtml","in_black_list":false},{"name":"TRUNCATE","url":"http://www.gbudb.com/truncate/index.jsp","in_black_list":false}]},"smtp_information":{"ok":false}}'
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

PII detected: Real email address with identifiable name in response body.

The to_email field contains a real email address (yahor.vaitsiakhouski@railsware.com) that includes a person's full name. This should be replaced with a placeholder to avoid recording PII in test fixtures.

As per coding guidelines: "Act as a data privacy officer. Carefully read all the vcr cassettes with recorded HTTP interactions and try to identify sensitive data that could potentially be recorded. It can be anything from PII to credentials."

🤖 Prompt for AI Agents
In
`@spec/fixtures/vcr_cassettes/Mailtrap_SandboxMessagesAPI/_delete/returns_deleted_project_id.yml`
around lines 78 - 81, The recorded fixture JSON string contains PII in the
to_email field ("to_email":"yahor.vaitsiakhouski@railsware.com"); replace that
real address with a safe placeholder (e.g., "to_email":"user@example.com")
inside the string value and scan the same JSON blob for any other personal
identifiers (from_email, from_name, any human-readable names within the string)
and substitute with non-identifying placeholders, ensuring you preserve valid
JSON escaping and the overall string format (the surrounding "string": '...') so
the cassette remains valid for tests.

http_interactions:
- request:
method: get
uri: https://mailtrap.io/api/accounts/1111111/inboxes/4288340/messages?last_id=5273671999&search=alex.d
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

URL query parameter contains potential PII.

The search parameter search=alex.d reveals a partial username/name pattern.

🤖 Prompt for AI Agents
In
`@spec/fixtures/vcr_cassettes/Mailtrap_SandboxMessagesAPI/_list/with_parameters/when_last_id_includes_more_messages/maps_response_data_to_SandboxMessage_objects.yml`
at line 5, The URI in the cassette contains a PII-bearing query parameter value
("search=alex.d"); update the recorded URI value under the uri key so the search
parameter is anonymized (e.g., replace "search=alex.d" with "search=REDACTED" or
a generic non-identifying value like "search=sample") while keeping other
parameters (last_id, account/inbox ids) unchanged; edit the YAML entry where uri
is defined to use the sanitized query string so tests still map responses but no
actual username fragments are stored.

Comment on lines +78 to +85
string: '[{"id":5273671945,"inbox_id":4288340,"subject":"Hello from Mailtrap","sent_at":"2026-01-04T11:17:56.575-12:00","from_email":"reply@demomailtrap.co","from_name":"","to_email":"alex.d@railsware.com","to_name":"","email_size":677,"is_read":false,"created_at":"2026-01-04T11:17:56.579-12:00","updated_at":"2026-01-04T11:17:56.797-12:00","template_id":0,"template_variables":null,"html_body_size":29,"text_body_size":20,"human_size":"677
Bytes","html_path":"/api/testing_message_parts/QEVuQwFAEAD6SO5PqWHlAWWad6MRonhtsuZxmCo4xzuFGE0LxKt6KDddCoGCmfx8kJdAWyxONzRlvqhB5mnkHxhicF6lbIh6dxpbBW8fNKU4wFjIE2F2d6+IvRnXDgBVNvWDZ+PIEjg=/body.html","txt_path":"/api/testing_message_parts/QEVuQwFAEAD6SO5PqWHlAWWad6MRonhtsuZxmCo4xzuFGE0LxKt6KDddCoGCmfx8kJdAWyxONzRlvqhB5mnkHxhicF6lbIh6dxpbBW8fNKU4wFjIE2F2d6+IvRnXDgBVNvWDZ+PIEjg=/body.txt","raw_path":"/api/testing_message_parts/QEVuQwFAEAD6SO5PqWHlAWWad6MRonhtsuZxmCo4xzuFGE0LxKt6KDddCoGCmfx8kJdAWyxONzRlvqhB5mnkHxhicF6lbIh6dxpbBW8fNKU4wFjIE2F2d6+IvRnXDgBVNvWDZ+PIEjg=/body.raw","download_path":"/api/testing_message_parts/QEVuQwFAEAD6SO5PqWHlAWWad6MRonhtsuZxmCo4xzuFGE0LxKt6KDddCoGCmfx8kJdAWyxONzRlvqhB5mnkHxhicF6lbIh6dxpbBW8fNKU4wFjIE2F2d6+IvRnXDgBVNvWDZ+PIEjg=/body.eml","html_source_path":"/api/testing_message_parts/QEVuQwFAEAD6SO5PqWHlAWWad6MRonhtsuZxmCo4xzuFGE0LxKt6KDddCoGCmfx8kJdAWyxONzRlvqhB5mnkHxhicF6lbIh6dxpbBW8fNKU4wFjIE2F2d6+IvRnXDgBVNvWDZ+PIEjg=/body.htmlsource","blacklists_report_info":{"result":"success","domain":"demomailtrap.co","ip":"104.21.25.146","report":[{"name":"BACKSCATTERER","url":"http://www.backscatterer.org/index.php","in_black_list":false},{"name":"BARRACUDA","url":"http://barracudacentral.org/rbl","in_black_list":false},{"name":"Spamrbl
IMP-SPAM","url":"http://antispam.imp.ch/?lng=1","in_black_list":false},{"name":"Wormrbl
IMP-SPAM","url":"http://antispam.imp.ch/?lng=1","in_black_list":false},{"name":"LASHBACK","url":"http://blacklist.lashback.com/","in_black_list":false},{"name":"NIXSPAM","url":"https://www.heise.de/ix/NiX-Spam-DNSBL-and-blacklist-for-download-499637.html","in_black_list":false},{"name":"PSBL","url":"https://psbl.org/","in_black_list":false},{"name":"SORBS-SPAM","url":"http://www.sorbs.net/lookup.shtml","in_black_list":false},{"name":"SPAMCOP","url":"http://spamcop.net/bl.shtml","in_black_list":false},{"name":"TRUNCATE","url":"http://www.gbudb.com/truncate/index.jsp","in_black_list":false}]},"smtp_information":{"ok":false}},{"id":5273671571,"inbox_id":4288340,"subject":"Hello
from Mailtrap","sent_at":"2026-01-04T11:17:40.025-12:00","from_email":"reply@demomailtrap.co","from_name":"","to_email":"alex.d@railsware.com","to_name":"","email_size":677,"is_read":false,"created_at":"2026-01-04T11:17:40.029-12:00","updated_at":"2026-01-04T11:17:40.208-12:00","template_id":0,"template_variables":null,"html_body_size":29,"text_body_size":20,"human_size":"677
Bytes","html_path":"/api/testing_message_parts/QEVuQwFAEAAwMccBahxwpCSHvGNpOmK54J4cObh1DdN9Wixmdbc%2FzsVUvM0ac986GgLvzHzXUmQG0Dp+qyvCjLu62542pDz7ORIh4XQlmVjD7Ey0bN1IPtRIYPQgHXf+ldLmjrn1eEw=/body.html","txt_path":"/api/testing_message_parts/QEVuQwFAEAAwMccBahxwpCSHvGNpOmK54J4cObh1DdN9Wixmdbc%2FzsVUvM0ac986GgLvzHzXUmQG0Dp+qyvCjLu62542pDz7ORIh4XQlmVjD7Ey0bN1IPtRIYPQgHXf+ldLmjrn1eEw=/body.txt","raw_path":"/api/testing_message_parts/QEVuQwFAEAAwMccBahxwpCSHvGNpOmK54J4cObh1DdN9Wixmdbc%2FzsVUvM0ac986GgLvzHzXUmQG0Dp+qyvCjLu62542pDz7ORIh4XQlmVjD7Ey0bN1IPtRIYPQgHXf+ldLmjrn1eEw=/body.raw","download_path":"/api/testing_message_parts/QEVuQwFAEAAwMccBahxwpCSHvGNpOmK54J4cObh1DdN9Wixmdbc%2FzsVUvM0ac986GgLvzHzXUmQG0Dp+qyvCjLu62542pDz7ORIh4XQlmVjD7Ey0bN1IPtRIYPQgHXf+ldLmjrn1eEw=/body.eml","html_source_path":"/api/testing_message_parts/QEVuQwFAEAAwMccBahxwpCSHvGNpOmK54J4cObh1DdN9Wixmdbc%2FzsVUvM0ac986GgLvzHzXUmQG0Dp+qyvCjLu62542pDz7ORIh4XQlmVjD7Ey0bN1IPtRIYPQgHXf+ldLmjrn1eEw=/body.htmlsource","blacklists_report_info":{"result":"success","domain":"demomailtrap.co","ip":"104.21.25.146","report":[{"name":"BACKSCATTERER","url":"http://www.backscatterer.org/index.php","in_black_list":false},{"name":"BARRACUDA","url":"http://barracudacentral.org/rbl","in_black_list":false},{"name":"Spamrbl
IMP-SPAM","url":"http://antispam.imp.ch/?lng=1","in_black_list":false},{"name":"Wormrbl
IMP-SPAM","url":"http://antispam.imp.ch/?lng=1","in_black_list":false},{"name":"LASHBACK","url":"http://blacklist.lashback.com/","in_black_list":false},{"name":"NIXSPAM","url":"https://www.heise.de/ix/NiX-Spam-DNSBL-and-blacklist-for-download-499637.html","in_black_list":false},{"name":"PSBL","url":"https://psbl.org/","in_black_list":false},{"name":"SORBS-SPAM","url":"http://www.sorbs.net/lookup.shtml","in_black_list":false},{"name":"SPAMCOP","url":"http://spamcop.net/bl.shtml","in_black_list":false},{"name":"TRUNCATE","url":"http://www.gbudb.com/truncate/index.jsp","in_black_list":false}]},"smtp_information":{"ok":false}}]'
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

Response body contains real email addresses (PII).

The recorded response contains real employee email addresses:

  • alex.d@railsware.com
  • yahor.vaitsiakhouski@railsware.com

These should be replaced with generic placeholders (e.g., user1@example.com, user2@example.com) to avoid exposing personal information in a public repository. The full name pattern in yahor.vaitsiakhouski is particularly identifiable.

As per coding guidelines: "Act as a data privacy officer. Carefully read all the vcr cassettes with recorded HTTP interactions and try to identify sensitive data that could potentially be recorded. It can be anything from PII to credentials."

🤖 Prompt for AI Agents
In
`@spec/fixtures/vcr_cassettes/Mailtrap_SandboxMessagesAPI/_list/with_parameters/when_last_id_includes_more_messages/maps_response_data_to_SandboxMessage_objects.yml`
around lines 78 - 85, The YAML cassette's recorded JSON in the string field
contains real PII emails (alex.d@railsware.com and
yahor.vaitsiakhouski@railsware.com); locate the large "string" value in the
fixture and replace those addresses with generic placeholders (e.g.,
user1@example.com, user2@example.com) preserving the JSON structure/escaping and
all other fields (ids, paths, timestamps) so tests remain valid; ensure you
update every occurrence of those two addresses inside the "string" value.

Comment on lines +78 to +81
string: '{"id":5273448410,"inbox_id":4288340,"subject":"Hello from Mailtrap","sent_at":"2026-01-04T04:55:50.867-12:00","from_email":"reply@demomailtrap.co","from_name":"","to_email":"alex.b@railsware.com","to_name":"","email_size":691,"is_read":true,"created_at":"2026-01-04T04:55:50.871-12:00","updated_at":"2026-01-04T04:55:56.618-12:00","template_id":0,"template_variables":null,"html_body_size":29,"text_body_size":20,"human_size":"691
Bytes","html_path":"/api/testing_message_parts/QEVuQwFAEABC6ODCerNYpR2yAx7xrEH6%2Ftn5yHfn6zIHYoQ9Ce0V5e9PB+Fy64WrVklr1BTc8WNmeft20TH7pytYbFWIzBx61dHgATF0KMjaFS1whA7iqWor%2FinVxeyLbZbskDsXojw=/body.html","txt_path":"/api/testing_message_parts/QEVuQwFAEABC6ODCerNYpR2yAx7xrEH6%2Ftn5yHfn6zIHYoQ9Ce0V5e9PB+Fy64WrVklr1BTc8WNmeft20TH7pytYbFWIzBx61dHgATF0KMjaFS1whA7iqWor%2FinVxeyLbZbskDsXojw=/body.txt","raw_path":"/api/testing_message_parts/QEVuQwFAEABC6ODCerNYpR2yAx7xrEH6%2Ftn5yHfn6zIHYoQ9Ce0V5e9PB+Fy64WrVklr1BTc8WNmeft20TH7pytYbFWIzBx61dHgATF0KMjaFS1whA7iqWor%2FinVxeyLbZbskDsXojw=/body.raw","download_path":"/api/testing_message_parts/QEVuQwFAEABC6ODCerNYpR2yAx7xrEH6%2Ftn5yHfn6zIHYoQ9Ce0V5e9PB+Fy64WrVklr1BTc8WNmeft20TH7pytYbFWIzBx61dHgATF0KMjaFS1whA7iqWor%2FinVxeyLbZbskDsXojw=/body.eml","html_source_path":"/api/testing_message_parts/QEVuQwFAEABC6ODCerNYpR2yAx7xrEH6%2Ftn5yHfn6zIHYoQ9Ce0V5e9PB+Fy64WrVklr1BTc8WNmeft20TH7pytYbFWIzBx61dHgATF0KMjaFS1whA7iqWor%2FinVxeyLbZbskDsXojw=/body.htmlsource","blacklists_report_info":{"result":"success","domain":"demomailtrap.co","ip":"172.67.134.80","report":[{"name":"BACKSCATTERER","url":"http://www.backscatterer.org/index.php","in_black_list":false},{"name":"BARRACUDA","url":"http://barracudacentral.org/rbl","in_black_list":false},{"name":"Spamrbl
IMP-SPAM","url":"http://antispam.imp.ch/?lng=1","in_black_list":false},{"name":"Wormrbl
IMP-SPAM","url":"http://antispam.imp.ch/?lng=1","in_black_list":false},{"name":"LASHBACK","url":"http://blacklist.lashback.com/","in_black_list":false},{"name":"NIXSPAM","url":"https://www.heise.de/ix/NiX-Spam-DNSBL-and-blacklist-for-download-499637.html","in_black_list":false},{"name":"PSBL","url":"https://psbl.org/","in_black_list":false},{"name":"SORBS-SPAM","url":"http://www.sorbs.net/lookup.shtml","in_black_list":false},{"name":"SPAMCOP","url":"http://spamcop.net/bl.shtml","in_black_list":false},{"name":"TRUNCATE","url":"http://www.gbudb.com/truncate/index.jsp","in_black_list":false}]},"smtp_information":{"ok":false}}'
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

Response body contains a real email address (PII).

The response contains to_email":"alex.b@railsware.com" which should be replaced with a generic placeholder like user@example.com.

As per coding guidelines for VCR cassettes: identify sensitive data including PII.

🤖 Prompt for AI Agents
In
`@spec/fixtures/vcr_cassettes/Mailtrap_SandboxMessagesAPI/_update/maps_response_data_to_SandboxMessage_object.yml`
around lines 78 - 81, The cassette contains real PII in the JSON string under
the "to_email" field (value "alex.b@railsware.com"); open the YAML fixture and
replace that email with a generic placeholder (e.g., "user@example.com")
preserving the surrounding JSON quoting/escaping and any adjacent
whitespace/newlines so the string remains valid; ensure no other occurrences of
the same real address remain in this fixture (search for "alex.b@railsware.com")
and update them similarly.

sent_at: '2026-01-04T04:55:50.867-12:00',
from_email: 'reply@demomailtrap.co',
from_name: '',
to_email: 'alex.b@railsware.com',
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

PII detected: Real email addresses in test expectations.

The test expectations contain what appear to be real email addresses (e.g., alex.b@railsware.com). Consider using placeholder emails (e.g., user@example.com) to avoid exposing real user data. The same applies to other occurrences on lines 142 and 363.

🤖 Prompt for AI Agents
In `@spec/mailtrap/sandbox_messages_api_spec.rb` at line 25, Replace the real
email literal 'alex.b@railsware.com' used as the to_email expectation with a
non-PII placeholder (e.g., 'user@example.com') and do the same for the other
occurrences of that literal elsewhere in the spec; locate the string by
searching for the to_email key and the exact email literal (and any identical
string occurrences) in sandbox_messages_api_spec and swap them for a placeholder
or generated test email (e.g., using a factory or Faker) so tests no longer
contain real PII.

Comment on lines +391 to +395
it 'returns deleted project id' do
expect(delete[:id]).to eq(sandbox_message_id)
end

context 'when project does not exist' do
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

Terminology inconsistency: "project" should be "message".

The test description and context reference "project" but this API deals with sandbox messages.

📝 Proposed fix
-    it 'returns deleted project id' do
+    it 'returns deleted message id' do
       expect(delete[:id]).to eq(sandbox_message_id)
     end

-    context 'when project does not exist' do
+    context 'when message does not exist' do
📝 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
it 'returns deleted project id' do
expect(delete[:id]).to eq(sandbox_message_id)
end
context 'when project does not exist' do
it 'returns deleted message id' do
expect(delete[:id]).to eq(sandbox_message_id)
end
context 'when message does not exist' do
🤖 Prompt for AI Agents
In `@spec/mailtrap/sandbox_messages_api_spec.rb` around lines 391 - 395, Update
the test descriptions to use consistent terminology: replace occurrences of
"project" with "message" in the example block that asserts deletion and in the
following context; specifically update the it description that currently reads
'returns deleted project id' to 'returns deleted message id' and change the
context label 'when project does not exist' to 'when message does not exist' so
the text matches the API under test (references: the example using delete[:id]
and sandbox_message_id).

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