Skip to content

Commit a88f4c3

Browse files
committed
MT-22401: Add Email Campaigns API to the Java SDK
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
1 parent b8df273 commit a88f4c3

38 files changed

Lines changed: 1766 additions & 1 deletion

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,10 @@ You can find the [Mailtrap Java API reference](https://mailtrap.github.io/mailtr
378378

379379
- [Email Templates](examples/java/io/mailtrap/examples/emailtemplates/EmailTemplatesExample.java)
380380

381+
### Email Marketing API
382+
383+
- [Email Campaigns](examples/java/io/mailtrap/examples/emailcampaigns/EmailCampaignsExample.java)
384+
381385
## Contributing
382386

383387
Bug reports and pull requests are welcome on [GitHub](https://github.com/mailtrap/mailtrap-java). This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](CODE_OF_CONDUCT.md).
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package io.mailtrap.examples.emailcampaigns;
2+
3+
import io.mailtrap.config.MailtrapConfig;
4+
import io.mailtrap.factory.MailtrapClientFactory;
5+
import io.mailtrap.model.DeliveryMode;
6+
import io.mailtrap.model.request.emailcampaigns.CreateEmailCampaign;
7+
import io.mailtrap.model.request.emailcampaigns.ScheduleEmailCampaignRequest;
8+
import io.mailtrap.model.request.emailcampaigns.TemplateAttributes;
9+
import io.mailtrap.model.request.emailcampaigns.UpdateEmailCampaign;
10+
import io.mailtrap.model.response.emailcampaigns.DeliveryOptions;
11+
import io.mailtrap.model.response.emailcampaigns.ReplyTo;
12+
13+
import java.time.OffsetDateTime;
14+
import java.time.ZoneOffset;
15+
import java.util.List;
16+
17+
public class EmailCampaignsExample {
18+
19+
private static final String TOKEN = "<YOUR MAILTRAP TOKEN>";
20+
// UUID of a verified sending domain on the account.
21+
private static final String MAILSEND_DOMAIN_ID = "<YOUR SENDING DOMAIN UUID>";
22+
private static final long CONTACT_LIST_ID = 55L;
23+
24+
public static void main(String[] args) {
25+
final var config = new MailtrapConfig.Builder()
26+
.token(TOKEN)
27+
.build();
28+
29+
final var client = MailtrapClientFactory.createMailtrapClient(config);
30+
31+
// The campaign endpoints are token-scoped: the account is resolved from the API token.
32+
final var campaigns = client.emailCampaignsApi().emailCampaigns();
33+
34+
// List campaigns (newest first). `search` filters by name; `token` is the page number.
35+
final var page = campaigns.getEmailCampaigns(50, "Spring", 1);
36+
System.out.println(page);
37+
38+
// Create a campaign — it starts in the `draft` state. The request body is flat.
39+
final var created = campaigns.createEmailCampaign(
40+
CreateEmailCampaign.builder()
41+
.name("Spring Sale")
42+
.mailsendDomainId(MAILSEND_DOMAIN_ID)
43+
.fromDisplayName("Acme Marketing")
44+
.fromLocalPart("news")
45+
.replyTo(ReplyTo.builder()
46+
.displayName("Acme Support")
47+
.localPart("support")
48+
.domain("acme.com")
49+
.build())
50+
.templateAttributes(TemplateAttributes.builder()
51+
.subject("Spring is here — 30% off")
52+
.build())
53+
.contactListIds(List.of(CONTACT_LIST_ID))
54+
.build());
55+
System.out.println(created.getData());
56+
57+
final var campaignId = created.getData().getId();
58+
59+
// Retrieve a single campaign.
60+
final var fetched = campaigns.getEmailCampaign(campaignId);
61+
System.out.println(fetched.getData());
62+
63+
// Update is a PATCH — only the provided fields change. The template is edited in place;
64+
// `bodyHtml` is the design and must contain an unsubscribe link.
65+
final var updated = campaigns.updateEmailCampaign(campaignId,
66+
UpdateEmailCampaign.builder()
67+
.name("Spring Sale (updated)")
68+
.templateAttributes(TemplateAttributes.builder()
69+
.subject("New subject")
70+
.bodyHtml("<html><body><h1>Hi {{first_name}}!</h1>"
71+
+ "<p><a href=\"__unsubscribe_url__\">Unsubscribe</a></p></body></html>")
72+
.mergeTags(List.of("first_name"))
73+
.build())
74+
.deliveryMode(DeliveryMode.GRADUAL)
75+
.deliveryOptions(DeliveryOptions.builder().emailsPerHour(1000).build())
76+
.build());
77+
System.out.println(updated.getData());
78+
79+
// Schedule the draft to send later; the time comes back in
80+
// currentStateMetadata.scheduledAt.
81+
final var scheduled = campaigns.scheduleEmailCampaign(campaignId,
82+
new ScheduleEmailCampaignRequest(OffsetDateTime.of(2026, 6, 1, 9, 0, 0, 0, ZoneOffset.UTC)));
83+
System.out.println(scheduled.getData().getCurrentStateMetadata().getScheduledAt());
84+
85+
// Cancel the scheduled send — the campaign returns to `draft`.
86+
final var cancelled = campaigns.cancelEmailCampaign(campaignId);
87+
System.out.println(cancelled.getData().getCurrentState());
88+
89+
// Or start sending immediately.
90+
final var started = campaigns.startEmailCampaign(campaignId);
91+
System.out.println(started.getData().getCurrentState());
92+
93+
// Aggregated performance statistics; narrow the window with start/end dates (YYYY-MM-DD).
94+
final var stats = campaigns.getEmailCampaignStats(campaignId, "2026-05-01", "2026-05-31");
95+
System.out.println(stats.getData());
96+
97+
// Delete returns 204 No Content.
98+
campaigns.deleteEmailCampaign(campaignId);
99+
}
100+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package io.mailtrap.api.emailcampaigns;
2+
3+
import io.mailtrap.model.request.emailcampaigns.CreateEmailCampaign;
4+
import io.mailtrap.model.request.emailcampaigns.ScheduleEmailCampaignRequest;
5+
import io.mailtrap.model.request.emailcampaigns.UpdateEmailCampaign;
6+
import io.mailtrap.model.response.emailcampaigns.EmailCampaignListResponse;
7+
import io.mailtrap.model.response.emailcampaigns.EmailCampaignResponse;
8+
import io.mailtrap.model.response.emailcampaigns.EmailCampaignStatsResponse;
9+
10+
/**
11+
* Email Campaigns API. Manage email marketing campaigns and retrieve their performance
12+
* statistics.
13+
*
14+
* <p>The account is resolved from the API token, so these endpoints are token-scoped and the
15+
* path is not account-scoped.
16+
*/
17+
public interface EmailCampaigns {
18+
19+
/**
20+
* List the account's email campaigns, newest first.
21+
*
22+
* @param perPage number of campaigns per page (max 100, default 50); {@code null} to omit
23+
* @param search filter campaigns by name; {@code null} to omit
24+
* @param token page number to retrieve (page-token pagination, default 1);
25+
* {@code null} to omit
26+
* @return a page of campaigns and the pagination metadata
27+
*/
28+
EmailCampaignListResponse getEmailCampaigns(Integer perPage, String search, Integer token);
29+
30+
/**
31+
* Create a new email campaign in the {@code draft} state.
32+
*
33+
* @param request the campaign attributes ({@code name}, {@code mailsendDomainId},
34+
* {@code fromLocalPart} and a template {@code subject} are required)
35+
* @return the created email campaign
36+
*/
37+
EmailCampaignResponse createEmailCampaign(CreateEmailCampaign request);
38+
39+
/**
40+
* Get a single email campaign by ID.
41+
*
42+
* @param emailCampaignId unique email campaign ID
43+
* @return the email campaign
44+
*/
45+
EmailCampaignResponse getEmailCampaign(long emailCampaignId);
46+
47+
/**
48+
* Update an existing {@code draft} email campaign. Only the provided attributes are
49+
* changed.
50+
*
51+
* @param emailCampaignId unique email campaign ID
52+
* @param request the attributes to update
53+
* @return the updated email campaign
54+
*/
55+
EmailCampaignResponse updateEmailCampaign(long emailCampaignId, UpdateEmailCampaign request);
56+
57+
/**
58+
* Delete an email campaign. The campaign must not be in a sending state.
59+
*
60+
* @param emailCampaignId unique email campaign ID
61+
*/
62+
void deleteEmailCampaign(long emailCampaignId);
63+
64+
/**
65+
* Start sending a {@code draft} campaign immediately.
66+
*
67+
* @param emailCampaignId unique email campaign ID
68+
* @return the started email campaign
69+
*/
70+
EmailCampaignResponse startEmailCampaign(long emailCampaignId);
71+
72+
/**
73+
* Schedule a {@code draft} campaign to start sending at a future time. The scheduled time
74+
* is reported back in {@code currentStateMetadata.scheduledAt}.
75+
*
76+
* @param emailCampaignId unique email campaign ID
77+
* @param request when to start sending the campaign
78+
* @return the scheduled email campaign
79+
*/
80+
EmailCampaignResponse scheduleEmailCampaign(long emailCampaignId, ScheduleEmailCampaignRequest request);
81+
82+
/**
83+
* Cancel a {@code scheduled} campaign, returning it to the {@code draft} state.
84+
*
85+
* @param emailCampaignId unique email campaign ID
86+
* @return the cancelled email campaign
87+
*/
88+
EmailCampaignResponse cancelEmailCampaign(long emailCampaignId);
89+
90+
/**
91+
* Terminate a campaign that is currently sending ({@code started}, {@code queued} or
92+
* {@code paused}), aborting the in-flight send.
93+
*
94+
* @param emailCampaignId unique email campaign ID
95+
* @return the terminated email campaign
96+
*/
97+
EmailCampaignResponse terminateEmailCampaign(long emailCampaignId);
98+
99+
/**
100+
* Reset a {@code scheduled} campaign back to the {@code draft} state.
101+
*
102+
* @param emailCampaignId unique email campaign ID
103+
* @return the reset email campaign
104+
*/
105+
EmailCampaignResponse resetEmailCampaign(long emailCampaignId);
106+
107+
/**
108+
* Get aggregated performance statistics for an email campaign over the whole period since
109+
* the campaign was last started. If the campaign has never been started, all counts and
110+
* rates are returned as {@code 0}.
111+
*
112+
* @param emailCampaignId unique email campaign ID
113+
* @return aggregated campaign statistics
114+
*/
115+
EmailCampaignStatsResponse getEmailCampaignStats(long emailCampaignId);
116+
117+
/**
118+
* Get aggregated performance statistics for an email campaign over a narrowed aggregation
119+
* window.
120+
*
121+
* @param emailCampaignId unique email campaign ID
122+
* @param startDate start of the aggregation window (inclusive), in
123+
* {@code YYYY-MM-DD} format; {@code null} to omit
124+
* @param endDate end of the aggregation window (inclusive), in {@code YYYY-MM-DD}
125+
* format; {@code null} to omit
126+
* @return aggregated campaign statistics
127+
*/
128+
EmailCampaignStatsResponse getEmailCampaignStats(long emailCampaignId, String startDate, String endDate);
129+
130+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package io.mailtrap.api.emailcampaigns;
2+
3+
import io.mailtrap.Constants;
4+
import io.mailtrap.api.apiresource.ApiResource;
5+
import io.mailtrap.config.MailtrapConfig;
6+
import io.mailtrap.http.RequestData;
7+
import io.mailtrap.model.AbstractModel;
8+
import io.mailtrap.model.request.emailcampaigns.CreateEmailCampaign;
9+
import io.mailtrap.model.request.emailcampaigns.ScheduleEmailCampaignRequest;
10+
import io.mailtrap.model.request.emailcampaigns.UpdateEmailCampaign;
11+
import io.mailtrap.model.response.emailcampaigns.EmailCampaignListResponse;
12+
import io.mailtrap.model.response.emailcampaigns.EmailCampaignResponse;
13+
import io.mailtrap.model.response.emailcampaigns.EmailCampaignStatsResponse;
14+
15+
import java.util.Optional;
16+
17+
import static io.mailtrap.http.RequestData.entry;
18+
19+
public class EmailCampaignsImpl extends ApiResource implements EmailCampaigns {
20+
21+
private static final String BASE_PATH = "/api/email_campaigns";
22+
23+
public EmailCampaignsImpl(final MailtrapConfig config) {
24+
super(config);
25+
this.apiHost = Constants.GENERAL_HOST;
26+
}
27+
28+
@Override
29+
public EmailCampaignListResponse getEmailCampaigns(final Integer perPage, final String search, final Integer token) {
30+
final var queryParams = RequestData.buildQueryParams(
31+
entry("per_page", Optional.ofNullable(perPage)),
32+
entry("search", Optional.ofNullable(search)),
33+
entry("token", Optional.ofNullable(token))
34+
);
35+
36+
return httpClient.get(
37+
apiHost + BASE_PATH,
38+
new RequestData(queryParams),
39+
EmailCampaignListResponse.class
40+
);
41+
}
42+
43+
@Override
44+
public EmailCampaignResponse createEmailCampaign(final CreateEmailCampaign request) {
45+
return httpClient.post(
46+
apiHost + BASE_PATH,
47+
request,
48+
new RequestData(),
49+
EmailCampaignResponse.class
50+
);
51+
}
52+
53+
@Override
54+
public EmailCampaignResponse getEmailCampaign(final long emailCampaignId) {
55+
return httpClient.get(
56+
String.format(apiHost + BASE_PATH + "/%d", emailCampaignId),
57+
new RequestData(),
58+
EmailCampaignResponse.class
59+
);
60+
}
61+
62+
@Override
63+
public EmailCampaignResponse updateEmailCampaign(final long emailCampaignId, final UpdateEmailCampaign request) {
64+
return httpClient.patch(
65+
String.format(apiHost + BASE_PATH + "/%d", emailCampaignId),
66+
request,
67+
new RequestData(),
68+
EmailCampaignResponse.class
69+
);
70+
}
71+
72+
@Override
73+
public void deleteEmailCampaign(final long emailCampaignId) {
74+
httpClient.delete(
75+
String.format(apiHost + BASE_PATH + "/%d", emailCampaignId),
76+
new RequestData(),
77+
Void.class
78+
);
79+
}
80+
81+
@Override
82+
public EmailCampaignResponse startEmailCampaign(final long emailCampaignId) {
83+
return performLifecycleAction(emailCampaignId, "start");
84+
}
85+
86+
@Override
87+
public EmailCampaignResponse scheduleEmailCampaign(final long emailCampaignId, final ScheduleEmailCampaignRequest request) {
88+
return httpClient.post(
89+
String.format(apiHost + BASE_PATH + "/%d/schedule", emailCampaignId),
90+
request,
91+
new RequestData(),
92+
EmailCampaignResponse.class
93+
);
94+
}
95+
96+
@Override
97+
public EmailCampaignResponse cancelEmailCampaign(final long emailCampaignId) {
98+
return performLifecycleAction(emailCampaignId, "cancel");
99+
}
100+
101+
@Override
102+
public EmailCampaignResponse terminateEmailCampaign(final long emailCampaignId) {
103+
return performLifecycleAction(emailCampaignId, "terminate");
104+
}
105+
106+
@Override
107+
public EmailCampaignResponse resetEmailCampaign(final long emailCampaignId) {
108+
return performLifecycleAction(emailCampaignId, "reset");
109+
}
110+
111+
@Override
112+
public EmailCampaignStatsResponse getEmailCampaignStats(final long emailCampaignId) {
113+
return getEmailCampaignStats(emailCampaignId, null, null);
114+
}
115+
116+
@Override
117+
public EmailCampaignStatsResponse getEmailCampaignStats(final long emailCampaignId, final String startDate, final String endDate) {
118+
final var queryParams = RequestData.buildQueryParams(
119+
entry("start_date", Optional.ofNullable(startDate)),
120+
entry("end_date", Optional.ofNullable(endDate))
121+
);
122+
123+
return httpClient.get(
124+
String.format(apiHost + BASE_PATH + "/%d/stats", emailCampaignId),
125+
new RequestData(queryParams),
126+
EmailCampaignStatsResponse.class
127+
);
128+
}
129+
130+
private EmailCampaignResponse performLifecycleAction(final long emailCampaignId, final String action) {
131+
return httpClient.post(
132+
String.format(apiHost + BASE_PATH + "/%d/%s", emailCampaignId, action),
133+
(AbstractModel) null,
134+
new RequestData(),
135+
EmailCampaignResponse.class
136+
);
137+
}
138+
}

src/main/java/io/mailtrap/client/MailtrapClient.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ public class MailtrapClient {
6262
@Getter
6363
private final MailtrapOrganizationsApi organizationsApi;
6464

65+
/**
66+
* API for Mailtrap.io Email Campaigns functionality
67+
*/
68+
@Getter
69+
private final MailtrapEmailCampaignsApi emailCampaignsApi;
70+
6571
/**
6672
* Utility class which holds sending context (which API to use: Email Sending API, Bulk Sending API or
6773
* Email Testing API, inbox id for Email Testing API) to make it possible to perform send directly from MailtrapClient

0 commit comments

Comments
 (0)