MT-22401: Add Email Campaigns API - #65
Conversation
Decisions: - Request bodies are flat per the current contract — the email_campaign wrapper classes were deleted; CreateEmailCampaign/UpdateEmailCampaign extend AbstractModel and are passed directly - Single-object and stats responses unwrap through data-envelope types (EmailCampaignResponse, EmailCampaignStatsResponse), following the GetContactResponse precedent - deleteEmailCampaign returns void via Void.class since the API responds 204 No Content - Five lifecycle endpoints (start/schedule/cancel/terminate/reset) share a no-body POST helper; ScheduleEmailCampaignRequest.datetime is OffsetDateTime with @jsonformat(shape = STRING) because the mapper otherwise emits numeric timestamps - CampaignState carries the full 10-value enum — its @JsonCreator throws on unknown values, so stale values hard-fail against production
📝 WalkthroughWalkthroughAdds a token-scoped Email Campaigns API with campaign models, CRUD and lifecycle operations, statistics retrieval, client-factory integration, mocked tests with JSON fixtures, and an end-to-end Java example linked from the README. ChangesEmail Campaigns API
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant MailtrapClient
participant MailtrapEmailCampaignsApi
participant EmailCampaignsImpl
participant EmailCampaignsAPI
MailtrapClient->>MailtrapEmailCampaignsApi: access email campaigns endpoint
MailtrapEmailCampaignsApi->>EmailCampaignsImpl: invoke campaign operation
EmailCampaignsImpl->>EmailCampaignsAPI: send campaign request
EmailCampaignsAPI-->>EmailCampaignsImpl: return campaign or statistics response
EmailCampaignsImpl-->>MailtrapEmailCampaignsApi: deserialize response model
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/test/java/io/mailtrap/api/emailcampaigns/EmailCampaignsImplTest.java (1)
97-132: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winCover
per_pageandtokenrequest serialization.The suite asserts pagination fields from the response but never calls
getEmailCampaignswithperPageortoken. Add a matchingDataMockand test invocation so query-key/type regressions are caught.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/java/io/mailtrap/api/emailcampaigns/EmailCampaignsImplTest.java` around lines 97 - 132, Add a dedicated getEmailCampaigns test using a matching DataMock response and invoke api.getEmailCampaigns with non-null perPage and token values. Assert the existing response expectations while ensuring the request serialization covers both pagination query keys and their numeric types.src/main/java/io/mailtrap/model/request/emailcampaigns/CreateEmailCampaign.java (1)
1-71: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
CreateEmailCampaignandUpdateEmailCampaignare structurally duplicated and both depend on the response package for shared types.Both classes declare an identical set of fields (
name,mailsendDomainId,fromDisplayName,fromLocalPart,replyTo,templateAttributes,deliveryMode,deliveryOptions,contactListIds,contactSegmentIds) with the same annotations, and both importReplyTo/DeliveryOptionsfromio.mailtrap.model.response.emailcampaignsfor request bodies. This is a DRY violation and a layering smell (request DTOs depending on theresponsepackage for types that are genuinely shared).
src/main/java/io/mailtrap/model/request/emailcampaigns/CreateEmailCampaign.java#L1-L71: extract the common fields into a shared abstract base (e.g.AbstractEmailCampaignRequest) that bothCreateEmailCampaignandUpdateEmailCampaignextend, and moveReplyTo/DeliveryOptionsto a neutral shared package (e.g.io.mailtrap.model.emailcampaigns) instead ofresponse.emailcampaigns.src/main/java/io/mailtrap/model/request/emailcampaigns/UpdateEmailCampaign.java#L1-L70: extend the same shared base class once introduced, removing the duplicated field declarations.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/io/mailtrap/model/request/emailcampaigns/CreateEmailCampaign.java` around lines 1 - 71, The email campaign request DTOs duplicate fields and depend on response-only shared types. Create an AbstractEmailCampaignRequest containing the common fields and annotations, make CreateEmailCampaign and UpdateEmailCampaign extend it, and remove their duplicated declarations; move ReplyTo and DeliveryOptions from io.mailtrap.model.response.emailcampaigns to a neutral shared package and update both request DTOs and other references. Apply the changes in src/main/java/io/mailtrap/model/request/emailcampaigns/CreateEmailCampaign.java#L1-L71 and src/main/java/io/mailtrap/model/request/emailcampaigns/UpdateEmailCampaign.java#L1-L70.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@examples/java/io/mailtrap/examples/emailcampaigns/EmailCampaignsExample.java`:
- Around line 81-82: Update the timestamp passed to
EmailCampaignsExample.scheduleEmailCampaign so it is always future-dated when
the example runs, preferably by deriving it from the current time; preserve the
existing UTC OffsetDateTime request construction.
---
Nitpick comments:
In
`@src/main/java/io/mailtrap/model/request/emailcampaigns/CreateEmailCampaign.java`:
- Around line 1-71: The email campaign request DTOs duplicate fields and depend
on response-only shared types. Create an AbstractEmailCampaignRequest containing
the common fields and annotations, make CreateEmailCampaign and
UpdateEmailCampaign extend it, and remove their duplicated declarations; move
ReplyTo and DeliveryOptions from io.mailtrap.model.response.emailcampaigns to a
neutral shared package and update both request DTOs and other references. Apply
the changes in
src/main/java/io/mailtrap/model/request/emailcampaigns/CreateEmailCampaign.java#L1-L71
and
src/main/java/io/mailtrap/model/request/emailcampaigns/UpdateEmailCampaign.java#L1-L70.
In `@src/test/java/io/mailtrap/api/emailcampaigns/EmailCampaignsImplTest.java`:
- Around line 97-132: Add a dedicated getEmailCampaigns test using a matching
DataMock response and invoke api.getEmailCampaigns with non-null perPage and
token values. Assert the existing response expectations while ensuring the
request serialization covers both pagination query keys and their numeric types.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 46d36535-60d9-43fe-a60c-30f229087592
📒 Files selected for processing (38)
README.mdexamples/java/io/mailtrap/examples/emailcampaigns/EmailCampaignsExample.javasrc/main/java/io/mailtrap/api/emailcampaigns/EmailCampaigns.javasrc/main/java/io/mailtrap/api/emailcampaigns/EmailCampaignsImpl.javasrc/main/java/io/mailtrap/client/MailtrapClient.javasrc/main/java/io/mailtrap/client/api/MailtrapEmailCampaignsApi.javasrc/main/java/io/mailtrap/factory/MailtrapClientFactory.javasrc/main/java/io/mailtrap/model/CampaignState.javasrc/main/java/io/mailtrap/model/DeliveryMode.javasrc/main/java/io/mailtrap/model/request/emailcampaigns/CreateEmailCampaign.javasrc/main/java/io/mailtrap/model/request/emailcampaigns/ScheduleEmailCampaignRequest.javasrc/main/java/io/mailtrap/model/request/emailcampaigns/TemplateAttributes.javasrc/main/java/io/mailtrap/model/request/emailcampaigns/UpdateEmailCampaign.javasrc/main/java/io/mailtrap/model/response/emailcampaigns/CampaignRecipientError.javasrc/main/java/io/mailtrap/model/response/emailcampaigns/CurrentStateMetadata.javasrc/main/java/io/mailtrap/model/response/emailcampaigns/DeliveryOptions.javasrc/main/java/io/mailtrap/model/response/emailcampaigns/EmailCampaign.javasrc/main/java/io/mailtrap/model/response/emailcampaigns/EmailCampaignListResponse.javasrc/main/java/io/mailtrap/model/response/emailcampaigns/EmailCampaignResponse.javasrc/main/java/io/mailtrap/model/response/emailcampaigns/EmailCampaignStats.javasrc/main/java/io/mailtrap/model/response/emailcampaigns/EmailCampaignStatsResponse.javasrc/main/java/io/mailtrap/model/response/emailcampaigns/Pagination.javasrc/main/java/io/mailtrap/model/response/emailcampaigns/ReplyTo.javasrc/main/java/io/mailtrap/model/response/emailcampaigns/Template.javasrc/test/java/io/mailtrap/api/emailcampaigns/EmailCampaignsImplTest.javasrc/test/resources/api/emailcampaigns/cancelEmailCampaignResponse.jsonsrc/test/resources/api/emailcampaigns/createEmailCampaignRequest.jsonsrc/test/resources/api/emailcampaigns/createEmailCampaignResponse.jsonsrc/test/resources/api/emailcampaigns/getEmailCampaignResponse.jsonsrc/test/resources/api/emailcampaigns/getEmailCampaignStatsResponse.jsonsrc/test/resources/api/emailcampaigns/listEmailCampaignsResponse.jsonsrc/test/resources/api/emailcampaigns/resetEmailCampaignResponse.jsonsrc/test/resources/api/emailcampaigns/scheduleEmailCampaignRequest.jsonsrc/test/resources/api/emailcampaigns/scheduleEmailCampaignResponse.jsonsrc/test/resources/api/emailcampaigns/startEmailCampaignResponse.jsonsrc/test/resources/api/emailcampaigns/terminateEmailCampaignResponse.jsonsrc/test/resources/api/emailcampaigns/updateEmailCampaignRequest.jsonsrc/test/resources/api/emailcampaigns/updateEmailCampaignResponse.json
| final var scheduled = campaigns.scheduleEmailCampaign(campaignId, | ||
| new ScheduleEmailCampaignRequest(OffsetDateTime.of(2026, 6, 1, 9, 0, 0, 0, ZoneOffset.UTC))); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Use a future scheduling timestamp.
2026-06-01T09:00:00Z is already in the past as of July 30, 2026, so this public example can fail when executed. Generate the value relative to the current time or use a clearly future date.
Proposed fix
final var scheduled = campaigns.scheduleEmailCampaign(campaignId,
- new ScheduleEmailCampaignRequest(OffsetDateTime.of(2026, 6, 1, 9, 0, 0, 0, ZoneOffset.UTC)));
+ new ScheduleEmailCampaignRequest(
+ OffsetDateTime.now(ZoneOffset.UTC).plusDays(1)
+ .withHour(9).withMinute(0).withSecond(0).withNano(0)));📝 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 scheduled = campaigns.scheduleEmailCampaign(campaignId, | |
| new ScheduleEmailCampaignRequest(OffsetDateTime.of(2026, 6, 1, 9, 0, 0, 0, ZoneOffset.UTC))); | |
| final var scheduled = campaigns.scheduleEmailCampaign(campaignId, | |
| new ScheduleEmailCampaignRequest( | |
| OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | |
| .withHour(9).withMinute(0).withSecond(0).withNano(0))); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/java/io/mailtrap/examples/emailcampaigns/EmailCampaignsExample.java`
around lines 81 - 82, Update the timestamp passed to
EmailCampaignsExample.scheduleEmailCampaign so it is always future-dated when
the example runs, preferably by deriving it from the current time; preserve the
existing UTC OffsetDateTime request construction.
Motivation
https://railsware.atlassian.net/browse/MT-22401
Port the Email Campaigns public API (MT-21113) to the Java SDK.
Changes
client.emailCampaignsApi().emailCampaigns()covering the full contract: list (per_page/search/token), get, create, update, delete (204 →void), the five lifecycle actions (start,schedule,cancel,terminate,reset), and stats with an optional date windowdata-envelope response types, UUIDmailsendDomainId,RAPID/GRADUALdelivery modes, 10-valueCampaignState, audience id lists,TemplateAttributeswithbodyHtml/bodyText/mergeTags, per-recipient state-metadata errorsScheduleEmailCampaignRequestserializesdatetimeas ISO 8601 (@JsonFormat(shape = STRING)) — the default mapper would emit numeric timestampsHow to test
EmailCampaignsExamplewith a real API token and a verified sending domain — create a draft, update design/audience, schedule + cancel, fetch stats, deleteCampaignStateis fail-closed on unknown values)Summary by CodeRabbit