-
Notifications
You must be signed in to change notification settings - Fork 0
Implemented Email Templates API #36
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
Conversation
WalkthroughAdds Email Templates feature: API interface and implementation, request/response models, client wiring (API wrapper, client field, factory creation), example usage, unit tests with fixtures, and cascading validation on inbox request models. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Dev as Developer App
participant Factory as MailtrapClientFactory
participant Client as MailtrapClient
participant ApiWrap as MailtrapEmailTemplatesApi
participant Service as EmailTemplatesImpl
participant HTTP as Mailtrap HTTP Service
Dev->>Factory: createMailtrapClient(config)
Factory->>ApiWrap: new MailtrapEmailTemplatesApi(new EmailTemplatesImpl(config, validator))
Factory-->>Dev: MailtrapClient(..., emailTemplatesApi, ...)
Dev->>Client: emailTemplatesApi().emailTemplates().createEmailTemplate(accId, req)
Client->>ApiWrap: emailTemplates()
ApiWrap->>Service: createEmailTemplate(accId, req)
Service->>Service: validateRequestBodyAndThrowException(req)
Service->>HTTP: POST /api/accounts/{accId}/email_templates
HTTP-->>Service: 201 EmailTemplateResponse
Service-->>Dev: EmailTemplateResponse
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
✨ 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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
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: 2
🧹 Nitpick comments (17)
src/test/resources/api/emailtemplates/updateEmailTemplateRequest.json (1)
1-9: Consider modeling partial update if the endpoint is PATCHIf the update endpoint supports PATCH semantics, trimming unchanged fields keeps the fixture focused on the delta. If it’s PUT, ignore.
Example minimal PATCH-style fixture:
{ "email_template": { - "name": "My Updated Email Template", - "category": "Promotion", - "subject": "Promotion Template subject", - "body_text": "Promotion Text body", - "body_html": "<div>Promotion body</div>" + "name": "My Updated Email Template" } }src/main/java/io/mailtrap/model/request/inboxes/CreateInboxRequest.java (1)
22-24: Strengthen string validation on name
@Sizedoesn’t fail on null/blank. Consider@NotBlankalongside@Size.Add:
import jakarta.validation.constraints.Size; +import jakarta.validation.constraints.NotBlank; @JsonProperty("name") - @Size(min = 2, max = 100) + @NotBlank + @Size(min = 2, max = 100) private String name;src/test/resources/api/emailtemplates/updateEmailTemplateResponse.json (1)
1-11: Make updated_at reflect a real updateFor clarity, set
updated_atlater thancreated_at."created_at": "2025-09-05T01:00:00Z", - "updated_at": "2025-09-05T01:00:00Z" + "updated_at": "2025-09-05T01:05:00Z"src/main/java/io/mailtrap/model/request/inboxes/UpdateInboxRequest.java (2)
5-5: Also require presence of the nested object.Add
@NotNullto ensure the"inbox"payload is provided; otherwise Bean Validation won’t catch a missing body.Apply:
-import jakarta.validation.Valid; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; @@ - @Valid + @Valid + @NotNull @JsonProperty("inbox") private InboxUpdateData inboxUpdateData;Also applies to: 14-16
10-20: Optional: add no-args ctors for Jackson-friendly deserialization.If these models are ever deserialized from JSON, consider
@NoArgsConstructorfor both classes.Apply:
import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; @@ @Getter @AllArgsConstructor +@NoArgsConstructor public class UpdateInboxRequest extends AbstractModel { @@ @Getter @AllArgsConstructor + @NoArgsConstructor public static class InboxUpdateData {src/main/java/io/mailtrap/model/response/emailtemplates/EmailTemplateResponse.java (1)
3-5: Harden mapping against unknown fields.Add
@JsonIgnoreProperties(ignoreUnknown = true)to avoid breakage if API returns extra fields.Apply:
-import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @@ -@Data -public class EmailTemplateResponse { +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class EmailTemplateResponse {Also applies to: 8-9
src/main/java/io/mailtrap/client/api/MailtrapEmailTemplatesApi.java (1)
8-13: Guard against nulls on dependencyAnnotate the dependency as non-null to fail fast if miswired.
import io.mailtrap.api.emailtemplates.EmailTemplates; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.experimental.Accessors; +import lombok.NonNull; @Getter @Accessors(fluent = true) @RequiredArgsConstructor public class MailtrapEmailTemplatesApi { - private final EmailTemplates emailTemplates; + private final @NonNull EmailTemplates emailTemplates; }src/main/java/io/mailtrap/model/request/emailtemplates/CreateEmailTemplateRequest.java (1)
1-19: Parity with Update DTO + safer Jackson usageMatch Update DTO’s NON_NULL policy and add a protected no-args ctor for Jackson deserialization flexibility (future-proofing).
package io.mailtrap.model.request.emailtemplates; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonInclude; import io.mailtrap.model.AbstractModel; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; @Getter @AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@JsonInclude(JsonInclude.Include.NON_NULL) public class CreateEmailTemplateRequest extends AbstractModel { @Valid @NotNull @JsonProperty("email_template") private EmailTemplate emailTemplate; }src/main/java/io/mailtrap/model/request/emailtemplates/UpdateEmailTemplateRequest.java (1)
1-21: Add protected no-args constructor for JacksonKeeps DTOs easy to deserialize if needed (tests/examples or future features).
package io.mailtrap.model.request.emailtemplates; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import io.mailtrap.model.AbstractModel; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; @Getter @AllArgsConstructor @JsonInclude(JsonInclude.Include.NON_NULL) +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class UpdateEmailTemplateRequest extends AbstractModel { @Valid @NotNull @JsonProperty("email_template") private EmailTemplate emailTemplate; }examples/java/io/mailtrap/examples/emailtemplates/EmailTemplates.java (3)
12-14: Avoid hardcoding secrets; read from environment.Safer for examples and prevents accidental token leaks.
- private static final String TOKEN = "<YOUR MAILTRAP TOKEN>"; - private static final long ACCOUNT_ID = 1L; + private static final String TOKEN = System.getenv("MAILTRAP_TOKEN"); + private static final long ACCOUNT_ID = Long.parseLong(System.getenv().getOrDefault("MAILTRAP_ACCOUNT_ID", "0"));
26-27: Remove no-op instantiation.This object is unused.
- new EmailTemplate(EMAIL_TEMPLATE_NAME, EMAIL_TEMPLATE_CATEGORY, EMAIL_TEMPLATE_SUBJECT, EMAIL_TEMPLATE_BODY_TEXT, EMAIL_TEMPLATE_BODY_HTML);
21-70: Optional: basic error handling for example flow.Wrap calls to show typical failure handling and avoid noisy stack traces in examples.
public static void main(String[] args) { - final var config = new MailtrapConfig.Builder() + final var config = new MailtrapConfig.Builder() .token(TOKEN) .build(); - - final var client = MailtrapClientFactory.createMailtrapClient(config); + final var client = MailtrapClientFactory.createMailtrapClient(config); + try { // ... existing example calls ... - client.emailTemplatesApi().emailTemplates() - .deleteEmailTemplate(ACCOUNT_ID, updatedEmailTemplate.getId()); + client.emailTemplatesApi().emailTemplates() + .deleteEmailTemplate(ACCOUNT_ID, updatedEmailTemplate.getId()); + } catch (Exception e) { + System.err.println("Email Templates API example failed: " + e.getMessage()); + } }src/test/java/io/mailtrap/api/emailtemplates/EmailTemplatesImplTest.java (3)
94-96: Fix typo in test name (WIth→With).Minor readability nit.
- void test_createEmailTemplate_shouldFailOnValidationWIthNullableBody() { + void test_createEmailTemplate_shouldFailOnValidationWithNullableBody() {
88-91: Make assertion less brittle against message formatting changes.Assert key parts separately to reduce coupling to exact text.
- assertTrue(exception.getMessage().contains("Violations: emailTemplate.name=size must be between 1 and 255")); + assertTrue(exception.getMessage().contains("emailTemplate.name")); + assertTrue(exception.getMessage().contains("size must be between 1 and 255"));
138-141: Same robustness tweak for update validation message.- assertTrue(exception.getMessage().contains("Violations: emailTemplate.subject=size must be between 1 and 255")); + assertTrue(exception.getMessage().contains("emailTemplate.subject")); + assertTrue(exception.getMessage().contains("size must be between 1 and 255"));src/main/java/io/mailtrap/api/emailtemplates/EmailTemplatesImpl.java (2)
21-29: Path building is duplicated; extract helpers.Improves maintainability and reduces formatting mistakes.
public class EmailTemplatesImpl extends ApiResourceWithValidation implements EmailTemplates { @@ public List<EmailTemplateResponse> getAllTemplates(long accountId) { - return - httpClient.getList( - String.format(apiHost + "/api/accounts/%d/email_templates", accountId), - new RequestData(), - EmailTemplateResponse.class - ); + return httpClient.getList(path(accountId), new RequestData(), EmailTemplateResponse.class); } @@ - httpClient.post( - String.format(apiHost + "/api/accounts/%d/email_templates", accountId), - request, - new RequestData(), - EmailTemplateResponse.class - ); + httpClient.post(path(accountId), request, new RequestData(), EmailTemplateResponse.class); @@ - httpClient.get( - String.format(apiHost + "/api/accounts/%d/email_templates/%d", accountId, emailTemplateId), - new RequestData(), - EmailTemplateResponse.class - ); + httpClient.get(path(accountId, emailTemplateId), new RequestData(), EmailTemplateResponse.class); @@ - httpClient.patch( - String.format(apiHost + "/api/accounts/%d/email_templates/%d", accountId, emailTemplateId), - request, - new RequestData(), - EmailTemplateResponse.class - ); + httpClient.patch(path(accountId, emailTemplateId), request, new RequestData(), EmailTemplateResponse.class); @@ - httpClient - .delete( - String.format(apiHost + "/api/accounts/%d/email_templates/%d", accountId, emailTemplateId), - new RequestData(), - Void.class - ); + httpClient.delete(path(accountId, emailTemplateId), new RequestData(), Void.class); + } + + private String path(long accountId) { + return String.format(apiHost + "/api/accounts/%d/email_templates", accountId); + } + + private String path(long accountId, long emailTemplateId) { + return String.format(apiHost + "/api/accounts/%d/email_templates/%d", accountId, emailTemplateId); }
31-42: Validate IDs early to fail fast on obvious input mistakes.Check positive IDs before network calls.
public EmailTemplateResponse createEmailTemplate(long accountId, CreateEmailTemplateRequest request) { + if (accountId <= 0) { + throw new IllegalArgumentException("accountId must be > 0"); + } validateRequestBodyAndThrowException(request);And similarly add at the start of:
- getAllTemplates(long accountId)
- getEmailTemplate(long accountId, long emailTemplateId)
- updateEmailTemplate(long accountId, long emailTemplateId, UpdateEmailTemplateRequest request)
- deleteEmailTemplate(long accountId, long emailTemplateId)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (20)
examples/java/io/mailtrap/examples/emailtemplates/EmailTemplates.java(1 hunks)src/main/java/io/mailtrap/api/emailtemplates/EmailTemplates.java(1 hunks)src/main/java/io/mailtrap/api/emailtemplates/EmailTemplatesImpl.java(1 hunks)src/main/java/io/mailtrap/client/MailtrapClient.java(1 hunks)src/main/java/io/mailtrap/client/api/MailtrapEmailTemplatesApi.java(1 hunks)src/main/java/io/mailtrap/factory/MailtrapClientFactory.java(3 hunks)src/main/java/io/mailtrap/model/request/emailtemplates/CreateEmailTemplateRequest.java(1 hunks)src/main/java/io/mailtrap/model/request/emailtemplates/EmailTemplate.java(1 hunks)src/main/java/io/mailtrap/model/request/emailtemplates/UpdateEmailTemplateRequest.java(1 hunks)src/main/java/io/mailtrap/model/request/inboxes/CreateInboxRequest.java(2 hunks)src/main/java/io/mailtrap/model/request/inboxes/UpdateInboxRequest.java(2 hunks)src/main/java/io/mailtrap/model/response/emailtemplates/EmailTemplateResponse.java(1 hunks)src/test/java/io/mailtrap/api/emailtemplates/EmailTemplatesImplTest.java(1 hunks)src/test/java/io/mailtrap/testutils/BaseTest.java(1 hunks)src/test/resources/api/emailtemplates/createEmailTemplateRequest.json(1 hunks)src/test/resources/api/emailtemplates/createEmailTemplateResponse.json(1 hunks)src/test/resources/api/emailtemplates/getAllEmailTemplatesResponse.json(1 hunks)src/test/resources/api/emailtemplates/getEmailTemplateResponse.json(1 hunks)src/test/resources/api/emailtemplates/updateEmailTemplateRequest.json(1 hunks)src/test/resources/api/emailtemplates/updateEmailTemplateResponse.json(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/main/java/io/mailtrap/client/api/MailtrapEmailTemplatesApi.java (1)
src/main/java/io/mailtrap/model/request/emailtemplates/CreateEmailTemplateRequest.java (1)
Getter(10-19)
src/main/java/io/mailtrap/model/request/emailtemplates/CreateEmailTemplateRequest.java (1)
src/main/java/io/mailtrap/model/AbstractModel.java (1)
AbstractModel(10-29)
🔇 Additional comments (13)
src/test/resources/api/emailtemplates/createEmailTemplateResponse.json (1)
1-11: Fixture LGTMPayload structure and fields look consistent with the EmailTemplateResponse DTO and other fixtures.
src/main/java/io/mailtrap/model/request/inboxes/CreateInboxRequest.java (1)
14-16: Good call adding @Valid for cascading validationThis enables nested constraints on InboxCreateData to be enforced.
src/test/resources/api/emailtemplates/getEmailTemplateResponse.json (1)
1-11: Fixture LGTMFields and types align with the response DTO; consistent with other email template fixtures.
src/main/java/io/mailtrap/model/request/inboxes/UpdateInboxRequest.java (1)
14-16: Cascading validation enabled — LGTM.Adding
@Validon the nested payload is correct and aligns with the validation onInboxUpdateData.src/test/resources/api/emailtemplates/getAllEmailTemplatesResponse.json (1)
1-24: Fixture looks good.Fields and snake_case keys match
EmailTemplateResponse.src/main/java/io/mailtrap/model/request/emailtemplates/EmailTemplate.java (1)
3-8: Enforce non-blank fields & require body content
- Replace
@NotNull @Size(min=1, max=255)onname,category,subjectwith:@NotBlank @Size(max = 255)- Add an
@AssertTruemethod to the class:and import@AssertTrue(message = "Either body_text or body_html must be provided") private boolean hasBodyContent() { return (bodyText != null && !bodyText.isBlank()) || (bodyHtml != null && !bodyHtml.isBlank()); }jakarta.validation.constraints.AssertTrue.- Confirm that
@Size(max = 10_000_000)on the body fields aligns with Mailtrap’s server limits to prevent oversized payloads.src/test/resources/api/emailtemplates/createEmailTemplateRequest.json (1)
1-9: Request fixture — LGTM.Covers both
body_textandbody_htmlpaths.src/test/java/io/mailtrap/testutils/BaseTest.java (1)
26-26: LGTM: fixture ID for templates addedThe
emailTemplateId = 2222Laligns with the new fixtures and test usage.src/main/java/io/mailtrap/client/MailtrapClient.java (1)
51-56: AllMailtrapClientconstructor invocations have been updated with the new parameter
Verified the sole instantiation in MailtrapClientFactory (now passingemailTemplatesApiat line 55); no further updates required.src/main/java/io/mailtrap/api/emailtemplates/EmailTemplates.java (2)
9-55: Interface shape looks good and cohesive.CRUD surface and DTO types are clear.
17-17: Ignore the rename suggestion:getAllTemplatesfollows the establishedgetAll…naming used by other collection endpoints (e.g.getAllAccounts,getAllContactFields), so no change is needed.Likely an incorrect or invalid review comment.
src/main/java/io/mailtrap/factory/MailtrapClientFactory.java (2)
100-104: Good separation for API wiring.Helper keeps factory readable and follows existing pattern.
51-56: Constructor arity is correct and all call sites are in sync
MailtrapClientFactory’snew MailtrapClient(...)invocation matches the Lombok-generated constructor parameters; no other instantiations detected.
Motivation
Next steps implementing Mailtrap API - Email Templates
Changes
Summary by CodeRabbit
New Features
Bug Fixes
Tests