Skip to content

Conversation

i7an
Copy link
Contributor

@i7an i7an commented Jul 9, 2025

Motivation

The goal of this PR is to prepare the codebase for the Batch Sending API. The Batch Sending API has BatchEmailBase. There are several ways how to implement it.

Get rid of hierarchy of the mail classes

The existing hierarchy is broken:

mail = Mailtrap::Mail::FromTemplate.new(...)
mail.html = 'hi' # there should be no html, text, category, subject in a mail build from template
client.send(mail) # => error

The client relies solely on the to_json method to build a request. The client depends more on the interface than on a base class.

I suggest to get rid of FromTemplate class and avoid introducing new subclasses. The Base class extended with templates would do the trick. Input validation will be handled by the server.

Pros:

  • Simple.
  • Backward-compatible.
  • The reliance on the interface allows for hashes to be used as input.

Cons:

  • Less strict.

Rework the hierarchy of the mail classes

Base
class Base
  attr_accessor :from, :reply_to, :headers, :custom_variables, :attachments
end
BatchContentBase
class BatchContentBase < Base
  attr_accessor :text, :html, :category, :subject
end
BatchTemplateBase
class BatchTemplateBase < Base
  attr_accessor :template_uuid, :template_variables
end
ContentMail
class ContentMail < BatchContentBase
  attr_accessor :to, :cc, :bcc
end
TemplateMail
class ContentMail < BatchTemplateBase
  attr_accessor :to, :cc, :bcc
end

Pros:

  • Builds clearer boundaries.

Cons:

  • It's overcomplicated.
  • It's practically unnecessary because the client relies only on the to_json method.
  • The existing Base class should be renamed, which would constitute a breaking change, or a parallel hierarchy should be introduced instead.

Changes

  • Deprecated Mailtrap::Mail::FromTemplate
  • Added builder method
  • Updated docs
  • Updated examples

How to test

  • Send an email using:
    • Mailtrap::Mail::FromTemplate
    • Mailtrap::Mail::Base
    • Mailtrap::Mail.from_template
    • Mailtrap::Mail.from_content

Summary by CodeRabbit

  • New Features

    • Added support for creating emails from templates, including new methods for building mail objects with template parameters.
    • The client can now send emails using either mail objects or plain Ruby hashes for flexibility.
  • Bug Fixes

    • Empty fields such as cc, bcc, headers, attachments, and custom variables are now omitted from outgoing email payloads.
  • Documentation

    • Updated usage examples and README to reflect new ways to create and send emails, including template support and direct hash usage.
  • Tests

    • Enhanced tests to cover sending emails with hash parameters and updated expectations to match the omission of empty fields.
  • Refactor

    • Deprecated the old template mail class in favor of a unified mail object approach.

Copy link

coderabbitai bot commented Jul 9, 2025

Walkthrough

This update introduces new factory methods for constructing email objects with or without templates, enhances the Mailtrap::Client to accept any object responding to #to_json for sending emails, and updates documentation and examples to reflect these changes. Tests and fixtures were adjusted to omit empty fields from serialized output.

Changes

File(s) Change Summary
lib/mailtrap/mail.rb, lib/mailtrap/mail/base.rb Added from_template and from_content factory methods; extended Mail::Base to support templates and omit empty fields in JSON.
lib/mailtrap/client.rb Relaxed type constraint for send; now accepts any object responding to #to_json.
lib/mailtrap/mail/from_template.rb Deprecated FromTemplate class; removed template-specific logic and attributes.
README.md, examples/full.rb, examples/email_template.rb Updated usage examples to demonstrate new factory methods and direct hash usage in send.
spec/mailtrap/client_spec.rb, spec/mailtrap/mail/base_spec.rb, spec/mailtrap/mail/from_template_spec.rb Updated and added tests for new send logic and serialization behavior.
spec/fixtures/vcr_cassettes/... (multiple YAML files) Modified VCR cassettes to remove empty fields from request bodies; added new cassette for hash-based send.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant MailtrapClient
    participant MailObject
    participant MailtrapAPI

    User->>MailObject: Create via from_content or from_template
    User->>MailtrapClient: send(MailObject or Hash)
    MailtrapClient->>MailObject: to_json (if needed)
    MailtrapClient->>MailtrapAPI: POST /api/send (JSON body)
    MailtrapAPI-->>MailtrapClient: Response
    MailtrapClient-->>User: Response
Loading

Suggested reviewers

  • IgorDobryn

Poem

In the meadow of code, where emails take flight,
New methods now bloom, both simple and bright.
With hashes or objects, your message will send,
No empty arrays in the payload to mend.
The client is nimble, the tests all agree—
Hooray for the changes from your friend, Rabbit-y! 🐇


📜 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 15f1a0a and 8faa471.

⛔ Files ignored due to path filters (1)
  • Gemfile.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • README.md (1 hunks)
  • lib/mailtrap/client.rb (2 hunks)
  • lib/mailtrap/mail.rb (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • lib/mailtrap/client.rb
🚧 Files skipped from review as they are similar to previous changes (2)
  • README.md
  • 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.

@i7an i7an force-pushed the deprecate-from-template branch from 44c15ef to db0c765 Compare July 9, 2025 16:43
@i7an i7an force-pushed the deprecate-from-template branch from db0c765 to 8a46ffb Compare July 9, 2025 16:44
@i7an i7an requested review from mklocek, ayanko and IgorDobryn July 10, 2025 11:59
@i7an i7an marked this pull request as ready for review July 10, 2025 11:59
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 (5)
README.md (2)

49-58: Example now uses Mail.from_content – good call

Using the builder clarifies intent and steers newcomers away from manual instantiation.
Minor nit: consider adding a short comment that the method returns a Mailtrap::Mail::Base object to hint at the underlying type for readers skimming the docs.


64-72: Hash-based client.send example is valuable

Demonstrates the relaxed parameter contract well.
You might mention that any object responding to #to_json is accepted, not only raw hashes, to avoid readers inferring hash-only support.

lib/mailtrap/mail.rb (3)

16-27: Consider adding parameter validation for required fields.

Both factory methods accept many optional parameters, but some combinations might be invalid (e.g., template emails without template_uuid, or content emails without any content). Consider adding basic validation to provide better error messages.

Example validation for from_template:

def from_template( # rubocop:disable Metrics/ParameterLists
  from: nil,
  to: [],
  reply_to: nil,
  cc: [],
  bcc: [],
  attachments: [],
  headers: {},
  custom_variables: {},
  template_uuid: nil,
  template_variables: {}
)
+ raise ArgumentError, 'template_uuid is required for template-based emails' if template_uuid.nil?
+ raise ArgumentError, 'from address is required' if from.nil?
+ raise ArgumentError, 'to addresses are required' if to.empty?
  
  Mailtrap::Mail::Base.new(
    from:,
    to:,
    # ... rest of parameters
  )
end

Also applies to: 43-56


12-15: Enhance documentation with usage examples.

The documentation could be more comprehensive by including usage examples and clarifying when to use each method.

- # Builds a mail object that will be sent using a pre-defined email
- # template. The template content (subject, text, html, category) is
- # defined in the Mailtrap dashboard and referenced by the template_uuid.
- # Template variables can be passed to customize the template content.
+ # Builds a mail object that will be sent using a pre-defined email
+ # template. The template content (subject, text, html, category) is
+ # defined in the Mailtrap dashboard and referenced by the template_uuid.
+ # Template variables can be passed to customize the template content.
+ #
+ # @param template_uuid [String] The UUID of the template defined in Mailtrap dashboard
+ # @param template_variables [Hash] Variables to customize the template content
+ # @param from [Hash] Sender information with :email and optional :name
+ # @param to [Array<Hash>] Array of recipient hashes with :email and optional :name
+ # @return [Mailtrap::Mail::Base] Mail object ready to be sent
+ #
+ # @example
+ #   mail = Mailtrap::Mail.from_template(
+ #     from: { email: '[email protected]', name: 'Sender' },
+ #     to: [{ email: '[email protected]', name: 'Recipient' }],
+ #     template_uuid: 'template-uuid-here',
+ #     template_variables: { name: 'John', product: 'Widget' }
+ #   )

Also applies to: 42-42


16-16: Consider refactoring to reduce parameter list complexity.

The rubocop disable comments indicate these methods have complex parameter lists. Consider using a parameter object or builder pattern to improve maintainability.

Alternative approach using keyword arguments with a parameter object:

def from_template(**params)
  template_params = TemplateMailParams.new(**params)
  template_params.validate!
  
  Mailtrap::Mail::Base.new(**template_params.to_h)
end

This would centralize parameter validation and make the methods more maintainable.

Also applies to: 43-43

📜 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 15f1a0a.

📒 Files selected for processing (21)
  • README.md (2 hunks)
  • examples/email_template.rb (2 hunks)
  • examples/full.rb (2 hunks)
  • lib/mailtrap/client.rb (1 hunks)
  • lib/mailtrap/mail.rb (1 hunks)
  • lib/mailtrap/mail/base.rb (5 hunks)
  • lib/mailtrap/mail/from_template.rb (2 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_Client/_send/when_mail/when_all_params_are_set/sending_is_successful.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_Client/_send/when_mail/when_api_key_is_incorrect/raises_authorization_error_with_array_of_errors.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_Client/_send/when_mail/when_no_subject_and_no_text_set/raises_sending_error_with_array_of_errors.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_Client/_send/when_mail/with_an_alternative_host/sending_is_successful.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_Client/_send/when_mail/with_bulk_flag/chooses_host_for_bulk_sending.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_Client/_send/when_mail/with_bulk_flag_and_alternative_host/chooses_alternative_host.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_Client/_send/when_mail/with_sandbox_flag/chooses_host_for_sandbox_sending.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_Client/_send/when_mail_is_hash/sends_an_email.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_Client/_send/when_template/when_all_params_are_set/sending_is_successful.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_Client/_send/when_template/when_api_key_is_incorrect/raises_authorization_error_with_array_of_errors.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_Client/_send/when_template/when_using_sandbox/sending_is_successful.yml (1 hunks)
  • spec/mailtrap/client_spec.rb (1 hunks)
  • spec/mailtrap/mail/base_spec.rb (1 hunks)
  • spec/mailtrap/mail/from_template_spec.rb (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (5)
examples/full.rb (2)
lib/mailtrap/mail.rb (1)
  • from_content (43-71)
lib/mailtrap/client.rb (1)
  • send (61-63)
examples/email_template.rb (2)
lib/mailtrap/mail.rb (1)
  • from_template (16-40)
lib/mailtrap/client.rb (1)
  • send (61-63)
spec/mailtrap/client_spec.rb (1)
lib/mailtrap/client.rb (1)
  • send (61-63)
lib/mailtrap/mail/base.rb (2)
lib/mailtrap/action_mailer/delivery_method.rb (1)
  • attr_accessor (5-25)
lib/mailtrap/attachment.rb (1)
  • attr_accessor (7-49)
lib/mailtrap/mail.rb (1)
lib/mailtrap/mail/base.rb (1)
  • attachments (71-73)
🔇 Additional comments (28)
spec/fixtures/vcr_cassettes/Mailtrap_Client/_send/when_template/when_api_key_is_incorrect/raises_authorization_error_with_array_of_errors.yml (1)

8-8: Payload simplified – empty collections correctly removed

The cassette now mirrors the new as_json behaviour, avoiding noisy empty keys. 👍

spec/fixtures/vcr_cassettes/Mailtrap_Client/_send/when_template/when_using_sandbox/sending_is_successful.yml (1)

8-8: Consistent with updated serialization logic

Empty cc, bcc, headers, and custom_variables are gone, keeping the request minimal and aligned with code changes.

spec/fixtures/vcr_cassettes/Mailtrap_Client/_send/when_mail/with_bulk_flag_and_alternative_host/chooses_alternative_host.yml (1)

8-8: Fixture trimmed to essentials

The removal of redundant empty fields keeps the cassette tidy and future-proof. Looks good.

spec/fixtures/vcr_cassettes/Mailtrap_Client/_send/when_mail/when_api_key_is_incorrect/raises_authorization_error_with_array_of_errors.yml (1)

8-8: Updated body reflects new Mail#as_json contract

Cassette accurately captures the streamlined payload. No issues spotted.

spec/fixtures/vcr_cassettes/Mailtrap_Client/_send/when_mail/with_sandbox_flag/chooses_host_for_sandbox_sending.yml (1)

8-8: Fixture follows serialization change

Empty arrays/hashes removed – cassette remains valid and clearer.

spec/fixtures/vcr_cassettes/Mailtrap_Client/_send/when_mail/when_all_params_are_set/sending_is_successful.yml (1)

6-9: Cassette payload trimmed – looks good

Empty keys (cc, bcc, headers, custom_variables) are now omitted, matching the new as_json behaviour. This keeps cassettes smaller and avoids unnecessary diff-noise. 👍

spec/fixtures/vcr_cassettes/Mailtrap_Client/_send/when_template/when_all_params_are_set/sending_is_successful.yml (1)

6-9: Consistent removal of empty fields

Change aligns template cassette with content cassette updates. All good.

spec/fixtures/vcr_cassettes/Mailtrap_Client/_send/when_mail/with_an_alternative_host/sending_is_successful.yml (1)

6-9: Alternative-host cassette updated correctly

Payload pruning matches the unified serialization logic. ✔

spec/fixtures/vcr_cassettes/Mailtrap_Client/_send/when_mail/with_bulk_flag/chooses_host_for_bulk_sending.yml (1)

6-9: Bulk-host cassette in sync

No issues spotted; fixture now reflects the compact JSON.

README.md (1)

60-63: Explicit send step kept concise

Clear and idiomatic Ruby; no concerns.

spec/fixtures/vcr_cassettes/Mailtrap_Client/_send/when_mail/when_no_subject_and_no_text_set/raises_sending_error_with_array_of_errors.yml (1)

8-8: LGTM! JSON payload optimization.

The removal of empty fields from the JSON request body is a good optimization that reduces payload size and aligns with the updated serialization behavior in the mail classes.

spec/fixtures/vcr_cassettes/Mailtrap_Client/_send/when_mail_is_hash/sends_an_email.yml (1)

1-38: LGTM! New test fixture supports hash-based mail sending.

This VCR cassette properly records the HTTP interaction for sending emails using a hash instead of a mail object, supporting the enhanced client flexibility introduced in this PR.

spec/mailtrap/mail/from_template_spec.rb (1)

99-99: LGTM! Test expectations updated for empty field omission.

The test correctly expects empty fields to be omitted from the JSON output, aligning with the updated serialization behavior. Since FromTemplate is being deprecated per the PR objectives, consider whether these tests should be updated to reflect the new patterns or marked for future removal.

spec/mailtrap/mail/base_spec.rb (1)

71-71: LGTM! Test expectations updated for cleaner JSON serialization.

The test correctly expects empty fields to be omitted from the JSON output, which results in cleaner and more efficient serialization of mail objects.

examples/full.rb (2)

4-8: LGTM! Clear documentation of new factory methods.

The comments effectively document the available options for creating mail objects, including the new factory methods that provide a cleaner API.


60-68: LGTM! Excellent demonstration of direct hash sending.

This example clearly shows the new flexibility where raw email parameters can be passed directly to client.send, eliminating the need to create mail objects in simple cases. This aligns perfectly with the PR objective of making the client accept any object responding to #to_json.

spec/mailtrap/client_spec.rb (1)

117-136: LGTM! New test properly validates hash-based email sending.

The test correctly validates that the client can now accept plain Ruby hashes for email sending, which aligns with the relaxed type constraint on the send method. The use of Base64.strict_encode64 for attachment content in the hash format is appropriate and consistent with how the Mail object would serialize attachments.

examples/email_template.rb (2)

3-14: LGTM! Example properly demonstrates the new factory method.

The change from Mailtrap::Mail::FromTemplate.new to Mailtrap::Mail.from_template correctly demonstrates the new recommended approach for creating templated emails, following the deprecation of the FromTemplate class.


20-30: LGTM! Excellent demonstration of the new hash-based API.

This second example effectively shows users how to send emails by passing parameters directly to the client without creating mail objects, which is now possible due to the relaxed type constraint on the send method.

lib/mailtrap/client.rb (1)

57-58: LGTM! Documentation accurately reflects the relaxed type constraint.

The updated documentation correctly indicates that the send method now accepts any object responding to #to_json, which enables the new hash-based email sending functionality while maintaining backwards compatibility with existing mail objects.

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

5-5: LGTM! Proper deprecation marking.

The @deprecated comment clearly indicates that users should use Mailtrap::Mail::Base instead, providing clear guidance for migration.


19-19: LGTM! Simplified constructor delegates to Base class.

The constructor now simply calls super, which is appropriate since all template functionality has been moved to the Base class. This maintains backwards compatibility while the class is being deprecated.

lib/mailtrap/mail/base.rb (4)

8-9: LGTM! Template attributes properly added to Base class.

The addition of template_uuid and template_variables to the attr_accessor declaration correctly consolidates template functionality into the Base class, supporting the deprecation of the FromTemplate class.


25-26: LGTM! Constructor properly supports template parameters.

The constructor correctly accepts and initializes the new template parameters, maintaining consistency with the existing parameter handling pattern.

Also applies to: 40-41


59-61: LGTM! JSON serialization enhanced with template fields and empty value filtering.

The as_json method now includes template fields and uses the presence helper with compact to omit empty values from the JSON output, which improves API efficiency by reducing payload size.


88-90: LGTM! Well-implemented presence helper method.

The presence helper method correctly identifies empty values by checking if the value responds to empty? and returns nil for empty collections, which works well with the compact method to filter out empty fields from JSON serialization.

lib/mailtrap/mail.rb (2)

12-40: LGTM! Clean factory method implementation for template-based emails.

The from_template method provides a clear API for creating template-based emails and correctly separates template parameters from content parameters, addressing the issues mentioned in the PR objectives.


42-71: LGTM! Clean factory method implementation for content-based emails.

The from_content method provides a clear API for creating content-based emails with explicit subject, text, html, and category parameters.

def send(mail)
raise ArgumentError, 'should be Mailtrap::Mail::Base object' unless mail.is_a? Mail::Base
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Its ruby 🦆

# create mail object
mail = Mailtrap::Mail::Base.new(
# Create mail object
mail = Mailtrap::Mail.from_content(
Copy link
Contributor

Choose a reason for hiding this comment

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

Other name could be from_hash

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 wanted to highlight the fact that mail object is instantiated with text and html in contrast with from_template where template_uuid is provided.

@i7an i7an merged commit c53f606 into main Jul 11, 2025
12 checks passed
@i7an i7an deleted the deprecate-from-template branch July 11, 2025 13:30
@coderabbitai coderabbitai bot mentioned this pull request Jul 29, 2025
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.

4 participants