-
Notifications
You must be signed in to change notification settings - Fork 0
Implement /batch
method for sending/testing/bulk emails
#38
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
base: main
Are you sure you want to change the base?
Conversation
WalkthroughIntroduces 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
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested reviewers
Pre-merge checks (2 passed, 1 warning)❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
Poem
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.
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 unit tests
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. Comment |
There was a problem hiding this 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 declaresthrows HttpException, InvalidRequestBodyException
in its signature, but these exceptions are already runtime exceptions that don't need to be declared. This is inconsistent with thesend
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 declaresthrows HttpException, InvalidRequestBodyException
in its signature, but these are runtime exceptions that don't need to be declared. This is inconsistent with thesend
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 fieldsIgnore 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 requestsCoverage 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 casesKeep 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 testEnsure 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 payloadssrc/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
📒 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 annotatetemplate_uuid
andtemplate_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 instantiatesnew RequestData()
without forwarding any headers fromBatchEmailBase#getHeaders()
(accessible viamail.getBase().getHeaders()
). If batch requests should honor custom headers assend()
does, pullmail.getBase().getHeaders()
into aRequestData
and pass it tohttpClient.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 existingsend
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 tovalidateRequestBodyOrThrowException
.
import java.util.List; | ||
import java.util.Map; | ||
|
||
public class Bulk { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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.
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(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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.
protected void assertBatchMailNotNull(MailtrapBatchMail batchMail){ | ||
if (batchMail == null) { | ||
throw new InvalidRequestBodyException("BatchMail must not be null"); | ||
} | ||
} |
There was a problem hiding this comment.
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.
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.
boolean isSubjectTextHtmlEmpty = StringUtils.isEmpty(mail.getSubject()) | ||
&& StringUtils.isEmpty(mail.getText()) | ||
&& StringUtils.isEmpty(mail.getHtml()); | ||
&& StringUtils.isEmpty(mail.getText()) | ||
&& StringUtils.isEmpty(mail.getHtml()); |
There was a problem hiding this comment.
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.
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.
public BatchSendResponse batchSend(MailtrapBatchMail mail) throws HttpException, InvalidRequestBodyException { | ||
assertBatchMailNotNull(mail); | ||
|
||
.getRequests() | ||
.forEach(this::validateMailPayload); | ||
|
||
return | ||
httpClient.post(apiHost + "/api/batch", mail, new RequestData(), BatchSendResponse.class); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
public BatchSendResponse batchSend(MailtrapBatchMail mail) throws HttpException, InvalidRequestBodyException { | |
assertBatchMailNotNull(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.
@JsonProperty("custom_properties") | ||
private Map<String, String> customVariables; | ||
|
There was a problem hiding this comment.
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.
@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.
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; | ||
|
||
} |
There was a problem hiding this comment.
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.
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") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@JsonProperty("custom_properties") | |
@JsonProperty("custom_variables") |
Motivation
Implement the
/batch
endpoint for the Sending/Testing/Bulk Emails API to keep the SDK up-to-dateChanges
Summary by CodeRabbit
New Features
Documentation