Skip to content

Batch Sending Api #53

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open

Conversation

sarco3t
Copy link
Contributor

@sarco3t sarco3t commented Jun 30, 2025

Motivation

Integrate Batch Sending API to support sending multiple emails in a single request, improving efficiency and reducing API calls.

Changes

  • Added send_batch to Mailtrap::Client
  • Implemented batch sending support for all modes (transactional, bulk, sandbox)
  • Added comprehensive test coverage for batch sending functionality
  • Added example file with batch sending scenario

How to test

rspec

or set yout api key

mail = Mailtrap::Mail::Base.new(
  from: { email: '[email protected]', name: 'Mailtrap Test' }
)

client = Mailtrap::Client.new(api_key: 'your-api-key')
client.send_batch(mail, [
                    Mailtrap::Mail::Base.new(
                      to: [
                        { email: '[email protected]', name: 'Your name' }
                      ]
                    ),
                    Mailtrap::Mail::Base.new(
                      to: [
                        { email: '[email protected]', name: 'Your name' }
                      ]
                    )
                  ])

# other examples can be found in examples/batch.rb

Summary by CodeRabbit

  • New Features

    • Added batch email sending support, enabling sending multiple personalized emails in one request.
    • Introduced helper methods to build batch emails from templates or custom content without recipient details.
    • Provided a new example script demonstrating batch email sending with personalization, attachments, custom metadata, and templates.
  • Bug Fixes

    • Enhanced argument validation and error handling for batch email sending, including mode compatibility checks.
  • Tests

    • Added extensive tests covering batch email sending success and API error scenarios.
    • Included new test fixtures to support reliable testing of batch email operations in different modes.

Copy link

coderabbitai bot commented Jun 30, 2025

"""

Walkthrough

This update introduces batch email sending capabilities to the Mailtrap Ruby client. It adds new methods for constructing batch emails, a send_batch method in the client, comprehensive tests, VCR fixtures for batch API interactions, and a usage example script. Argument validation is refactored for clarity.

Changes

Files / Paths Change Summary
lib/mailtrap/client.rb Adds send_batch method and refactors argument validation into a private method.
lib/mailtrap/mail.rb Adds batch_base_from_template and batch_base_from_content methods for batch mail construction.
spec/mailtrap/client_spec.rb Adds and updates tests for batch sending, including error handling and successful scenarios.
examples/batch.rb Adds a new example script demonstrating batch email sending.
spec/fixtures/vcr_cassettes/Mailtrap_Client/_send_batch/... Adds VCR cassettes for various batch sending scenarios (bulk, sandbox, template, errors).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant MailtrapClient
    participant MailtrapAPI

    User->>MailtrapClient: Initialize with api_key (and options)
    User->>MailtrapClient: send_batch(base_mail, recipient_mails)
    MailtrapClient->>MailtrapAPI: POST /api/batch with base_mail and recipient_mails
    MailtrapAPI-->>MailtrapClient: Batch send response (success/errors)
    MailtrapClient-->>User: Returns API response
Loading

Possibly related issues

Suggested reviewers

  • mklocek
  • ayanko

Poem

In a warren of code, a new batch hops in,
Sending emails in groups, let the bulk fun begin!
With templates and tests, and fixtures galore,
Our client now batches—just open the door.
From sandbox to bulk, each message takes flight,
Rabbits rejoice—batch sending feels right! 🐇✉️
"""


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eeff166 and fe7b7b8.

📒 Files selected for processing (2)
  • lib/mailtrap/client.rb (4 hunks)
  • lib/mailtrap/mail.rb (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • lib/mailtrap/client.rb
  • lib/mailtrap/mail.rb
✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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: 3

🧹 Nitpick comments (7)
examples/batch.rb (2)

10-15: Prefer Base64.strict_encode64 to avoid soft line-breaks

Base64.encode64 inserts \n every 60 chars, which can mangle attachments or exceed Mailtrap’s size limits.

-      content: Base64.encode64('Attachment content'), # base64 encoded content or IO string
+      content: Base64.strict_encode64('Attachment content'), # single-line base64

24-24: Use ENV for secrets in examples

Hard-coding even a placeholder key encourages bad habits.

-client = Mailtrap::Client.new(api_key: 'your-api-key')
+client = Mailtrap::Client.new(api_key: ENV.fetch('MAILTRAP_API_KEY'))
lib/mailtrap/client.rb (2)

67-70: Consider optimizing the validation logic.

The all? method call could be more efficient by using is_a? with the method argument.

-      unless requests.all?(Mail::Base)
-        raise ArgumentError,
-              'requests should be an array of Mailtrap::Mail::Base objects'
-      end
+      unless requests.all? { |request| request.is_a?(Mail::Base) }
+        raise ArgumentError,
+              'requests should be an array of Mailtrap::Mail::Base objects'
+      end

31-55: Note: Cyclomatic complexity increase due to additional validation.

The pipeline failure indicates that the initialize method's cyclomatic complexity has exceeded the threshold (8/7). While the new validation on line 42 is necessary and follows the existing pattern, consider refactoring the initialization logic to reduce complexity.

Consider extracting validation logic into a separate private method:

private

def validate_configuration!(sandbox, bulk, inbox_id)
  raise ArgumentError, 'bulk mode is not applicable for sandbox API' if sandbox && bulk
  raise ArgumentError, 'inbox_id is required for sandbox API' if sandbox && inbox_id.nil?
end

Would you like me to generate a refactored version of the initialize method to address the complexity issue?

spec/fixtures/vcr_cassettes/Mailtrap_Client/_send_batch/in_sandbox_mode/successfully_sends_a_batch_of_emails.yml (1)

8-10: Use reserved example domains to avoid accidental traffic

The cassette payload embeds [email protected] and [email protected]. While harmless, RFC 2606 recommends example.com, example.org, or example.net for documentation & fixtures to guarantee they never resolve in production.
Switching keeps us 100 % safe from stray e-mails if the cassette is ever replayed against live endpoints.

spec/fixtures/vcr_cassettes/Mailtrap_Client/_send_batch/with_template/successfully_sends_a_batch_of_emails_with_template.yml (1)

8-9: Consider RFC-2606 example domains in fixture addresses

Same note as the sandbox cassette—replace real-looking domains ([email protected], [email protected]) with example.com variants to remove any chance of misrouting.

spec/fixtures/vcr_cassettes/Mailtrap_Client/_send_batch/in_bulk_mode/successfully_sends_a_batch_of_emails.yml (1)

8-9: Prefer placeholder domains reserved for docs

For consistency and zero-risk, substitute [email protected] / [email protected] with example.com addresses in the recorded JSON string.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9429154 and 5814deb.

📒 Files selected for processing (7)
  • examples/batch.rb (1 hunks)
  • lib/mailtrap/client.rb (4 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_Client/_send_batch/in_bulk_mode/successfully_sends_a_batch_of_emails.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_Client/_send_batch/in_sandbox_mode/successfully_sends_a_batch_of_emails.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_Client/_send_batch/with_API_errors/handles_API_errors.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_Client/_send_batch/with_template/successfully_sends_a_batch_of_emails_with_template.yml (1 hunks)
  • spec/mailtrap/client_spec.rb (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
examples/batch.rb (2)
lib/mailtrap/action_mailer/delivery_method.rb (1)
  • client (22-24)
lib/mailtrap/client.rb (1)
  • send_batch (64-76)
spec/mailtrap/client_spec.rb (2)
lib/mailtrap/action_mailer/delivery_method.rb (1)
  • client (22-24)
lib/mailtrap/client.rb (1)
  • send_batch (64-76)
lib/mailtrap/client.rb (3)
lib/mailtrap/mail/from_template.rb (1)
  • as_json (34-41)
lib/mailtrap/mail/base.rb (1)
  • as_json (39-55)
lib/mailtrap/attachment.rb (1)
  • as_json (19-27)
🪛 RuboCop (1.75.5)
spec/mailtrap/client_spec.rb

[convention] 303-303: Context description should match /^when\b/, /^with\b/, or /^without\b/.

(RSpec/ContextWording)


[convention] 320-320: Context description should match /^when\b/, /^with\b/, or /^without\b/.

(RSpec/ContextWording)

🪛 GitHub Actions: Ruby
spec/mailtrap/client_spec.rb

[error] 303-303: Rubocop RSpec/ContextWording: Context description should match /^when\b/, /^with\b/, or /^without\b/.


[error] 320-320: Rubocop RSpec/ContextWording: Context description should match /^when\b/, /^with\b/, or /^without\b/.

lib/mailtrap/client.rb

[error] 31-31: Rubocop Metrics/CyclomaticComplexity: Cyclomatic complexity for initialize is too high. [8/7]

🔇 Additional comments (6)
examples/batch.rb (1)

35-46: Confirm per-recipient minimal payload is accepted

Each Mailtrap::Mail::Base in requests only sets to:. If the API does not auto-inherit from and subject from base, the call will reproduce the 422 validation error captured in the cassette ('from' is required, 'subject' is required). Please verify this with a live call or spec.

spec/fixtures/vcr_cassettes/Mailtrap_Client/_send_batch/with_API_errors/handles_API_errors.yml (1)

45-47: Check double-quoted field names in error strings

The response shows ''from'' is required (double single-quotes). If the client surfaces these messages directly, they’ll look odd to users. Consider normalising or stripping surplus quotes when presenting validation errors.

spec/mailtrap/client_spec.rb (1)

254-390: Comprehensive test coverage for the batch sending feature.

The test suite thoroughly covers various scenarios including bulk mode, sandbox mode, template usage, and error handling. The use of VCR cassettes for HTTP interaction recording is appropriate for integration testing.

lib/mailtrap/client.rb (3)

57-76: Well-implemented batch sending method with proper validation.

The implementation follows established patterns from the existing send method and includes comprehensive parameter validation. The use of compact_with_empty_arrays for data cleaning is appropriate for API payloads.


146-148: Consistent path generation following established patterns.

The batch_request_path method properly mirrors the structure of send_path with appropriate sandbox inbox ID handling.


215-217: Useful utility method for API payload cleaning.

The compact_with_empty_arrays method provides more thorough data cleaning than the standard compact method by also removing empty arrays, which is appropriate for API payloads.

Comment on lines 72 to 75
perform_request(:post, api_host, batch_request_path, {
base: compact_with_empty_arrays(base.as_json),
requests:
})
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify the requests array transformation in the API payload.

The requests array is passed directly without applying compact_with_empty_arrays transformation. Ensure this is intentional and that individual request objects don't require the same cleaning as the base object.


🏁 Script executed:

#!/bin/bash
# Description: Check how requests array is handled in the codebase and verify the API expects raw request objects

# Search for batch API usage patterns and documentation
rg -A 10 -B 5 "batch.*api|api.*batch" --type md

# Look for any examples or documentation about batch request format
fd -e rb -e md | xargs rg -l "send_batch|batch.*request" | head -5 | xargs cat

Length of output: 20855


🏁 Script executed:

#!/bin/bash
# Inspect Mailtrap::Mail::Base class and its JSON serialization methods
rg -n "class Base" -A10 -B2 --type rb

Length of output: 66


🏁 Script executed:

#!/bin/bash
# Locate and inspect Mailtrap::Mail::Base#as_json implementation
files=$(fd -e rb base.rb)
for f in $files; do
  echo "=== Inspecting $f ==="
  rg -n "def as_json" -A20 -B2 "$f"
done

Length of output: 887


🏁 Script executed:

#!/bin/bash
# Display Mailtrap::Mail::Base to inspect defaults, initialize, and to_json
sed -n '1,200p' lib/mailtrap/mail/base.rb

Length of output: 2019


Clean request objects in batch payload

In send_batch (lib/mailtrap/client.rb), we apply compact_with_empty_arrays to the base JSON but pass each request raw. Since Mailtrap::Mail::Base#as_json only uses Hash#compact (removing nils but not empty arrays), any empty‐array fields (e.g. cc, bcc, attachments, headers, custom_variables) will still be sent. To ensure consistency and avoid sending unwanted empty arrays, wrap each request in the same cleanup:

• File: lib/mailtrap/client.rb
Method: send_batch

Proposed change:

- perform_request(:post, api_host, batch_request_path, {
-                   base: compact_with_empty_arrays(base.as_json),
-                   requests:
-                 })
+ perform_request(:post, api_host, batch_request_path, {
+                   base: compact_with_empty_arrays(base.as_json),
+                   requests: requests.map { |req|
+                             compact_with_empty_arrays(req.as_json)
+                           }
+                 })

If the API is fine with empty arrays in individual requests, please confirm this is intentional.

📝 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
perform_request(:post, api_host, batch_request_path, {
base: compact_with_empty_arrays(base.as_json),
requests:
})
perform_request(:post, api_host, batch_request_path, {
base: compact_with_empty_arrays(base.as_json),
requests: requests.map { |req|
compact_with_empty_arrays(req.as_json)
}
})
🤖 Prompt for AI Agents
In lib/mailtrap/client.rb around lines 72 to 75, the send_batch method applies
compact_with_empty_arrays to the base JSON but sends each request without
cleaning empty arrays, causing unwanted empty array fields to be included. To
fix this, apply compact_with_empty_arrays to each request's as_json output
before including it in the requests array, ensuring all empty arrays are removed
consistently from both base and individual requests.

@@ -53,6 +51,27 @@ def initialize( # rubocop:disable Metrics/ParameterLists
@http_clients = {}
end

# Sends a batch of emails
# @param base [Mailtrap::Mail::Base] The base email configuration
Copy link
Contributor

Choose a reason for hiding this comment

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

According to API, base parameter has less fields than Mailtrap::Mail::Base. Please consider those options:

  • Dedicated class for base e.g. Mailtrap::Batch::Base
  • Raise ArgumentError if base has to, cc, bcc

@IgorDobryn what do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I would go with dedicated class, so in batch_send method we won't need to do base.as_json.except('to', 'cc', 'bcc') and having dedicated class would be more descriptive and straitforward

Copy link
Contributor

Choose a reason for hiding this comment

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

The problem with the dedicated class is that you'd need two of them: one for regular emails, another for template emails. This creates potentially confusing hierarchy. Having said that, I agree with your point.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Valid point. In that case, what about having dedicated serializer for Mailtrap::Mail::Base handling batch case and raise errors there, like

perform_request(:post, api_host, batch_request_path, {
                        base: BaseBatchSerializer(base).to_h,
                        ...,
                        })
  class     BaseBatchSerializer
  def initialize(base)
    raise ArgumentError unless base.to.empty?
  end
  def to_h
    @base.as_json(%w[to cc, bcc])
  end

ans as result we will have dedicated batch logic encapsulated somewhere

Copy link
Contributor

Choose a reason for hiding this comment

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

Let's use separate class. Otherwise it will create misexpectations. I.e. when somebody constructs mail object with to/cc/bcc

Copy link
Contributor

Choose a reason for hiding this comment

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

  • please remove unnecessary requires
  • update formatting making it similar to the existing example files
  • make sure the example works
  • the the example more unique by removing the comments that present in other files.

Mailtrap::Mail::Base.new(
to: [
{
email: ENV.fetch('MAILTRAP_TO_EMAIL', '[email protected]'),
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do you need ENV.fetch here? Could you please explain the use case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Only to not expose own email address, as sender [email protected] allows you to send only to account owner.

end

perform_request(:post, api_host, batch_request_path, {
base: compact_with_empty_arrays(base.as_json),
Copy link
Contributor

@i7an i7an Jun 30, 2025

Choose a reason for hiding this comment

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

  • how about base.as_json.except('to', 'cc', 'bcc')?
  • why do you want to compact base json?

Copy link
Contributor Author

@sarco3t sarco3t Jun 30, 2025

Choose a reason for hiding this comment

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

why do you want to compact base json?

only to remove 'to', 'cc', 'bcc', which in case of base has to be empty

Copy link
Contributor

Choose a reason for hiding this comment

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

It wont be necessary once you add classes for base.

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

♻️ Duplicate comments (1)
lib/mailtrap/client.rb (1)

72-75: Apply consistent payload cleaning to request objects.

Based on previous review feedback, the requests array should have the same empty array cleaning applied as the base object to ensure consistency and avoid sending unwanted empty fields.

Apply the same compact_with_empty_arrays treatment to each request:

 perform_request(:post, api_host, batch_request_path, {
                   base: base.as_json.except('to', 'cc', 'bcc'),
-                  requests:
+                  requests: requests.map { |req| compact_with_empty_arrays(req.as_json) }
                 })
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 183af30 and f93bbd3.

📒 Files selected for processing (12)
  • examples/batch.rb (1 hunks)
  • lib/mailtrap.rb (1 hunks)
  • lib/mailtrap/client.rb (4 hunks)
  • lib/mailtrap/mail/base.rb (1 hunks)
  • lib/mailtrap/mail/batch/base.rb (1 hunks)
  • lib/mailtrap/mail/batch/from_template.rb (1 hunks)
  • lib/mailtrap/mail/from_template.rb (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_Client/_send_batch/when_in_bulk_mode/successfully_sends_a_batch_of_emails.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_Client/_send_batch/when_in_sandbox_mode/successfully_sends_a_batch_of_emails.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_Client/_send_batch/with_API_errors/handles_API_errors.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_Client/_send_batch/with_template/successfully_sends_a_batch_of_emails_with_template.yml (1 hunks)
  • spec/mailtrap/client_spec.rb (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • lib/mailtrap.rb
🚧 Files skipped from review as they are similar to previous changes (6)
  • spec/fixtures/vcr_cassettes/Mailtrap_Client/_send_batch/with_template/successfully_sends_a_batch_of_emails_with_template.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_Client/_send_batch/with_API_errors/handles_API_errors.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_Client/_send_batch/when_in_bulk_mode/successfully_sends_a_batch_of_emails.yml
  • spec/mailtrap/client_spec.rb
  • examples/batch.rb
  • spec/fixtures/vcr_cassettes/Mailtrap_Client/_send_batch/when_in_sandbox_mode/successfully_sends_a_batch_of_emails.yml
🧰 Additional context used
🧬 Code Graph Analysis (3)
lib/mailtrap/mail/from_template.rb (1)
lib/mailtrap/mail/base.rb (1)
  • to_batch (84-96)
lib/mailtrap/mail/base.rb (1)
lib/mailtrap/mail/from_template.rb (1)
  • to_batch (46-56)
lib/mailtrap/mail/batch/base.rb (3)
lib/mailtrap/mail/batch/from_template.rb (3)
  • attr_accessor (13-51)
  • initialize (21-39)
  • as_json (43-50)
lib/mailtrap/mail/base.rb (4)
  • attr_accessor (7-97)
  • initialize (11-37)
  • attachments (64-66)
  • as_json (39-55)
lib/mailtrap/client.rb (2)
  • attr_reader (8-221)
  • initialize (31-52)
🔇 Additional comments (7)
lib/mailtrap/mail/base.rb (1)

81-96: Well-implemented conversion method.

The to_batch method correctly converts a single mail object to a batch mail object, preserving all relevant attributes while excluding recipient-specific fields that are handled separately in batch operations.

lib/mailtrap/mail/from_template.rb (1)

43-56: Consistent template-to-batch conversion.

The method follows the same pattern as the base class to_batch method while correctly handling template-specific attributes. Good consistency across the codebase.

lib/mailtrap/mail/batch/from_template.rb (1)

1-54: Well-structured template batch class.

The FromTemplate class properly extends the batch base functionality with template-specific attributes and maintains clean inheritance patterns. The implementation correctly handles template UUID and variables while preserving base batch behavior.

lib/mailtrap/client.rb (3)

40-40: Good refactoring of validation logic.

Extracting argument validation into a dedicated method improves code organization and maintainability.


146-148: Correct batch endpoint path construction.

The batch request path correctly mirrors the send path pattern, including sandbox inbox ID when needed.


215-220: Well-organized validation method.

The extracted validation method consolidates all argument checks in a logical sequence and provides clear error messages.

lib/mailtrap/mail/batch/base.rb (1)

71-73: Correct JSON serialization for batch base.

The as_json method correctly excludes recipient fields (to, cc, bcc) that are not relevant for batch base objects, as these are handled in individual requests.

Comment on lines 62 to 71
def send_batch(base, requests)
unless base.is_a?(Mail::Batch::Base)
raise ArgumentError,
'base should be Mailtrap::Mail::Batch::Base or Mailtrap::Mail::FromTemplate object'
end

unless requests.all?(Mail::Base)
raise ArgumentError,
'requests should be an array of Mailtrap::Mail::Batch::Base or Mailtrap::Mail::FromTemplate objects'
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

Fix request validation logic.

The validation on line 68 checks requests.all?(Mail::Base) but should validate batch mail objects instead. Batch requests should be instances of Mail::Batch::Base or its subclasses.

-    unless requests.all?(Mail::Base)
+    unless requests.all?(Mail::Batch::Base)
       raise ArgumentError,
             'requests should be an array of Mailtrap::Mail::Batch::Base or Mailtrap::Mail::FromTemplate objects'
     end
📝 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 send_batch(base, requests)
unless base.is_a?(Mail::Batch::Base)
raise ArgumentError,
'base should be Mailtrap::Mail::Batch::Base or Mailtrap::Mail::FromTemplate object'
end
unless requests.all?(Mail::Base)
raise ArgumentError,
'requests should be an array of Mailtrap::Mail::Batch::Base or Mailtrap::Mail::FromTemplate objects'
end
def send_batch(base, requests)
unless base.is_a?(Mail::Batch::Base)
raise ArgumentError,
'base should be Mailtrap::Mail::Batch::Base or Mailtrap::Mail::FromTemplate object'
end
unless requests.all?(Mail::Batch::Base)
raise ArgumentError,
'requests should be an array of Mailtrap::Mail::Batch::Base or Mailtrap::Mail::FromTemplate objects'
end
# ... rest of method ...
end
🤖 Prompt for AI Agents
In lib/mailtrap/client.rb around lines 62 to 71, the validation for the requests
array incorrectly checks if all elements are instances of Mail::Base, but it
should verify that each element is an instance of Mail::Batch::Base or its
subclasses. Update the validation condition to check
requests.all?(Mail::Batch::Base) to correctly enforce the expected type for
batch mail objects.

Comment on lines 47 to 67
def initialize( # rubocop:disable Metrics/ParameterLists,Lint/MissingSuper
from: nil,
reply_to: nil,
subject: nil,
text: nil,
html: nil,
attachments: [],
headers: {},
custom_variables: {},
category: nil
)
@from = from
@reply_to = reply_to
@subject = subject
@text = text
@html = html
self.attachments = attachments
@headers = headers
@custom_variables = custom_variables
@category = category
end
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix initialization to properly inherit from parent class.

The initializer disables Lint/MissingSuper but doesn't call the parent's initialize method. This could cause issues with inherited functionality, particularly attachment handling which relies on the parent's setter method.

-        def initialize( # rubocop:disable Metrics/ParameterLists,Lint/MissingSuper
+        def initialize( # rubocop:disable Metrics/ParameterLists
           from: nil,
           reply_to: nil,
           subject: nil,
           text: nil,
           html: nil,
           attachments: [],
           headers: {},
           custom_variables: {},
           category: nil
         )
+          super(
+            from: from,
+            reply_to: reply_to,
+            subject: subject,
+            text: text,
+            html: html,
+            attachments: attachments,
+            headers: headers,
+            custom_variables: custom_variables,
+            category: category,
+            to: [],  # Batch emails don't have direct recipients
+            cc: [],
+            bcc: []
+          )
-          @from = from
-          @reply_to = reply_to
-          @subject = subject
-          @text = text
-          @html = html
-          self.attachments = attachments
-          @headers = headers
-          @custom_variables = custom_variables
-          @category = category
         end
🤖 Prompt for AI Agents
In lib/mailtrap/mail/batch/base.rb lines 47 to 67, the initialize method
disables the Lint/MissingSuper cop but does not call super, which can break
inherited behavior. Fix this by adding a call to super with the appropriate
arguments at the start of the initialize method to ensure the parent class's
initialization logic, especially for attachments, is executed properly.

@sarco3t sarco3t requested review from IgorDobryn and i7an July 2, 2025 08:44
# @param attachments [Array<Hash>] The attachments for the email.
# @param headers [Hash] The custom headers for the email.
# @param custom_variables [Hash] The custom variables for the email.
class Base < Mailtrap::Mail::Base
Copy link
Contributor

Choose a reason for hiding this comment

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

The hierarchy should be reworked. I'll have to prepare a separate PR with the refactoring. Until that, this PR is on hold.

Copy link
Contributor

Choose a reason for hiding this comment

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

@sarco3t I've merged hierarchy refactoring. There is no need to create a dedicated class for batch base. Feel free to use just Base. No need to exclude anything from the to_json output, simply rely on the server validations.

@sarco3t sarco3t force-pushed the batch-sending-api branch from f93bbd3 to 7ecb29b Compare July 14, 2025 11:00
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: 0

♻️ Duplicate comments (2)
examples/batch.rb (2)

6-13: Simplify configuration comments to make the example more unique.

The configuration comments appear to duplicate content from other example files. Per previous feedback, these should be more concise and unique to the batch sending example.

Consider simplifying to focus on batch-specific configuration:

-# Set your API credentials as environment variables
-# export MAILTRAP_API_KEY='your-api-key'
-#
-# client = Mailtrap::Client.new
-# Bulk sending (@see https://help.mailtrap.io/article/113-sending-streams)
-# client = Mailtrap::Client.new(bulk: true)
-# Sandbox sending (@see https://help.mailtrap.io/article/109-getting-started-with-mailtrap-email-testing)
-# client = Mailtrap::Client.new(sandbox: true, inbox_id: 12)
+# For bulk or sandbox mode: 
+# client = Mailtrap::Client.new(bulk: true)
+# client = Mailtrap::Client.new(sandbox: true, inbox_id: 12)

1-74: Improve overall structure and formatting consistency.

The example file should follow the formatting patterns of other example files to maintain consistency across the codebase. Consider adding a brief header comment and ensuring the structure aligns with existing examples.

Consider adding a header comment to explain the example:

+# frozen_string_literal: true
+
+# This example demonstrates batch email sending using the Mailtrap Ruby client.
+# It shows two approaches: regular batch sending and template-based batch sending.
+
 require 'mailtrap'
🧹 Nitpick comments (4)
examples/batch.rb (4)

30-32: Update the hardcoded year to be more current.

The hardcoded year 2022 in the custom variables makes the example feel outdated.

  custom_variables: {
-    year: 2022
+    year: 2024
  }

35-46: Use different email addresses to better demonstrate batch sending.

Using the same email address for both recipients doesn't effectively showcase batch sending functionality. Different email addresses would provide a more realistic example.

  Mailtrap::Mail::Base.new(
    to: [
-     { email: '[email protected]', name: 'recipient1' }
+     { email: '[email protected]', name: 'recipient1' }
    ]
  ),
  Mailtrap::Mail::Base.new(
    to: [
-     { email: '[email protected]', name: 'recipient2' }
+     { email: '[email protected]', name: 'recipient2' }
    ]
  )

48-48: Make the comment more specific to differentiate from the first example.

The comment is identical to line 15, which doesn't help users understand the difference between the two approaches.

-# Batch sending with Mailtrap::Mail::Base
+# Batch sending with templates using Mailtrap::Mail::Base

58-73: Improve recipient diversity and template variable clarity.

Similar to the first example, using the same email address doesn't effectively demonstrate batch sending. Additionally, the template variables could be more meaningful.

  Mailtrap::Mail::Base.new(
    to: [
-     { email: '[email protected]', name: 'recipient1' }
+     { email: '[email protected]', name: 'recipient1' }
    ]
  ),
  Mailtrap::Mail::Base.new(
    to: [
-     { email: '[email protected]', name: 'recipient2' }
+     { email: '[email protected]', name: 'recipient2' }
    ],
    template_variables: {
-     'user_name' => 'John Doe 1',
-     'user_name2' => 'John Doe 2'
+     'user_name' => 'Jane Smith',
+     'user_id' => '12345'
    }
  )
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f93bbd3 and 7ecb29b.

📒 Files selected for processing (8)
  • examples/batch.rb (1 hunks)
  • lib/mailtrap/client.rb (4 hunks)
  • lib/mailtrap/mail/from_template.rb (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_Client/_send_batch/when_in_bulk_mode/successfully_sends_a_batch_of_emails.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_Client/_send_batch/when_in_sandbox_mode/successfully_sends_a_batch_of_emails.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_Client/_send_batch/with_API_errors/handles_API_errors.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_Client/_send_batch/with_template/successfully_sends_a_batch_of_emails_with_template.yml (1 hunks)
  • spec/mailtrap/client_spec.rb (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
  • lib/mailtrap/mail/from_template.rb
  • spec/fixtures/vcr_cassettes/Mailtrap_Client/_send_batch/when_in_bulk_mode/successfully_sends_a_batch_of_emails.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_Client/_send_batch/when_in_sandbox_mode/successfully_sends_a_batch_of_emails.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_Client/_send_batch/with_API_errors/handles_API_errors.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_Client/_send_batch/with_template/successfully_sends_a_batch_of_emails_with_template.yml
  • spec/mailtrap/client_spec.rb
  • lib/mailtrap/client.rb
🔇 Additional comments (1)
examples/batch.rb (1)

1-2: Keep explicit require 'base64'

Our check confirmed that the Base64 module isn’t auto-loaded in Ruby, so the explicit require 'base64' on line 2 is necessary. No changes needed.

@sarco3t sarco3t requested a review from i7an July 14, 2025 11:33
)

client.send_batch(mail, [
Mailtrap::Mail::Base.new(
Copy link
Contributor

Choose a reason for hiding this comment

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

minor. the excessive indentation does not look pretty. how about reformatting it

client.send_batch(
  mail,
  [
    Mailtrap::Mail::Base.new(
      to: [
        { email: '[email protected]', name: 'recipient1' }
      ]
    ),
    Mailtrap::Mail::Base.new(
      to: [
        { email: '[email protected]', name: 'recipient2' }
      ]
    )
  ]
)

Applies to other places as well.

# client = Mailtrap::Client.new(sandbox: true, inbox_id: 12)

# Batch sending with Mailtrap::Mail::Base
mail = Mailtrap::Mail::Base.new(
Copy link
Contributor

@i7an i7an Jul 16, 2025

Choose a reason for hiding this comment

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

what do you think about creating helper methods for base similar to from_template and from_content:

  • batch_base_from_template
  • batch_base_from_content
def batch_base_from_content(
  from: nil,
  reply_to: nil,
  attachments: [],
  headers: {},
  custom_variables: {},
  subject: nil,
  text: nil,
  html: nil,
  category: nil
)
  Mailtrap::Mail::Base.new(
    from:,
    reply_to:,
    attachments:,
    headers:,
    custom_variables:,
    subject:,
    text:,
    html:,
    category:
  )
end

def validate_args!(api_key, api_port, bulk, sandbox, inbox_id)
raise ArgumentError, 'api_key is required' if api_key.nil?
raise ArgumentError, 'api_port is required' if api_port.nil?
raise ArgumentError, 'bulk mode is not applicable for sandbox API' if bulk && sandbox
Copy link
Contributor

Choose a reason for hiding this comment

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

We call it "bulk stream" in public articles.
https://help.mailtrap.io/article/113-sending-streams

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: 0

🧹 Nitpick comments (3)
lib/mailtrap/mail.rb (3)

125-131: Fix the documentation example to reference the correct method name.

The documentation example references Mailtrap::Mail.from_template instead of Mailtrap::Mail.batch_from_template.

Apply this diff to fix the documentation:

-      #   mail = Mailtrap::Mail.from_template(
+      #   mail = Mailtrap::Mail.batch_from_template(

154-158: Fix the documentation example to reference the correct method name.

The documentation example references Mailtrap::Mail.from_content instead of Mailtrap::Mail.batch_from_content.

Apply this diff to fix the documentation:

-      #   mail = Mailtrap::Mail.from_content(
+      #   mail = Mailtrap::Mail.batch_from_content(

132-150: Consider refactoring to reduce code duplication.

The batch methods have significant code duplication with their regular counterparts. Consider extracting the common logic into a private method or using a more DRY approach.

Here's a potential refactor approach:

def batch_from_template(**args)
  from_template(**args.except(:to, :cc, :bcc))
end

def batch_from_content(**args)
  from_content(**args.except(:to, :cc, :bcc))
end

Or create a private helper method:

private

def build_mail_object(recipient_fields: true, **args)
  params = args.dup
  unless recipient_fields
    params.delete(:to)
    params.delete(:cc) 
    params.delete(:bcc)
  end
  Mailtrap::Mail::Base.new(**params)
end

Also applies to: 159-181

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d7ca30a and db29a61.

📒 Files selected for processing (5)
  • examples/batch.rb (1 hunks)
  • lib/mailtrap/client.rb (4 hunks)
  • lib/mailtrap/mail.rb (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_Client/_send_batch/when_in_bulk_stream/successfully_sends_a_batch_of_emails.yml (1 hunks)
  • spec/mailtrap/client_spec.rb (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • spec/fixtures/vcr_cassettes/Mailtrap_Client/_send_batch/when_in_bulk_stream/successfully_sends_a_batch_of_emails.yml
🚧 Files skipped from review as they are similar to previous changes (3)
  • lib/mailtrap/client.rb
  • examples/batch.rb
  • spec/mailtrap/client_spec.rb
🧰 Additional context used
🧬 Code Graph Analysis (1)
lib/mailtrap/mail.rb (1)
lib/mailtrap/mail/base.rb (1)
  • attachments (71-73)
🔇 Additional comments (1)
lib/mailtrap/mail.rb (1)

120-150: Implementation looks correct for batch sending functionality.

Both methods correctly omit recipient fields (to, cc, bcc) and pass the appropriate parameters to Mailtrap::Mail::Base.new, which aligns with the batch sending requirements described in the PR objectives.

Also applies to: 152-181

@sarco3t sarco3t force-pushed the batch-sending-api branch from db29a61 to 6995844 Compare July 16, 2025 10:24
@sarco3t sarco3t requested a review from i7an July 16, 2025 10:33
# 'user_name' => 'John Doe'
# }
# )
def batch_from_template(**args)
Copy link
Contributor

@i7an i7an Jul 16, 2025

Choose a reason for hiding this comment

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

  • the idea here is to provide argument level validation. Dont worry about the verbosity.
  • base word is valuable as it better describes what you are building.

@sarco3t sarco3t requested a review from i7an July 16, 2025 12:38
# }
# )
#
# client.send_batch(mail, [
Copy link
Contributor

Choose a reason for hiding this comment

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

  • rewrite the example to use the helper methods
  • change the formatting / indentation

@@ -117,6 +117,69 @@ def from_content( # rubocop:disable Metrics/ParameterLists
)
end

# Builds a mail object that will be sent using a pre-defined email
Copy link
Contributor

Choose a reason for hiding this comment

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

please update the documentation putting more emphasis on batch sending and the concept of base. Applies to the second base builder as well.

@yanchuk yanchuk linked an issue Jul 16, 2025 that may be closed by this pull request
@sarco3t sarco3t force-pushed the batch-sending-api branch from eeff166 to fe7b7b8 Compare July 17, 2025 11:36
@sarco3t sarco3t requested a review from i7an July 17, 2025 13:03
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.

Add Batch Email Sending Functionality to Mailtrap Gem
3 participants