Skip to content

Conversation

vitalii-t
Copy link
Collaborator

@vitalii-t vitalii-t commented Sep 9, 2025

Motivation

Implement the /batch endpoint for the Sending/Testing/Bulk Emails API to keep the SDK up-to-date

Changes

  • Added batch method to API interfaces and implementations
  • Added batchSend method to MailtrapClient
  • Covered with unit tests

Summary by CodeRabbit

  • New Features

    • Send multiple emails in a single request across standard, bulk, and sandbox modes.
    • Define shared defaults for batched emails (sender, subject, headers, templates) with per-email overrides.
    • Receive structured batch results with per-email success, message IDs, and error details.
    • Enhanced validation with clearer errors for invalid batch payloads.
  • Documentation

    • Added Java examples demonstrating batch sending for sending, testing (sandbox), and bulk scenarios.

Copy link

coderabbitai bot commented Sep 9, 2025

Walkthrough

Introduces batch email sending across the SDK: new request/response models, client routing, API interfaces and implementations, input validation updates, examples, and comprehensive tests plus fixtures. Adds MailtrapClient.batchSend(...) that dispatches to bulk, sending, or testing contexts, and implements /api/batch (and /api/batch/{inboxId}) endpoints.

Changes

Cohort / File(s) Summary
Java examples: batch sending
examples/java/io/mailtrap/examples/bulk/Batch.java, examples/java/io/mailtrap/examples/sending/Batch.java, examples/java/io/mailtrap/examples/testing/Batch.java
New sample programs demonstrating batch email sending for bulk, standard, and testing (with inboxId) flows.
API interfaces: batch support
src/main/java/io/mailtrap/api/bulkemails/BulkEmails.java, src/main/java/io/mailtrap/api/sendingemails/SendingEmails.java, src/main/java/io/mailtrap/api/testingemails/TestingEmails.java
Add batchSend(...) method signatures returning BatchSendResponse; import new request/response types.
API implementations: batch endpoints
src/main/java/io/mailtrap/api/bulkemails/BulkEmailsImpl.java, src/main/java/io/mailtrap/api/sendingemails/SendingEmailsImpl.java, src/main/java/io/mailtrap/api/testingemails/TestingEmailsImpl.java
Implement batchSend(...) posting to /api/batch (or /api/batch/{inboxId}); validate batch payload and each inner mail; update send(...) to use renamed validation.
Client routing
src/main/java/io/mailtrap/client/MailtrapClient.java
Add public batchSend(MailtrapBatchMail) routing to bulk/testing/sending emails().batchSend(...).
Validation entrypoint update
src/main/java/io/mailtrap/api/apiresource/SendApiResource.java
Rename validateRequestBodyOrThrowException → validateMailPayload; add assertBatchMailNotNull(...); route validation by template presence.
Request models: batch
src/main/java/io/mailtrap/model/request/emails/BatchEmailBase.java, src/main/java/io/mailtrap/model/request/emails/MailtrapBatchMail.java
New DTOs for batch base fields and batched requests list; Lombok builders/getters/setters; Jackson/validation annotations.
Response models: batch
src/main/java/io/mailtrap/model/response/emails/BatchSendDetails.java, src/main/java/io/mailtrap/model/response/emails/BatchSendResponse.java
New DTOs capturing batch send outcome and per-item details.
Tests: API impls
src/test/java/io/mailtrap/api/bulkemails/BulkEmailsImplTest.java, src/test/java/io/mailtrap/api/sendingemails/SendingEmailsImplTest.java, src/test/java/io/mailtrap/api/testingemails/TestingEmailsImplTest.java, src/test/java/io/mailtrap/testutils/BaseSendTest.java
Add batchSend tests (success, template, null, invalid template/text); add constant for null batch message; extend mocks to /api/batch endpoints.
Tests: client
src/test/java/io/mailtrap/client/MailtrapClientTest.java
Add batchSend tests for bulk, sandbox, and standard contexts; extend HTTP mocks for batch endpoints.
Test fixtures
src/test/resources/api/emails/batchSendRequest*.json, src/test/resources/api/emails/batchSendResponse.json
New JSON fixtures for batch request (standard and template) and response.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant App as Application
  participant Client as MailtrapClient
  participant Bulk as BulkSendingApi
  participant Send as SendingApi
  participant Test as TestingApi

  App->>Client: batchSend(MailtrapBatchMail)
  alt Bulk mode
    Client->>Bulk: emails().batchSend(batchMail)
    Bulk-->>Client: BatchSendResponse
  else Sandbox/Testing mode
    Client->>Test: emails().batchSend(batchMail, inboxId)
    Test-->>Client: BatchSendResponse
  else Standard mode
    Client->>Send: emails().batchSend(batchMail)
    Send-->>Client: BatchSendResponse
  end
  Client-->>App: BatchSendResponse
Loading
sequenceDiagram
  autonumber
  participant Impl as *EmailsImpl
  participant Validator as SendApiResource
  participant HTTP as HTTP Client

  Impl->>Validator: assertBatchMailNotNull(batchMail)
  loop For each MailtrapMail in batchMail.requests
    Impl->>Validator: validateMailPayload(mail)
    alt templateUuid present
      Note right of Validator: Validate with template
    else no templateUuid
      Note right of Validator: Validate subject/text/html
    end
  end
  Impl->>HTTP: POST /api/batch[/{inboxId}] with batchMail
  HTTP-->>Impl: BatchSendResponse
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • mklocek
  • vittorius

Pre-merge checks (2 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The current description follows the template for Motivation and Changes but omits the required “How to test” section with test checkboxes and the “Images and GIFs” section defined in the repository’s PR description template, making it incomplete relative to the expected structure. Please add a “How to test” section with checklist items describing manual or automated verification steps and include the “Images and GIFs” table (or explicitly note if no visuals are needed) so that the description fully conforms to the repository’s template.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title “Implement /batch method for sending/testing/bulk emails” succinctly and accurately describes the core change of adding batch send endpoints across the SDK’s sending, testing, and bulk email APIs without extraneous detail, making it clear to reviewers what primary functionality this PR introduces.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

Poem

Thump-thump! I queue my hops in batches now, you see,
Bundled notes and carrot quotes, sent rapidly.
One warren, three paths—bulk, test, or send—
Message IDs glitter, tail to end.
Ears up, whiskers out—success! 22222 squeaks back to me. 🥕✉️

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/batch-send

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/io/mailtrap/api/apiresource/SendApiResource.java (1)

56-71: Logic bug: subject-only emails (no text/html) pass validation when templateUuid is absent.

Current checks only reject when subject, text, and html are all empty. They allow subject-only payloads, contradicting the stated requirement “subject and either text or html”. Add an explicit check that at least one of text/html is present.

Apply this diff:

-    private void validateWithoutTemplate(MailtrapMail mail, boolean isSubjectTextHtmlEmpty) throws InvalidRequestBodyException {
+    private void validateWithoutTemplate(MailtrapMail mail, boolean isSubjectTextHtmlEmpty) throws InvalidRequestBodyException {
       // Ensure that at least subject, text, or html is provided if templateUuid is not set
-      if (isSubjectTextHtmlEmpty) {
-          throw new InvalidRequestBodyException("Mail must have subject and either text or html when templateUuid is not provided");
-      }
+      if (isSubjectTextHtmlEmpty) {
+          throw new InvalidRequestBodyException("Mail must have subject and either text or html when templateUuid is not provided");
+      }
 
       // Ensure templateVariables are not used if templateUuid is not set
       if (MapUtils.isNotEmpty(mail.getTemplateVariables())) {
           throw new InvalidRequestBodyException("Mail templateVariables must only be used with templateUuid");
       }
 
       // Ensure the subject is not empty
-      if (StringUtils.isEmpty(mail.getSubject())) {
+      if (StringUtils.isBlank(mail.getSubject())) {
           throw new InvalidRequestBodyException("Subject must not be null or empty");
       }
+
+      // Ensure at least one of text or html is present
+      if (StringUtils.isBlank(mail.getText()) && StringUtils.isBlank(mail.getHtml())) {
+          throw new InvalidRequestBodyException("Mail must have subject and either text or html when templateUuid is not provided");
+      }
   }
🧹 Nitpick comments (27)
src/test/resources/api/emails/batchSendResponse.json (1)

1-13: Add a mixed-result case to strengthen assertions.

Consider adding a second item with a failure (e.g., success=false with errors) to verify partial successes and error aggregation in parsers.

src/test/resources/api/emails/batchSendRequest.json (1)

1-26: Include multiple requests to truly exercise batching.

Add at least two requests (one minimal, one with attachments/headers) to validate list handling and per-item validation.

src/test/java/io/mailtrap/testutils/BaseSendTest.java (1)

13-22: Optional: Make constants static final for canonical usage.

These are immutable and shared; using static final avoids per-instance duplication.

src/main/java/io/mailtrap/api/testingemails/TestingEmails.java (1)

26-28: Add Javadoc for batchSend for parity with send.

Document params, behavior, and exceptions for consistency.

     SendResponse send(MailtrapMail mail, long inboxId) throws HttpException, InvalidRequestBodyException;
 
+    /**
+     * Sends a batch of emails to a Testing inbox.
+     *
+     * @param mail    Batch payload (list of requests).
+     * @param inboxId ID of the testing inbox.
+     * @return Batch send response with per-item results.
+     * @throws HttpException               on HTTP errors.
+     * @throws InvalidRequestBodyException on invalid payload.
+     */
     BatchSendResponse batchSend(MailtrapBatchMail mail, long inboxId) throws HttpException, InvalidRequestBodyException;
src/main/java/io/mailtrap/api/bulkemails/BulkEmails.java (1)

26-27: Add Javadoc for batchSend to match interface style.

Keeps API surface consistently documented.

     SendResponse send(MailtrapMail mail) throws HttpException, InvalidRequestBodyException;
 
+    /**
+     * Sends a batch of emails via Bulk API.
+     *
+     * @param mail Batch payload (list of requests).
+     * @return Batch send response.
+     * @throws HttpException
+     * @throws InvalidRequestBodyException
+     */
     BatchSendResponse batchSend(MailtrapBatchMail mail) throws HttpException, InvalidRequestBodyException;
src/main/java/io/mailtrap/api/sendingemails/SendingEmails.java (1)

26-27: Add Javadoc for batchSend for completeness.

Aligns with send() documentation and other interfaces.

     SendResponse send(MailtrapMail mail) throws HttpException, InvalidRequestBodyException;
 
+    /**
+     * Sends a batch of emails via Sending API.
+     *
+     * @param mail Batch payload (list of requests).
+     * @return Batch send response.
+     * @throws HttpException
+     * @throws InvalidRequestBodyException
+     */
     BatchSendResponse batchSend(MailtrapBatchMail mail) throws HttpException, InvalidRequestBodyException;
examples/java/io/mailtrap/examples/bulk/Batch.java (1)

11-11: Remove unused import.

The Map import is not used in this example class.

 import java.util.List;
-import java.util.Map;
src/main/java/io/mailtrap/api/testingemails/TestingEmailsImpl.java (2)

35-46: Consider consistent error handling approach.

The batchSend method declares throws HttpException, InvalidRequestBodyException in its signature, but these exceptions are already runtime exceptions that don't need to be declared. This is inconsistent with the send method which doesn't declare any exceptions.

 @Override
-public BatchSendResponse batchSend(MailtrapBatchMail mail, long inboxId) throws HttpException, InvalidRequestBodyException {
+public BatchSendResponse batchSend(MailtrapBatchMail mail, long inboxId) {

43-45: Remove unnecessary line breaks.

The return statement has unnecessary line breaks that hurt readability.

-        return
-            httpClient.post(String.format(apiHost + "/api/batch/%d", inboxId), mail, new RequestData(), BatchSendResponse.class);
-
+        return httpClient.post(String.format(apiHost + "/api/batch/%d", inboxId), mail, new RequestData(), BatchSendResponse.class);
examples/java/io/mailtrap/examples/sending/Batch.java (1)

11-11: Remove unused import.

The Map import is not used in this example class.

 import java.util.List;
-import java.util.Map;
src/main/java/io/mailtrap/client/MailtrapClient.java (1)

81-86: Fix Javadoc description.

The Javadoc comment incorrectly states "Sends an email" (singular) while the method sends multiple emails in a batch.

     /**
-     * Sends an email based on the specified sending configuration.
+     * Sends a batch of emails based on the specified sending configuration.
      *
      * @param mailtrapBatchMail emails to send
      * @return the response from the sending operation
      */
src/main/java/io/mailtrap/api/bulkemails/BulkEmailsImpl.java (2)

35-46: Consider consistent error handling approach.

The batchSend method declares throws HttpException, InvalidRequestBodyException in its signature, but these are runtime exceptions that don't need to be declared. This is inconsistent with the send method which doesn't declare any exceptions.

 @Override
-public BatchSendResponse batchSend(MailtrapBatchMail mail) throws HttpException, InvalidRequestBodyException {
+public BatchSendResponse batchSend(MailtrapBatchMail mail) {

43-45: Remove unnecessary line breaks.

The return statement has unnecessary line breaks that reduce readability.

-        return
-            httpClient.post(apiHost + "/api/batch", mail, new RequestData(), BatchSendResponse.class);
-
+        return httpClient.post(apiHost + "/api/batch", mail, new RequestData(), BatchSendResponse.class);
examples/java/io/mailtrap/examples/testing/Batch.java (1)

26-27: Showcase the new client-level batchSend API (clearer, single entrypoint)

Switch to testing context once, then call client.batchSend(...).

         final var client = MailtrapClientFactory.createMailtrapClient(config);
@@
-        System.out.println(client.testingApi().emails().batchSend(batchMail, config.getInboxId()));
+        client.switchToEmailTestingApi(config.getInboxId());
+        System.out.println(client.batchSend(batchMail));

Also applies to: 45-46

src/main/java/io/mailtrap/model/response/emails/BatchSendResponse.java (1)

3-16: Harden JSON mapping against forward-compatible fields

Ignore unknown JSON to avoid breakage if the API adds properties.

+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@@
-@Data
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
 public class BatchSendResponse {
src/test/java/io/mailtrap/api/bulkemails/BulkEmailsImplTest.java (1)

137-178: Add tests for base+template conflict and empty batch requests

Coverage is good; add two edge cases:

  • requests use templateUuid while base sets subject → expect TEMPLATE_UUID_IS_USED_SUBJECT_AND_TEXT_AND_HTML_SHOULD_BE_EMPTY.
  • empty requests list → expect an InvalidRequestBodyException (align with suggested DTO validation).

Example additions:

@Test
void batchSend_BaseSubjectWithTemplateUuid_ThrowsInvalidRequestBodyException() {
    var mail = createTestMailFromTemplate(); // has templateUuid + templateVariables
    var batchMail = MailtrapBatchMail.builder()
        .base(BatchEmailBase.builder().subject("Base Subject").build())
        .requests(List.of(mail))
        .build();

    var ex = assertThrows(InvalidRequestBodyException.class, () -> bulkEmails.batchSend(batchMail));
    assertEquals(TEMPLATE_UUID_IS_USED_SUBJECT_AND_TEXT_AND_HTML_SHOULD_BE_EMPTY, ex.getMessage());
}

@Test
void batchSend_EmptyRequests_ThrowsInvalidRequestBodyException() {
    var batchMail = MailtrapBatchMail.builder().requests(List.of()).build();
    var ex = assertThrows(InvalidRequestBodyException.class, () -> bulkEmails.batchSend(batchMail));
    // choose appropriate constant if available; otherwise assert on message substring
}
src/test/java/io/mailtrap/api/sendingemails/SendingEmailsImplTest.java (1)

137-178: Mirror bulk tests: add base+template conflict and empty requests cases

Keep API parity between sending and bulk tests.

@Test
void batchSend_BaseSubjectWithTemplateUuid_ThrowsInvalidRequestBodyException() {
    var mail = createTestMailFromTemplate();
    var batchMail = MailtrapBatchMail.builder()
        .base(BatchEmailBase.builder().subject("Base Subject").build())
        .requests(List.of(mail))
        .build();

    var ex = assertThrows(InvalidRequestBodyException.class, () -> sendApi.batchSend(batchMail));
    assertEquals(TEMPLATE_UUID_IS_USED_SUBJECT_AND_TEXT_AND_HTML_SHOULD_BE_EMPTY, ex.getMessage());
}

@Test
void batchSend_EmptyRequests_ThrowsInvalidRequestBodyException() {
    var batchMail = MailtrapBatchMail.builder().requests(List.of()).build();
    var ex = assertThrows(InvalidRequestBodyException.class, () -> sendApi.batchSend(batchMail));
}
src/test/java/io/mailtrap/client/MailtrapClientTest.java (1)

126-137: Add a client-level null batch test

Ensure MailtrapClient.batchSend(null) propagates validation consistently across contexts.

@Test
void test_batchSend_Null_throwsInvalidRequestBodyException() {
    assertThrows(InvalidRequestBodyException.class, () -> mailtrapClient.batchSend(null));
}
src/test/java/io/mailtrap/api/testingemails/TestingEmailsImplTest.java (4)

36-43: Good addition of batch endpoint mocks; consider adding negative-path mocks.

Add one 4xx/5xx mock (e.g., 400 invalid payload) to exercise error handling on batch requests.


139-151: Solid happy-path coverage; assert collection sizes too.

Add assertions on responses size to guard against accidental extra items:

  • assertEquals(1, response.getResponses().size());
  • assertEquals(1, response.getResponses().get(0).getMessageIds().size());

152-164: Mirror the same size assertions in the template-based batch test.

Replicate the collection size checks here for symmetry.


172-181: Great invalid-with-template coverage; add one more negative: base-only template + per-item text/html.

Add a test where BatchEmailBase has templateUuid set but a request provides text/html, expecting the same validation error. This catches merge conflicts between base and per-request fields.

src/main/java/io/mailtrap/api/apiresource/SendApiResource.java (2)

74-77: Align template validation with isBlank().

Prevent whitespace-only subject/text/html when templateUuid is used.

Apply this diff:

-        if (StringUtils.isNotEmpty(mail.getText()) || StringUtils.isNotEmpty(mail.getHtml()) || StringUtils.isNotEmpty(mail.getSubject())) {
+        if (StringUtils.isNotBlank(mail.getText()) || StringUtils.isNotBlank(mail.getHtml()) || StringUtils.isNotBlank(mail.getSubject())) {
             throw new InvalidRequestBodyException("When templateUuid is used, subject, text, and html must not be used");
         }

52-54: Comment nit: remove “assumed to be provided by the user”.

This reads like a placeholder in production code.

Apply this diff:

-        // Additional validation logic (assumed to be provided by the user)
+        // Additional generic validations common to all email payloads
src/main/java/io/mailtrap/model/request/emails/BatchEmailBase.java (3)

13-17: Consider omitting nulls in JSON.

Add @JsonInclude(Include.NON_NULL) to reduce payload size and avoid sending meaningless nulls.

Apply this diff:

+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
@@
-@Getter
-@Setter
-@Builder
-public class BatchEmailBase {
+@Getter
+@Setter
+@Builder
+@JsonInclude(Include.NON_NULL)
+public class BatchEmailBase {

49-54: Type check: should custom properties allow non-string values?

If the API allows numeric/boolean values here, switch to Map<String, Object> for parity with templateVariables.


26-36: Cross-field constraints are documented but not enforced here.

Relying on SendApiResource for enforcement is fine, but consider adding a bean-level validator for BatchEmailBase if base-only sends are allowed.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7841c8c and 25d33e7.

📒 Files selected for processing (23)
  • examples/java/io/mailtrap/examples/bulk/Batch.java (1 hunks)
  • examples/java/io/mailtrap/examples/sending/Batch.java (1 hunks)
  • examples/java/io/mailtrap/examples/testing/Batch.java (1 hunks)
  • src/main/java/io/mailtrap/api/apiresource/SendApiResource.java (2 hunks)
  • src/main/java/io/mailtrap/api/bulkemails/BulkEmails.java (2 hunks)
  • src/main/java/io/mailtrap/api/bulkemails/BulkEmailsImpl.java (2 hunks)
  • src/main/java/io/mailtrap/api/sendingemails/SendingEmails.java (2 hunks)
  • src/main/java/io/mailtrap/api/sendingemails/SendingEmailsImpl.java (2 hunks)
  • src/main/java/io/mailtrap/api/testingemails/TestingEmails.java (2 hunks)
  • src/main/java/io/mailtrap/api/testingemails/TestingEmailsImpl.java (2 hunks)
  • src/main/java/io/mailtrap/client/MailtrapClient.java (2 hunks)
  • src/main/java/io/mailtrap/model/request/emails/BatchEmailBase.java (1 hunks)
  • src/main/java/io/mailtrap/model/request/emails/MailtrapBatchMail.java (1 hunks)
  • src/main/java/io/mailtrap/model/response/emails/BatchSendDetails.java (1 hunks)
  • src/main/java/io/mailtrap/model/response/emails/BatchSendResponse.java (1 hunks)
  • src/test/java/io/mailtrap/api/bulkemails/BulkEmailsImplTest.java (3 hunks)
  • src/test/java/io/mailtrap/api/sendingemails/SendingEmailsImplTest.java (3 hunks)
  • src/test/java/io/mailtrap/api/testingemails/TestingEmailsImplTest.java (3 hunks)
  • src/test/java/io/mailtrap/client/MailtrapClientTest.java (4 hunks)
  • src/test/java/io/mailtrap/testutils/BaseSendTest.java (1 hunks)
  • src/test/resources/api/emails/batchSendRequest.json (1 hunks)
  • src/test/resources/api/emails/batchSendRequestFromTemplate.json (1 hunks)
  • src/test/resources/api/emails/batchSendResponse.json (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (14)
examples/java/io/mailtrap/examples/testing/Batch.java (1)
src/main/java/io/mailtrap/factory/MailtrapClientFactory.java (1)
  • MailtrapClientFactory (32-124)
src/main/java/io/mailtrap/api/sendingemails/SendingEmailsImpl.java (1)
src/main/java/io/mailtrap/exception/InvalidRequestBodyException.java (1)
  • InvalidRequestBodyException (6-12)
src/main/java/io/mailtrap/model/request/emails/MailtrapBatchMail.java (2)
src/main/java/io/mailtrap/model/AbstractModel.java (1)
  • AbstractModel (10-29)
src/main/java/io/mailtrap/model/request/emails/BatchEmailBase.java (1)
  • Getter (13-67)
examples/java/io/mailtrap/examples/bulk/Batch.java (2)
src/main/java/io/mailtrap/factory/MailtrapClientFactory.java (1)
  • MailtrapClientFactory (32-124)
examples/java/io/mailtrap/examples/sending/Batch.java (1)
  • Bulk (13-43)
src/main/java/io/mailtrap/model/response/emails/BatchSendResponse.java (1)
src/main/java/io/mailtrap/model/response/emails/BatchSendDetails.java (1)
  • Data (8-17)
src/test/java/io/mailtrap/api/bulkemails/BulkEmailsImplTest.java (1)
src/main/java/io/mailtrap/Constants.java (1)
  • Constants (6-15)
src/main/java/io/mailtrap/model/request/emails/BatchEmailBase.java (1)
src/main/java/io/mailtrap/model/request/emails/MailtrapBatchMail.java (1)
  • Getter (10-19)
src/main/java/io/mailtrap/api/bulkemails/BulkEmailsImpl.java (1)
src/main/java/io/mailtrap/exception/InvalidRequestBodyException.java (1)
  • InvalidRequestBodyException (6-12)
src/main/java/io/mailtrap/api/testingemails/TestingEmailsImpl.java (1)
src/main/java/io/mailtrap/exception/InvalidRequestBodyException.java (1)
  • InvalidRequestBodyException (6-12)
src/test/java/io/mailtrap/api/sendingemails/SendingEmailsImplTest.java (1)
src/main/java/io/mailtrap/Constants.java (1)
  • Constants (6-15)
src/test/java/io/mailtrap/api/testingemails/TestingEmailsImplTest.java (1)
src/main/java/io/mailtrap/Constants.java (1)
  • Constants (6-15)
src/test/java/io/mailtrap/client/MailtrapClientTest.java (1)
src/main/java/io/mailtrap/Constants.java (1)
  • Constants (6-15)
examples/java/io/mailtrap/examples/sending/Batch.java (2)
src/main/java/io/mailtrap/factory/MailtrapClientFactory.java (1)
  • MailtrapClientFactory (32-124)
examples/java/io/mailtrap/examples/bulk/Batch.java (1)
  • Bulk (13-43)
src/main/java/io/mailtrap/model/response/emails/BatchSendDetails.java (1)
src/main/java/io/mailtrap/model/response/emails/BatchSendResponse.java (1)
  • Data (7-16)
🔇 Additional comments (8)
src/test/java/io/mailtrap/testutils/BaseSendTest.java (1)

21-21: LGTM: Null-message assertion string added for batch.

Matches existing style and improves test clarity.

src/test/resources/api/emails/batchSendRequestFromTemplate.json (1)

1-25: Mapping correct: no action needed BatchEmailBase and MailtrapMail both annotate template_uuid and template_variables with @JsonProperty, and Address.name is correctly mapped by default.

src/main/java/io/mailtrap/api/sendingemails/SendingEmailsImpl.java (2)

27-33: LGTM: Reuse central mail validation.

Switch to validateMailPayload(mail) simplifies and unifies validation.


43-45: Confirm HTTP header propagation in batchSend. The batchSend call instantiates new RequestData() without forwarding any headers from BatchEmailBase#getHeaders() (accessible via mail.getBase().getHeaders()). If batch requests should honor custom headers as send() does, pull mail.getBase().getHeaders() into a RequestData and pass it to httpClient.post().

src/main/java/io/mailtrap/model/response/emails/BatchSendDetails.java (1)

1-18: LGTM!

The implementation of BatchSendDetails data class is correct and follows best practices. The use of Lombok's @Data annotation provides all necessary methods, and the Jackson annotation properly maps the JSON property name.

src/main/java/io/mailtrap/client/MailtrapClient.java (1)

87-95: LGTM!

The batchSend method implementation is clean and follows the same context-driven dispatch pattern as the existing send method. The routing logic correctly delegates to the appropriate API based on the sending context.

src/test/java/io/mailtrap/api/testingemails/TestingEmailsImplTest.java (1)

165-170: Null batch guard test looks good.

Covers the new null-check helper clearly.

src/main/java/io/mailtrap/api/apiresource/SendApiResource.java (1)

32-54: Rename successful: no remaining calls to validateRequestBodyOrThrowException.

import java.util.List;
import java.util.Map;

public class Bulk {
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 class name inconsistency.

The class is named Bulk but the file is named Batch.java. This should be consistent for clarity.

-public class Bulk {
+public class Batch {
🤖 Prompt for AI Agents
In examples/java/io/mailtrap/examples/bulk/Batch.java around line 13, the public
class is declared as Bulk while the file is named Batch.java; update the class
declaration to public class Batch (or rename the file to Bulk.java) so the
public class name matches the filename, and ensure any references/imports are
adjusted accordingly.

import java.util.List;
import java.util.Map;

public class Bulk {
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 class name inconsistency.

The class is named Bulk but the file is named Batch.java. For consistency with the file name and to better represent the batch sending functionality, rename the class to Batch.

-public class Bulk {
+public class Batch {
📝 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
public class Bulk {
public class Batch {
🤖 Prompt for AI Agents
In examples/java/io/mailtrap/examples/sending/Batch.java around line 13, the
class is declared as "Bulk" which mismatches the file name; rename the class
from Bulk to Batch, update any constructors and internal references to use
Batch, and ensure any external references/imports or tests are updated to the
new class name.

Comment on lines +38 to +43
final var batchMail = MailtrapBatchMail.builder()
// Optionally you can add this `base` object - if you have some common data across emails
// Each property can be overridden in `requests` for individual emails
.base(BatchEmailBase.builder().subject("Base Subject for all emails").build())
.requests(List.of(mail))
.build();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove base.subject when using templateUuid in requests

templateUuid requires subject/text/html to be empty. base.subject will be merged into each request, causing validation to fail. Drop the base subject (or switch the example to a non-template mail).

Apply:

         final var batchMail = MailtrapBatchMail.builder()
-            // Optionally you can add this `base` object - if you have some common data across emails
-            // Each property can be overridden in `requests` for individual emails
-            .base(BatchEmailBase.builder().subject("Base Subject for all emails").build())
             .requests(List.of(mail))
             .build();
📝 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
final var batchMail = MailtrapBatchMail.builder()
// Optionally you can add this `base` object - if you have some common data across emails
// Each property can be overridden in `requests` for individual emails
.base(BatchEmailBase.builder().subject("Base Subject for all emails").build())
.requests(List.of(mail))
.build();
final var batchMail = MailtrapBatchMail.builder()
.requests(List.of(mail))
.build();
🤖 Prompt for AI Agents
In examples/java/io/mailtrap/examples/testing/Batch.java around lines 38 to 43,
the example sets base.subject which will be merged into each request and
conflicts with using templateUuid (template mails require subject/text/html to
be empty); remove the subject from BatchEmailBase (i.e., drop .subject("Base
Subject for all emails")) or alternatively change the example to not use
templateUuid so the base subject is allowed; update the example accordingly so
validation does not fail.

Comment on lines +20 to +24
protected void assertBatchMailNotNull(MailtrapBatchMail batchMail){
if (batchMail == null) {
throw new InvalidRequestBodyException("BatchMail must not be null");
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Harden batch guard: ensure requests exist and contain no nulls.

Validate requests list to fail-fast before HTTP.

Apply this diff:

@@
-    protected void assertBatchMailNotNull(MailtrapBatchMail batchMail){
-        if (batchMail == null) {
-            throw new InvalidRequestBodyException("BatchMail must not be null");
-        }
-    }
+    protected void assertBatchMailNotNull(MailtrapBatchMail batchMail){
+        if (batchMail == null) {
+            throw new InvalidRequestBodyException("BatchMail must not be null");
+        }
+        if (batchMail.getRequests() == null || batchMail.getRequests().isEmpty()) {
+            throw new InvalidRequestBodyException("BatchMail.requests must not be null or empty");
+        }
+        if (batchMail.getRequests().stream().anyMatch(java.util.Objects::isNull)) {
+            throw new InvalidRequestBodyException("BatchMail.requests must not contain null items");
+        }
+    }

If preferred, add a top-level import:

+import java.util.Objects;
📝 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
protected void assertBatchMailNotNull(MailtrapBatchMail batchMail){
if (batchMail == null) {
throw new InvalidRequestBodyException("BatchMail must not be null");
}
}
protected void assertBatchMailNotNull(MailtrapBatchMail batchMail) {
if (batchMail == null) {
throw new InvalidRequestBodyException("BatchMail must not be null");
}
if (batchMail.getRequests() == null || batchMail.getRequests().isEmpty()) {
throw new InvalidRequestBodyException("BatchMail.requests must not be null or empty");
}
if (batchMail.getRequests().stream().anyMatch(java.util.Objects::isNull)) {
throw new InvalidRequestBodyException("BatchMail.requests must not contain null items");
}
}
🤖 Prompt for AI Agents
In src/main/java/io/mailtrap/api/apiresource/SendApiResource.java around lines
20-24, the current assertBatchMailNotNull only checks batchMail != null; extend
it to also validate batchMail.getRequests() exists, is non-empty, and contains
no null elements (fail-fast before HTTP). Update the method to throw
InvalidRequestBodyException with clear messages when requests is null, empty, or
any individual request is null; iterate the list to validate entries. Optionally
add a top-level import for java.util.Objects or java.util.List if needed to
implement null/empty checks.

Comment on lines 39 to +41
boolean isSubjectTextHtmlEmpty = StringUtils.isEmpty(mail.getSubject())
&& StringUtils.isEmpty(mail.getText())
&& StringUtils.isEmpty(mail.getHtml());
&& StringUtils.isEmpty(mail.getText())
&& StringUtils.isEmpty(mail.getHtml());
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Treat whitespace-only fields as empty.

Use isBlank() so " " doesn’t bypass validation.

Apply this diff:

-        boolean isSubjectTextHtmlEmpty = StringUtils.isEmpty(mail.getSubject())
-            && StringUtils.isEmpty(mail.getText())
-            && StringUtils.isEmpty(mail.getHtml());
+        boolean isSubjectTextHtmlEmpty = StringUtils.isBlank(mail.getSubject())
+            && StringUtils.isBlank(mail.getText())
+            && StringUtils.isBlank(mail.getHtml());
📝 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
boolean isSubjectTextHtmlEmpty = StringUtils.isEmpty(mail.getSubject())
&& StringUtils.isEmpty(mail.getText())
&& StringUtils.isEmpty(mail.getHtml());
&& StringUtils.isEmpty(mail.getText())
&& StringUtils.isEmpty(mail.getHtml());
boolean isSubjectTextHtmlEmpty = StringUtils.isBlank(mail.getSubject())
&& StringUtils.isBlank(mail.getText())
&& StringUtils.isBlank(mail.getHtml());
🤖 Prompt for AI Agents
In src/main/java/io/mailtrap/api/apiresource/SendApiResource.java around lines
39 to 41, the current validation uses StringUtils.isEmpty so whitespace-only
values pass as non-empty; change the three checks to use
StringUtils.isBlank(mail.getSubject()), StringUtils.isBlank(mail.getText()), and
StringUtils.isBlank(mail.getHtml()) respectively so fields containing only
whitespace are treated as empty; ensure any required imports remain or are
adjusted if necessary.

Comment on lines +36 to +45
public BatchSendResponse batchSend(MailtrapBatchMail mail) throws HttpException, InvalidRequestBodyException {
assertBatchMailNotNull(mail);

mail
.getRequests()
.forEach(this::validateMailPayload);

return
httpClient.post(apiHost + "/api/batch", mail, new RequestData(), BatchSendResponse.class);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Guard against null/empty batch requests to avoid NPE and weak validation.

mail.getRequests() may be null/empty; forEach would NPE and empty batches would pass.

Apply:

     @Override
     public BatchSendResponse batchSend(MailtrapBatchMail mail) throws HttpException, InvalidRequestBodyException {
         assertBatchMailNotNull(mail);
-
-        mail
-            .getRequests()
-            .forEach(this::validateMailPayload);
+        if (mail.getRequests() == null || mail.getRequests().isEmpty()) {
+            throw new InvalidRequestBodyException("Invalid request body. Violations: requests=must not be empty");
+        }
+        mail.getRequests().forEach(this::validateMailPayload);
 
-        return
-            httpClient.post(apiHost + "/api/batch", mail, new RequestData(), BatchSendResponse.class);
+        return httpClient.post(apiHost + "/api/batch", mail, new RequestData(), BatchSendResponse.class);
     }
📝 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
public BatchSendResponse batchSend(MailtrapBatchMail mail) throws HttpException, InvalidRequestBodyException {
assertBatchMailNotNull(mail);
mail
.getRequests()
.forEach(this::validateMailPayload);
return
httpClient.post(apiHost + "/api/batch", mail, new RequestData(), BatchSendResponse.class);
}
@Override
public BatchSendResponse batchSend(MailtrapBatchMail mail) throws HttpException, InvalidRequestBodyException {
assertBatchMailNotNull(mail);
if (mail.getRequests() == null || mail.getRequests().isEmpty()) {
throw new InvalidRequestBodyException("Invalid request body. Violations: requests=must not be empty");
}
mail.getRequests().forEach(this::validateMailPayload);
return httpClient.post(apiHost + "/api/batch", mail, new RequestData(), BatchSendResponse.class);
}
🤖 Prompt for AI Agents
In src/main/java/io/mailtrap/api/sendingemails/SendingEmailsImpl.java around
lines 36 to 45, the code calls mail.getRequests().forEach(...) which will throw
an NPE if getRequests() is null and will allow empty batches; add a guard that
checks if mail.getRequests() is null or empty and throw an
InvalidRequestBodyException with a clear message (e.g., "batch requests must
contain at least one request"); after the guard, iterate the non-null list and
validate each request as currently done, then proceed to call httpClient.post.

Comment on lines +52 to +54
@JsonProperty("custom_properties")
private Map<String, String> customVariables;

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 API field name: custom_properties vs custom_variables.

Double-check the API spec for the expected JSON key; adjust if necessary to avoid breaking requests.


🌐 Web query:

Mailtrap Email Sending API: Does the batch email "base" object expect the field name "custom_properties" or "custom_variables"?

💡 Result:

Use "custom_variables". The Mailtrap docs and SDKs show the field as custom_variables (or SDK camelCase variants like customVariables). [1][2]

Sources:
[1] Mailtrap "Custom Variables" docs.
[2] Mailtrap.Email API docs (shows custom_variables in the email object).


Change JSON property annotation to custom_variables
Update the @JsonProperty value to match the Mailtrap API spec.

--- src/main/java/io/mailtrap/model/request/emails/BatchEmailBase.java
@@ line 52
-    @JsonProperty("custom_properties")
+    @JsonProperty("custom_variables")
📝 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
@JsonProperty("custom_properties")
private Map<String, String> customVariables;
@JsonProperty("custom_variables")
private Map<String, String> customVariables;
🤖 Prompt for AI Agents
In src/main/java/io/mailtrap/model/request/emails/BatchEmailBase.java around
lines 52 to 54, the @JsonProperty currently uses "custom_properties" but the
Mailtrap API expects "custom_variables"; change the annotation value to
@JsonProperty("custom_variables") so the field maps correctly during JSON
serialization/deserialization and keep the field name customVariables unchanged.

Comment on lines +3 to +19
import io.mailtrap.model.AbstractModel;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

import java.util.List;

@Getter
@Setter
@Builder
public class MailtrapBatchMail extends AbstractModel {

private BatchEmailBase base;

private List<MailtrapMail> requests;

}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add bean validation: validate base and require at least one request

Enables nested validation (@Valid) and guards against null/empty batches. Without this, constraints in BatchEmailBase/MailtrapMail won’t run.

 package io.mailtrap.model.request.emails;
 
 import io.mailtrap.model.AbstractModel;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
 import lombok.Builder;
 import lombok.Getter;
 import lombok.Setter;
 
 import java.util.List;
@@
 public class MailtrapBatchMail extends AbstractModel {
 
-    private BatchEmailBase base;
+    @Valid
+    private BatchEmailBase base;
 
-    private List<MailtrapMail> requests;
+    @NotNull
+    @Size(min = 1)
+    @Valid
+    private List<MailtrapMail> requests;
 
 }
📝 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
import io.mailtrap.model.AbstractModel;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
@Builder
public class MailtrapBatchMail extends AbstractModel {
private BatchEmailBase base;
private List<MailtrapMail> requests;
}
package io.mailtrap.model.request.emails;
import io.mailtrap.model.AbstractModel;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
@Builder
public class MailtrapBatchMail extends AbstractModel {
@Valid
private BatchEmailBase base;
@NotNull
@Size(min = 1)
@Valid
private List<MailtrapMail> requests;
}
🤖 Prompt for AI Agents
In src/main/java/io/mailtrap/model/request/emails/MailtrapBatchMail.java around
lines 3 to 19, add bean validation so nested constraints run and empty batches
are rejected: annotate the 'base' field with @NotNull and @Valid, and annotate
the 'requests' field with @NotNull, @NotEmpty and @Valid (or use List<@Valid
MailtrapMail> plus @NotEmpty), and add the corresponding imports from
javax.validation (Valid, NotNull, NotEmpty). Ensure imports are added at the top
and keep Lombok annotations unchanged.

/**
* Values that are specific to the entire send that will be carried along with the email and its activity data
*/
@JsonProperty("custom_properties")
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
@JsonProperty("custom_properties")
@JsonProperty("custom_variables")

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