Skip to content

Add Inbound Email v2 API support#117

Open
mklocek wants to merge 6 commits into
mainfrom
inbound-v2
Open

Add Inbound Email v2 API support#117
mklocek wants to merge 6 commits into
mainfrom
inbound-v2

Conversation

@mklocek

@mklocek mklocek commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Motivation

Add SDK support for the Inbound Email v2 API, and surface the inbound-related additions on existing endpoints. Mirrors the OpenAPI spec.

Changes

  • Inbound Email API — new token-scoped API classes (no account_id required):
    • InboundFoldersAPI — list, get, create, update, delete
    • InboundInboxesAPI (folder-scoped) — list, get, create (hosted & custom-domain), update, delete
    • InboundMessagesAPI (inbox-scoped) — list (cursor-paginated), get, delete, reply, reply_all, forward
    • InboundThreadsAPI (inbox-scoped) — list (cursor-paginated), get, delete
  • DTOs for folders, inboxes, messages (with typed attachments), threads (with typed thread messages), the paginated list responses, and the send result.
  • Email LogsEmailLogMessage gains rfc_message_id, in_reply_to, references, thread_id.
  • Sending DomainsSendingDomain gains inbound_enabled, inbound_verified.
  • Specs (DTO + VCR-based API specs), per-resource examples, and README entries.

Split into reviewable commits: existing-endpoint fields, DTOs, API classes, specs, cassettes, examples + README.

mklocek added 6 commits July 24, 2026 06:28
EmailLogMessage gains rfc_message_id, in_reply_to, references, and thread_id. SendingDomain gains inbound_enabled and inbound_verified.

Specs and VCR cassettes for both endpoints updated to cover the new fields. Cassette bodies were hand-edited rather than re-recorded, to avoid churning live resources and to keep the curated email-log fixture stable.
Struct DTOs for folders, inboxes, messages (with typed attachments), threads (with typed thread messages), the paginated list responses, and the reply/forward send result. Matches the OpenAPI spec.
InboundFoldersAPI (CRUD), InboundInboxesAPI (folder-scoped CRUD), InboundMessagesAPI (list/get/delete + reply/reply_all/forward) and InboundThreadsAPI (list/get/delete), wired into lib/mailtrap.rb.

Inbound is token-scoped, so these override initialize to take just a client (no account_id). Nested attachments and thread messages are built into typed structs, mirroring EmailLogsAPI's event handling.
Offline DTO specs for all inbound structs, and VCR-based API specs for folders, inboxes, messages and threads (happy paths plus ArgumentError and Mailtrap::Error cases). Cassettes to be recorded against the live API.
Cassettes recorded against the live API for folders, inboxes, messages and threads, with the spec let IDs pointed at real recorded resources. Response bodies anonymised: the sending domain is replaced with example.com and the live inbox addresses with non-real slugs; the public inbound-mailtrap.io domain and resource IDs are kept, consistent with the existing cassettes.
Per-resource example scripts for inbound folders, inboxes, messages (incl. reply/reply_all/forward) and threads, linked from a new Inbound Email section in the README.
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Added inbound email support to the Ruby client through folder, inbox, message, and thread APIs. The change includes typed DTOs, CRUD and messaging operations, cursor pagination, attachment mapping, examples, compatibility fields, VCR fixtures, and RSpec coverage.

Changes

Inbound email functionality

Layer / File(s) Summary
Shared contracts and client wiring
lib/mailtrap.rb, lib/mailtrap/*, README.md, spec/mailtrap/email_logs_api_spec.rb, spec/mailtrap/sending_domains_api_spec.rb
Adds inbound API loading, pagination/send-result DTOs, RFC threading fields on email-log messages, inbound sending-domain fields, and README example links.
Folder and inbox management
lib/mailtrap/inbound_folder.rb, lib/mailtrap/inbound_folders_api.rb, lib/mailtrap/inbound_inbox.rb, lib/mailtrap/inbound_inboxes_api.rb, examples/inbound_folders_api.rb, examples/inbound_inboxes_api.rb, spec/mailtrap/inbound_*folder*, spec/mailtrap/inbound_*inbox*, spec/fixtures/vcr_cassettes/Mailtrap_InboundFoldersAPI/*, spec/fixtures/vcr_cassettes/Mailtrap_InboundInboxesAPI/*
Implements folder and folder-scoped inbox CRUD operations with option validation, typed mapping, error handling, runnable examples, and recorded request fixtures.
Message retrieval and sending workflows
lib/mailtrap/inbound_attachment.rb, lib/mailtrap/inbound_message.rb, lib/mailtrap/inbound_messages_api.rb, lib/mailtrap/inbound_messages_list_response.rb, lib/mailtrap/inbound_send_result.rb, examples/inbound_messages_api.rb, spec/mailtrap/inbound_message*, spec/mailtrap/inbound_messages*, spec/fixtures/vcr_cassettes/Mailtrap_InboundMessagesAPI/*
Adds cursor-paginated message listing, message retrieval and deletion, attachment conversion, reply/reply-all/forward actions, validation, examples, and VCR-backed tests.
Thread retrieval and lifecycle
lib/mailtrap/inbound_thread.rb, lib/mailtrap/inbound_thread_message.rb, lib/mailtrap/inbound_threads_api.rb, lib/mailtrap/inbound_threads_list_response.rb, examples/inbound_threads_api.rb, spec/mailtrap/inbound_thread*, spec/mailtrap/inbound_threads*, spec/fixtures/vcr_cassettes/Mailtrap_InboundThreadsAPI/*
Adds cursor-paginated thread listing, thread retrieval with nested typed messages and attachments, deletion, error handling, examples, and VCR-backed tests.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant InboundThreadsAPI
  participant MailtrapAPI
  Client->>InboundThreadsAPI: list or get inbox threads
  InboundThreadsAPI->>MailtrapAPI: GET threads endpoint with optional last_id
  MailtrapAPI-->>InboundThreadsAPI: thread payload with nested messages
  InboundThreadsAPI-->>Client: InboundThreadsListResponse or InboundThread
Loading

Possibly related PRs

Suggested reviewers: i7an, igordobryn

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 65.38% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title is concise and accurately summarizes the main change: adding Inbound Email v2 API support.
Description check ✅ Passed It includes the required Motivation and Changes sections and is specific; the test and images sections are omitted but not critical.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch inbound-v2

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (1)
spec/mailtrap/inbound_thread_message_spec.rb (1)

16-25: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Expand DTO tests to cover the complete public contracts.

  • spec/mailtrap/inbound_thread_message_spec.rb#L16-L25: assert RFC/threading headers, addresses, bodies, attachments, and bounced_at.
  • spec/mailtrap/inbound_thread_spec.rb#L20-L32: assert last_received_at, last_sent_at, and last_message_id.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@spec/mailtrap/inbound_thread_message_spec.rb` around lines 16 - 25, Expand
the DTO expectations in spec/mailtrap/inbound_thread_message_spec.rb lines 16-25
to cover RFC/threading headers, addresses, bodies, attachments, and bounced_at
in addition to the existing attributes. Also update
spec/mailtrap/inbound_thread_spec.rb lines 20-32 to assert last_received_at,
last_sent_at, and last_message_id, using the fixture values established by each
DTO.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/inbound_messages_api.rb`:
- Around line 14-15: Update the message retrieval flow around messages.get to
check whether page.data contains a first message before accessing its id; handle
an empty inbox gracefully and only perform the remaining message operations when
a message is available.

In `@examples/inbound_threads_api.rb`:
- Around line 14-19: Guard the example calls to threads.get and threads.delete
by checking that page.data contains a first thread before dereferencing
page.data.first.id. Preserve the existing get/delete behavior when the page is
non-empty, and skip both operations for an empty page.

In
`@spec/fixtures/vcr_cassettes/Mailtrap_InboundInboxesAPI/_list/maps_response_data_to_InboundInbox_objects.yml`:
- Line 71: Replace the recorded `@inbound-mailtrap.io` addresses with reserved
synthetic/local addresses in both fixture entries:
spec/fixtures/vcr_cassettes/Mailtrap_InboundInboxesAPI/_list/maps_response_data_to_InboundInbox_objects.yml:71
and
spec/fixtures/vcr_cassettes/Mailtrap_InboundInboxesAPI/_update/maps_response_data_to_an_InboundInbox_object.yml:71.
Keep the fixture response structure and inbox fields unchanged.

In
`@spec/fixtures/vcr_cassettes/Mailtrap_InboundMessagesAPI/_get/maps_response_data_to_an_InboundMessage_with_attachments.yml`:
- Around line 73-80: Sanitize the cassette response data by replacing the S3
presigned raw_message_url and raw_message_expires_at with deterministic redacted
values, and replace opaque unsubscribe and open-tracking URLs in html_body and
text_body with fixed placeholders. Remove the exposed credentials, signatures,
tokens, and PII from repository history, and invalidate the existing capture if
it remains usable.

In
`@spec/fixtures/vcr_cassettes/Mailtrap_InboundThreadsAPI/_get/maps_response_data_to_an_InboundThread_with_messages.yml`:
- Around line 72-79: Across the three listed fixture files and their indicated
ranges, address the repeated tickets-1a2b3c4d@inbound-mailtrap.io identity:
either document and test that it is intentional shared test setup, or replace it
consistently in every affected VCR JSON body with a reserved synthetic mailbox
address, preserving the fixture data structure and references.

---

Nitpick comments:
In `@spec/mailtrap/inbound_thread_message_spec.rb`:
- Around line 16-25: Expand the DTO expectations in
spec/mailtrap/inbound_thread_message_spec.rb lines 16-25 to cover RFC/threading
headers, addresses, bodies, attachments, and bounced_at in addition to the
existing attributes. Also update spec/mailtrap/inbound_thread_spec.rb lines
20-32 to assert last_received_at, last_sent_at, and last_message_id, using the
fixture values established by each DTO.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4ab09e27-0db0-4c1a-bd3e-24e1faa00513

📥 Commits

Reviewing files that changed from the base of the PR and between c5831c1 and a51f13f.

📒 Files selected for processing (68)
  • README.md
  • examples/inbound_folders_api.rb
  • examples/inbound_inboxes_api.rb
  • examples/inbound_messages_api.rb
  • examples/inbound_threads_api.rb
  • lib/mailtrap.rb
  • lib/mailtrap/email_log_message.rb
  • lib/mailtrap/inbound_attachment.rb
  • lib/mailtrap/inbound_folder.rb
  • lib/mailtrap/inbound_folders_api.rb
  • lib/mailtrap/inbound_inbox.rb
  • lib/mailtrap/inbound_inboxes_api.rb
  • lib/mailtrap/inbound_message.rb
  • lib/mailtrap/inbound_messages_api.rb
  • lib/mailtrap/inbound_messages_list_response.rb
  • lib/mailtrap/inbound_send_result.rb
  • lib/mailtrap/inbound_thread.rb
  • lib/mailtrap/inbound_thread_message.rb
  • lib/mailtrap/inbound_threads_api.rb
  • lib/mailtrap/inbound_threads_list_response.rb
  • lib/mailtrap/sending_domain.rb
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailLogsAPI/_get/maps_response_data_to_EmailLogMessage_with_events_and_raw_message_url.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailLogsAPI/_list/maps_response_data_to_EmailLogsListResponse_with_messages.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_InboundFoldersAPI/_create/maps_response_data_to_an_InboundFolder_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_InboundFoldersAPI/_delete/returns_nil.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_InboundFoldersAPI/_delete/when_the_folder_does_not_exist/raises_an_error.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_InboundFoldersAPI/_get/maps_response_data_to_an_InboundFolder_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_InboundFoldersAPI/_get/when_the_folder_does_not_exist/raises_an_error.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_InboundFoldersAPI/_list/maps_response_data_to_InboundFolder_objects.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_InboundFoldersAPI/_update/maps_response_data_to_an_InboundFolder_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_InboundInboxesAPI/_create/maps_response_data_to_an_InboundInbox_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_InboundInboxesAPI/_delete/returns_nil.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_InboundInboxesAPI/_delete/when_the_inbox_does_not_exist/raises_an_error.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_InboundInboxesAPI/_get/maps_response_data_to_an_InboundInbox_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_InboundInboxesAPI/_get/when_the_inbox_does_not_exist/raises_an_error.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_InboundInboxesAPI/_list/maps_response_data_to_InboundInbox_objects.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_InboundInboxesAPI/_update/maps_response_data_to_an_InboundInbox_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_InboundMessagesAPI/_delete/returns_nil.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_InboundMessagesAPI/_forward/maps_response_data_to_an_InboundSendResult.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_InboundMessagesAPI/_get/maps_response_data_to_an_InboundMessage_with_attachments.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_InboundMessagesAPI/_get/when_the_message_does_not_exist/raises_an_error.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_InboundMessagesAPI/_list/maps_response_data_to_an_InboundMessagesListResponse.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_InboundMessagesAPI/_list/with_pagination_cursor/passes_last_id_and_returns_a_page.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_InboundMessagesAPI/_reply/maps_response_data_to_an_InboundSendResult.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_InboundMessagesAPI/_reply_all/maps_response_data_to_an_InboundSendResult.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_InboundThreadsAPI/_delete/returns_nil.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_InboundThreadsAPI/_delete/when_the_thread_does_not_exist/raises_an_error.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_InboundThreadsAPI/_get/maps_response_data_to_an_InboundThread_with_messages.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_InboundThreadsAPI/_get/when_the_thread_does_not_exist/raises_an_error.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_InboundThreadsAPI/_list/maps_response_data_to_an_InboundThreadsListResponse.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_InboundThreadsAPI/_list/with_pagination_cursor/passes_last_id_and_returns_a_page.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_SendingDomainsAPI/_create/maps_response_data_to_sending_domain_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_SendingDomainsAPI/_get/maps_response_data_to_SendingDomain_object.yml
  • spec/mailtrap/email_logs_api_spec.rb
  • spec/mailtrap/inbound_attachment_spec.rb
  • spec/mailtrap/inbound_folder_spec.rb
  • spec/mailtrap/inbound_folders_api_spec.rb
  • spec/mailtrap/inbound_inbox_spec.rb
  • spec/mailtrap/inbound_inboxes_api_spec.rb
  • spec/mailtrap/inbound_message_spec.rb
  • spec/mailtrap/inbound_messages_api_spec.rb
  • spec/mailtrap/inbound_messages_list_response_spec.rb
  • spec/mailtrap/inbound_send_result_spec.rb
  • spec/mailtrap/inbound_thread_message_spec.rb
  • spec/mailtrap/inbound_thread_spec.rb
  • spec/mailtrap/inbound_threads_api_spec.rb
  • spec/mailtrap/inbound_threads_list_response_spec.rb
  • spec/mailtrap/sending_domains_api_spec.rb

Comment thread examples/inbound_messages_api.rb
Comment thread examples/inbound_threads_api.rb
Comment on lines +73 to +80
test 2026-07-24T05:58:20Z","headers":{"mime-version":"1.0","return-path":""},"size":4210,"html_size":968,"text_size":351,"received_at":"2026-07-24T05:58:21.528Z","rfc_message_id":"<ace7a270-8724-11f1-0040-f14113a7558c@example.com>","in_reply_to":null,"references":[],"thread_id":"1871574677878845504","attachments":[],"raw_message_url":"https://mailsend-us-inbound-mail-bodies.s3.amazonaws.com/raw/c3b/292/1871574677877796928/raw.eml?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=ASIAWYSURJ3UHLCG6JQ7%2F20260724%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20260724T060123Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEDUaCXVzLWVhc3QtMSJHMEUCIH9u%2BHOb4l%2B1ZFnNCLWzphITJt6I3taUKyf8nSVY4V8JAiEA4es2wKLun0kAbwWXIMleOdvbevn%2Bwzq%2FMent%2F02hcvUqggQI%2Fv%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FARAAGgw0NjUxMDc1MDQ4NzIiDBxJ01lkJNHlF%2Fn7airWA7vGXuAMz2QikJKrPvdt1cA%2F%2FvhrbmmZOqDUhsUc0mw9aFKe6ZwtnsxKbZABqlNIPX7DZsxvsXzyzu2icMGAHw%2F%2FHZPdtsy7zOd7F8jyU%2BVdvTU2TNVzpm%2FReJZQ3XZaZAwhBjWgxgPQS0Mf5VErdnZFb4M73V5mHOQR8qUmLByf2YKllmnDBd0l6rP6TLxJB2sUnqZBW0kjP%2Fw1H65sfiXtJgkL%2FiYtDLKoGHLrcACiGwSWWd1WEEQWNRUCHfVD5pTGPFxDhbpU9NWPBCrXEm7lhNCjQjw8GL6a7ZYJHFOxwLRm3OSSTsLg%2FMTH69oq3mEO8JPwk3hJEBWeTTtgSCpWhVz1stj2UHQeVbr0EQpOgyrPaZ%2BVybOL1zFsVXSRCNAstDi6II3BkOCPxWw6D89DLF3XPyLh6BQrwC%2BsNd6%2BpRqbsJD3MpFZaWzasTGvRho09r9FJx2HMinqjch3uQyYkIkjQ938q9c41OYrccqrvLpSGKjSFdbLKP8syMeZJzTA%2Bxh9Wmz0aBcl0BvQgFj%2BjFSQrZpCRirxzMHcpQi%2Bumc8bE4CW%2BHTI%2BqXNkIEO%2FpuIOEkLbZ8UO0FgKUnqbEtJOPTo9Hv13jhbuUW%2BokD4DMbUHVuMLjdi9MGOqUBJzvmV5%2FoGML5LYyb6ldBzYoIpt8aFoDBxxREfwePzvEbgOXaiBcQN372D6lkL1ocvWa3%2FEWyVOmu%2FpQ%2FAwcjkvOO7R9BMiMcmw%2Bn0ko7sRbi9Nd4UbUM7CtcvR5qGfUYw2kOleqhnKO2sMwqhCG3jawnBKjBCB4rqwxZa01ZuaUeggq%2FGTYqDFsAnBubAXNafUT%2FceS%2FlNQAWoV7s8B20hLtc%2BXR&X-Amz-SignedHeaders=host&X-Amz-Signature=37287b9020f3a3357197d518c874b2b0c9aa5b713e16b07e69d79f14ae43d78b","raw_message_expires_at":"2026-07-24T07:01:23.851Z","html_body":"<p>Hello
from the inbound test script.</p><table style=\"font-family:Helvetica,Arial,sans-serif;font-size:12px;color:#000;width:95%;margin:32px
auto 24px;border:none;border-top:1px solid #eee;\" cellspacing=\"20\" cellpadding=\"0\">\r\n <tr>\r\n <td
style=\"text-align:center;\">\r\n <a href=\"https://u3078320257.t.mailtrap.net/out/ntgNV6MS9IBzPmwVhf1_fKExno65VCmBSrdL4c5tlwH2CO9coPgYNBN3KERVdgaihvzBmS2k7E.ssfokaA1DxvdSzEJPRFJP9xyyzICbmf_vKcZxqeWCQRagJajz6iQpPCXv0W3YnCdg_mk2PeeTm_r1Oe5cT.Z3UKIou.V5wsdHbfhVJSfyZhXESOk_p5m4wUqrpFt9Hfj5fLrwi83tgWm0--k5ABeX5qcndWqpZZ--Oi0cYr28qmLNe0MqbS9i1A~~\"
style=\"font-weight:700;color:#000;\">Unsubscribe</a>\r\n </td>\r\n </tr>\r\n</table>\r\n<img
src=\"https://u3078320257.t.mailtrap.net/op/guTBjLEJorUAGJTKi1xOZn.ysPnvx93fOcnqfV5aLlTKKRtTF7Ov9ztc2x2u3BenuHpj1T46PSZP2d.rWDx3w4DuuQfp7Cr4s8joXgsu1BJVBCwhjkfu5h7tlQ5P1zjvu3BxQxSgQmNsniixpsb5mBW4nio.ZHZ4KvdMeAY~--aR0KP0qsZhMhvURM--6Xp0ANaGi1jyE3lCeKyi7w~~.gif\"
width=\"1\" height=\"1\" alt=\"\" />","text_body":"Hello from the inbound
test script.\r\n\r\nUnsubscribe: https://u3078320257.t.mailtrap.net/out/ntgNV6MS9IBzPmwVhf1_fKExno65VCmBSrdL4c5tlwH2CO9coPgYNBN3KERVdgaihvzBmS2k7E.ssfokaA1DxvdSzEJPRFJP9xyyzICbmf_vKcZxqeWCQRagJajz6iQpPCXv0W3YnCdg_mk2PeeTm_r1Oe5cT.Z3UKIou.V5wsdHbfhVJSfyZhXESOk_p5m4wUqrpFt9Hfj5fLrwi83tgWm0--k5ABeX5qcndWqpZZ--Oi0cYr28qmLNe0MqbS9i1A~~"}'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Redact the signed URL and tracking tokens before committing this cassette.

The response records an S3 presigned URL containing X-Amz-Credential, X-Amz-Security-Token, and X-Amz-Signature, plus opaque unsubscribe/open-tracking URLs in the HTML and text bodies. Replace these with deterministic redacted values and scrub the exposed material from repository history; invalidate the capture if it is still usable. As per path instructions, VCR cassettes must not retain recorded PII or credentials.

🧰 Tools
🪛 Checkov (3.3.8)

[high] 73-74: AWS Access Key

(CKV_SECRET_2)


[high] 73-74: IBM Cloud IAM Key

(CKV_SECRET_7)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@spec/fixtures/vcr_cassettes/Mailtrap_InboundMessagesAPI/_get/maps_response_data_to_an_InboundMessage_with_attachments.yml`
around lines 73 - 80, Sanitize the cassette response data by replacing the S3
presigned raw_message_url and raw_message_expires_at with deterministic redacted
values, and replace opaque unsubscribe and open-tracking URLs in html_body and
text_body with fixed placeholders. Remove the exposed credentials, signatures,
tokens, and PII from repository history, and invalidate the existing capture if
it remains usable.

Sources: Path instructions, Linters/SAST tools

Comment on lines +72 to +79
string: '{"id":"1871574677878845504","subject":"Inbound test 2026-07-24T05:58:20Z","message_count":2,"size":936,"first_message_at":"2026-07-24T05:58:21.528Z","last_received_at":"2026-07-24T05:58:21.528Z","last_sent_at":"2026-07-24T06:01:47.114Z","last_activity_at":"2026-07-24T06:01:47.114Z","last_message_id":"1871574893450792960","senders":["tickets-1a2b3c4d@inbound-mailtrap.io"],"recipients":["Inbound
Tester <sender@example.com>"],"attachments":[],"messages":[{"visibility_status":"available","direction":"outbound","id":"23594a30-8725-11f1-0000-f1b5e753a178","message_group_id":null,"subject":"Re:
Inbound test 2026-07-24T05:58:20Z","rfc_message_id":"<23594a30-8725-11f1-0000-f1b5e753a178@inbound-mailtrap.io>","in_reply_to":"<ace7a270-8724-11f1-0040-f14113a7558c@example.com>","references":["<ace7a270-8724-11f1-0040-f14113a7558c@example.com>"],"from":"tickets-1a2b3c4d@inbound-mailtrap.io","to":["Inbound
Tester <sender@example.com>"],"cc":[],"bcc":[],"reply_to":"","created_at":"2026-07-24T06:01:40.178Z","email_size":470,"text_body":"Thanks
for reaching out.","html_body":null,"attachments":[],"delivery_status":"enqueued","delivered_at":null,"bounced_at":null},{"visibility_status":"available","direction":"outbound","id":"277b7ca0-8725-11f1-0000-f12f7dc93be2","message_group_id":null,"subject":"Re:
Inbound test 2026-07-24T05:58:20Z","rfc_message_id":"<277b7ca0-8725-11f1-0000-f12f7dc93be2@inbound-mailtrap.io>","in_reply_to":"<ace7a270-8724-11f1-0040-f14113a7558c@example.com>","references":["<ace7a270-8724-11f1-0040-f14113a7558c@example.com>"],"from":"tickets-1a2b3c4d@inbound-mailtrap.io","to":["Inbound
Tester <sender@example.com>"],"cc":[],"bcc":[],"reply_to":"","created_at":"2026-07-24T06:01:47.114Z","email_size":466,"text_body":"Looping
everyone in.","html_body":null,"attachments":[],"delivery_status":"enqueued","delivered_at":null,"bounced_at":null}]}'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== referenced cassette snippets =="
for f in \
  spec/fixtures/vcr_cassettes/Mailtrap_InboundThreadsAPI/_get/maps_response_data_to_an_InboundThread_with_messages.yml \
  spec/fixtures/vcr_cassettes/Mailtrap_InboundThreadsAPI/_list/maps_response_data_to_an_InboundThreadsListResponse.yml \
  spec/fixtures/vcr_cassettes/Mailtrap_InboundThreadsAPI/_list/with_pagination_cursor/passes_last_id_and_returns_a_page.yml
do
  echo "--- $f ---"
  sed -n '1,120p' "$f" | nl -ba
done

echo
echo "== search for the mailbox address =="
rg -n "tickets-1a2b3c4d@inbound-mailtrap\.io|inbound-mailtrap\.io|Inbound Tester|sender@example\.com" spec config . -g '!vendor/**' -g '!node_modules/**' -g '!dist/**' -g '!build/**' || true

echo
echo "== git status and relevant files count =="
git status --short
git ls-files 'spec/fixtures/vcr_cassettes/**.yml' '**/Mailtrap*' | head -200

Repository: mailtrap/mailtrap-ruby

Length of output: 361


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== referenced cassette snippets =="
for f in \
  spec/fixtures/vcr_cassettes/Mailtrap_InboundThreadsAPI/_get/maps_response_data_to_an_InboundThread_with_messages.yml \
  spec/fixtures/vcr_cassettes/Mailtrap_InboundThreadsAPI/_list/maps_response_data_to_an_InboundThreads ListResponse.yml \
  spec/fixtures/vcr_cassettes/Mailtrap_InboundThreadsAPI/_list/with_pagination_cursor/passes_last_id_and_returns_a_page.yml
do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    sed -n '1,120p' "$f"
  else
    echo "MISSING: $f"
  fi
done

echo
echo "== exact mailbox address occurrences =="
rg -n "tickets-1a2b3c4d@inbound-mailtrap\.io|inbound-mailtrap\.io|Inbound Tester|sender@example\.com" . || true

echo
echo "== list referenced files =="
for f in \
  spec/fixtures/vcr_cassettes/Mailtrap_InboundThreadsAPI/_get/maps_response_data_to_an_InboundThread_with_messages.yml \
  spec/fixtures/vcr_cassettes/Mailtrap_InboundThreadsAPI/_list/maps_response_data_to_an_InboundThreadsListResponse.yml \
  spec/fixtures/vcr_cassettes/Mailtrap_InboundThreadsAPI/_list/with_pagination_cursor/passes_last_id_and_returns_a_page.yml
do
  [ -f "$f" ] && echo "FOUND: $f" || echo "MISSING: $f"
done

Repository: mailtrap/mailtrap-ruby

Length of output: 16106


Redact or verify the repeated mailbox identity across the thread fixtures.

tickets-1a2b3c4d@inbound-mailtrap.io is a non-obvious, shared mailbox address recorded in the VCR JSON bodies. If this is test setup, document/test against that identity; otherwise replace it with a reserved synthetic address across the affected response bodies.

  • spec/fixtures/vcr_cassettes/Mailtrap_InboundThreadsAPI/_get/maps_response_data_to_an_InboundThread_with_messages.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_InboundThreadsAPI/_list/maps_response_data_to_an_InboundThreadsListResponse.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_InboundThreadsAPI/_list/with_pagination_cursor/passes_last_id_and_returns_a_page.yml
📍 Affects 3 files
  • spec/fixtures/vcr_cassettes/Mailtrap_InboundThreadsAPI/_get/maps_response_data_to_an_InboundThread_with_messages.yml#L72-L79 (this comment)
  • spec/fixtures/vcr_cassettes/Mailtrap_InboundThreadsAPI/_list/maps_response_data_to_an_InboundThreadsListResponse.yml#L71-L72
  • spec/fixtures/vcr_cassettes/Mailtrap_InboundThreadsAPI/_list/with_pagination_cursor/passes_last_id_and_returns_a_page.yml#L71-L72
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@spec/fixtures/vcr_cassettes/Mailtrap_InboundThreadsAPI/_get/maps_response_data_to_an_InboundThread_with_messages.yml`
around lines 72 - 79, Across the three listed fixture files and their indicated
ranges, address the repeated tickets-1a2b3c4d@inbound-mailtrap.io identity:
either document and test that it is intentional shared test setup, or replace it
consistently in every affected VCR JSON body with a reserved synthetic mailbox
address, preserving the fixture data structure and references.

Source: Path instructions

# @attr_reader download_url [String, nil] Signed URL to download the attachment (only when fetched by ID)
# @attr_reader download_url_expires_at [String, nil] When the download URL expires (only when fetched by ID)
# rubocop:disable Lint/StructNewOverride -- +size+ is an API field that shadows Struct#size
InboundAttachment = Struct.new(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

BTW i wonder if it would make more sense to use Data
... Data doesn't even have size :)

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