diff --git a/README.md b/README.md index 28627f8..42c0766 100644 --- a/README.md +++ b/README.md @@ -342,6 +342,7 @@ You can find the [Mailtrap Java API reference](https://mailtrap.github.io/mailtr - [Batch](examples/java/io/mailtrap/examples/sending/BatchExample.java) - [Sending Domains](examples/java/io/mailtrap/examples/sendingdomains/SendingDomainsExample.java) - [Suppressions](examples/java/io/mailtrap/examples/suppressions/SuppressionsExample.java) +- [Email Logs](examples/java/io/mailtrap/examples/emaillogs/EmailLogsExample.java) ### Email Testing API diff --git a/examples/java/io/mailtrap/examples/emaillogs/EmailLogsExample.java b/examples/java/io/mailtrap/examples/emaillogs/EmailLogsExample.java new file mode 100644 index 0000000..13e1769 --- /dev/null +++ b/examples/java/io/mailtrap/examples/emaillogs/EmailLogsExample.java @@ -0,0 +1,56 @@ +package io.mailtrap.examples.emaillogs; + +import io.mailtrap.config.MailtrapConfig; +import io.mailtrap.factory.MailtrapClientFactory; +import io.mailtrap.model.request.emaillogs.EmailLogsListFilters; +import io.mailtrap.model.request.emaillogs.FilterCiString; +import io.mailtrap.model.request.emaillogs.FilterExactString; +import io.mailtrap.model.request.emaillogs.FilterStatus; +import io.mailtrap.model.request.emaillogs.FilterOptionalString; +import io.mailtrap.model.response.emaillogs.MessageStatus; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.List; + +public class EmailLogsExample { + + private static final String TOKEN = ""; + private static final long ACCOUNT_ID = 1L; + + public static void main(String[] args) { + final var config = new MailtrapConfig.Builder() + .token(TOKEN) + .build(); + + final var client = MailtrapClientFactory.createMailtrapClient(config); + + // List email logs for the last 2 days + final var now = Instant.now(); + final var twoDaysAgo = now.minus(2, ChronoUnit.DAYS); + final var filters = EmailLogsListFilters.builder() + .sentAfter(twoDaysAgo.toString()) + .sentBefore(now.toString()) + .subject(new FilterOptionalString(FilterOptionalString.Operator.not_empty)) + .to(new FilterCiString(FilterCiString.Operator.ci_equal, "recipient@example.com")) + .category(new FilterExactString(FilterExactString.Operator.equal, + List.of("Welcome Email"))) + .build(); + + final var listResponse = client.sendingApi().emailLogs() + .list(ACCOUNT_ID, null, filters); + + System.out.println("Total: " + listResponse.getTotalCount()); + listResponse.getMessages().forEach( + msg -> System.out.println(" " + msg.getMessageId() + " " + msg.getSubject() + " " + + msg.getStatus())); + + // Get a single message by ID (use message_id from list response) + if (!listResponse.getMessages().isEmpty()) { + final var messageId = listResponse.getMessages().get(0).getMessageId(); + final var message = client.sendingApi().emailLogs().get(ACCOUNT_ID, messageId); + System.out.println( + "Message: " + message.getSubject() + " events: " + message.getEvents().size()); + } + } +} diff --git a/src/main/java/io/mailtrap/api/emaillogs/EmailLogs.java b/src/main/java/io/mailtrap/api/emaillogs/EmailLogs.java new file mode 100644 index 0000000..e781a54 --- /dev/null +++ b/src/main/java/io/mailtrap/api/emaillogs/EmailLogs.java @@ -0,0 +1,31 @@ +package io.mailtrap.api.emaillogs; + +import io.mailtrap.model.request.emaillogs.EmailLogsListFilters; +import io.mailtrap.model.response.emaillogs.EmailLogsListResponse; +import io.mailtrap.model.response.emaillogs.EmailLogMessage; + +/** + * API for listing and retrieving email sending logs. + */ +public interface EmailLogs { + + /** + * Returns a paginated list of email logs for the account. + * + * @param accountId account ID + * @param searchAfter optional cursor (message_id UUID from previous response + * next_page_cursor) for the next page + * @param filters optional filters; pass null or empty to omit + * @return paginated list with messages, total_count, and next_page_cursor + */ + EmailLogsListResponse list(long accountId, String searchAfter, EmailLogsListFilters filters); + + /** + * Returns a single email log message by its UUID. + * + * @param accountId account ID + * @param sendingMessageId message UUID + * @return the message with details and events, or throws if not found + */ + EmailLogMessage get(long accountId, String sendingMessageId); +} diff --git a/src/main/java/io/mailtrap/api/emaillogs/EmailLogsImpl.java b/src/main/java/io/mailtrap/api/emaillogs/EmailLogsImpl.java new file mode 100644 index 0000000..ad06330 --- /dev/null +++ b/src/main/java/io/mailtrap/api/emaillogs/EmailLogsImpl.java @@ -0,0 +1,110 @@ +package io.mailtrap.api.emaillogs; + +import io.mailtrap.Constants; +import io.mailtrap.api.apiresource.ApiResource; +import io.mailtrap.config.MailtrapConfig; +import io.mailtrap.http.RequestData; +import io.mailtrap.model.request.emaillogs.EmailLogFilter; +import io.mailtrap.model.request.emaillogs.EmailLogsListFilters; +import io.mailtrap.model.response.emaillogs.EmailLogsListResponse; +import io.mailtrap.model.response.emaillogs.EmailLogMessage; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public class EmailLogsImpl extends ApiResource implements EmailLogs { + + public EmailLogsImpl(final MailtrapConfig config) { + super(config); + this.apiHost = Constants.GENERAL_HOST; + } + + @Override + public EmailLogsListResponse list(final long accountId, final String searchAfter, + final EmailLogsListFilters filters) { + final String queryString = buildQueryString(searchAfter, filters); + final String url = String.format("%s/api/accounts/%d/email_logs", apiHost, accountId) + + (queryString.isEmpty() ? "" : "?" + queryString); + + return httpClient.get(url, new RequestData(), EmailLogsListResponse.class); + } + + @Override + public EmailLogMessage get(final long accountId, final String sendingMessageId) { + if (sendingMessageId == null || sendingMessageId.isBlank()) { + throw new IllegalArgumentException("sendingMessageId must not be null or blank"); + } + final String url = String.format("%s/api/accounts/%d/email_logs/%s", apiHost, accountId, sendingMessageId); + return httpClient.get(url, new RequestData(), EmailLogMessage.class); + } + + private static String buildQueryString(final String searchAfter, final EmailLogsListFilters filters) { + final List params = new ArrayList<>(); + + if (searchAfter != null && !searchAfter.isBlank()) { + params.add(enc("search_after") + "=" + enc(searchAfter)); + } + + if (filters != null) { + appendFilter(params, "sent_after", filters.getSentAfter()); + appendFilter(params, "sent_before", filters.getSentBefore()); + appendOperatorValue(params, "to", filters.getTo()); + appendOperatorValue(params, "from", filters.getFrom()); + appendOperatorValue(params, "subject", filters.getSubject()); + appendOperatorValue(params, "status", filters.getStatus()); + appendOperatorValue(params, "events", filters.getEvents()); + appendOperatorValue(params, "clicks_count", filters.getClicksCount()); + appendOperatorValue(params, "opens_count", filters.getOpensCount()); + appendOperatorValue(params, "client_ip", filters.getClientIp()); + appendOperatorValue(params, "sending_ip", filters.getSendingIp()); + appendOperatorValue(params, "email_service_provider_response", filters.getEmailServiceProviderResponse()); + appendOperatorValue(params, "email_service_provider", filters.getEmailServiceProvider()); + appendOperatorValue(params, "recipient_mx", filters.getRecipientMx()); + appendOperatorValue(params, "category", filters.getCategory()); + appendOperatorValue(params, "sending_domain_id", filters.getSendingDomainId()); + appendOperatorValue(params, "sending_stream", filters.getSendingStream()); + } + + return String.join("&", params); + } + + private static void appendFilter(final List params, final String key, final String value) { + if (value != null && !value.isBlank()) { + params.add(enc("filters[" + key + "]") + "=" + enc(value)); + } + } + + private static void appendOperatorValue(final List params, final String field, final EmailLogFilter spec) { + if (spec == null) + return; + final String operator = spec.getOperatorString(); + if (operator == null || operator.isBlank()) + return; + params.add(enc("filters[" + field + "][operator]") + "=" + enc(operator)); + final Object value = spec.getValue(); + if (value != null) { + for (final String v : toValueList(value)) { + params.add(enc("filters[" + field + "][value]") + "=" + enc(String.valueOf(v))); + } + } + } + + private static List toValueList(final Object value) { + if (value instanceof Collection c) { + return c.stream() + .filter(v -> v != null) + .map(String::valueOf) + .collect(Collectors.toList()); + } + return Collections.singletonList(String.valueOf(value)); + } + + private static String enc(final String s) { + return URLEncoder.encode(s, StandardCharsets.UTF_8); + } +} diff --git a/src/main/java/io/mailtrap/client/api/MailtrapEmailSendingApi.java b/src/main/java/io/mailtrap/client/api/MailtrapEmailSendingApi.java index acfb776..c749ed7 100644 --- a/src/main/java/io/mailtrap/client/api/MailtrapEmailSendingApi.java +++ b/src/main/java/io/mailtrap/client/api/MailtrapEmailSendingApi.java @@ -1,5 +1,6 @@ package io.mailtrap.client.api; +import io.mailtrap.api.emaillogs.EmailLogs; import io.mailtrap.api.sendingdomains.SendingDomains; import io.mailtrap.api.sendingemails.SendingEmails; import io.mailtrap.api.suppressions.Suppressions; @@ -17,4 +18,5 @@ public class MailtrapEmailSendingApi { private final SendingEmails emails; private final SendingDomains domains; private final Suppressions suppressions; + private final EmailLogs emailLogs; } diff --git a/src/main/java/io/mailtrap/factory/MailtrapClientFactory.java b/src/main/java/io/mailtrap/factory/MailtrapClientFactory.java index 572e523..6c56a16 100644 --- a/src/main/java/io/mailtrap/factory/MailtrapClientFactory.java +++ b/src/main/java/io/mailtrap/factory/MailtrapClientFactory.java @@ -17,6 +17,7 @@ import io.mailtrap.api.messages.MessagesImpl; import io.mailtrap.api.permissions.PermissionsImpl; import io.mailtrap.api.projects.ProjectsImpl; +import io.mailtrap.api.emaillogs.EmailLogsImpl; import io.mailtrap.api.sendingdomains.SendingDomainsImpl; import io.mailtrap.api.sendingemails.SendingEmailsImpl; import io.mailtrap.api.suppressions.SuppressionsImpl; @@ -34,20 +35,22 @@ public final class MailtrapClientFactory { /** - * Creates a new instance of {@link MailtrapValidator} using the default validator factory. - * Intentionally not wrapped into try-with-resources to not close, as per Jakarta doc, after - * the {@code ValidatorFactory} instance is closed, calling the following methods is not allowed: + * Creates a new instance of {@link MailtrapValidator} using the default + * validator factory. + * Intentionally not wrapped into try-with-resources to not close, as per + * Jakarta doc, after + * the {@code ValidatorFactory} instance is closed, calling the following + * methods is not allowed: *
    - *
  • methods of this {@code ValidatorFactory} instance
  • - *
  • methods of {@link Validator} instances created by this - * {@code ValidatorFactory}
  • + *
  • methods of this {@code ValidatorFactory} instance
  • + *
  • methods of {@link Validator} instances created by this + * {@code ValidatorFactory}
  • *
*/ - private static final jakarta.validation.ValidatorFactory VALIDATOR_FACTORY = - Validation.buildDefaultValidatorFactory(); - - private static final MailtrapValidator VALIDATOR = - new MailtrapValidator(VALIDATOR_FACTORY.getValidator()); + private static final jakarta.validation.ValidatorFactory VALIDATOR_FACTORY = Validation + .buildDefaultValidatorFactory(); + + private static final MailtrapValidator VALIDATOR = new MailtrapValidator(VALIDATOR_FACTORY.getValidator()); private MailtrapClientFactory() { } @@ -68,7 +71,8 @@ public static MailtrapClient createMailtrapClient(final MailtrapConfig config) { final var sendingContextHolder = configureSendingContext(config); - return new MailtrapClient(sendingApi, testingApi, bulkSendingApi, generalApi, contactsApi, emailTemplatesApi, sendingContextHolder); + return new MailtrapClient(sendingApi, testingApi, bulkSendingApi, generalApi, contactsApi, emailTemplatesApi, + sendingContextHolder); } private static MailtrapContactsApi createContactsApi(final MailtrapConfig config) { @@ -79,7 +83,8 @@ private static MailtrapContactsApi createContactsApi(final MailtrapConfig config final var contactExports = new ContactExportsImpl(config); final var contactEvents = new ContactEventsImpl(config); - return new MailtrapContactsApi(contactLists, contacts, contactImports, contactFields, contactExports, contactEvents); + return new MailtrapContactsApi(contactLists, contacts, contactImports, contactFields, contactExports, + contactEvents); } private static MailtrapGeneralApi createGeneralApi(final MailtrapConfig config) { @@ -95,8 +100,9 @@ private static MailtrapEmailSendingApi createSendingApi(final MailtrapConfig con final var emails = new SendingEmailsImpl(config, VALIDATOR); final var domains = new SendingDomainsImpl(config); final var suppressions = new SuppressionsImpl(config); + final var emailLogs = new EmailLogsImpl(config); - return new MailtrapEmailSendingApi(emails, domains, suppressions); + return new MailtrapEmailSendingApi(emails, domains, suppressions, emailLogs); } private static MailtrapEmailTestingApi createTestingApi(final MailtrapConfig config) { @@ -124,9 +130,9 @@ private static MailtrapEmailTemplatesApi createEmailTemplatesApi(final MailtrapC private static SendingContextHolder configureSendingContext(final MailtrapConfig config) { return SendingContextHolder.builder() - .sandbox(config.isSandbox()) - .inboxId(config.getInboxId()) - .bulk(config.isBulk()) - .build(); + .sandbox(config.isSandbox()) + .inboxId(config.getInboxId()) + .bulk(config.isBulk()) + .build(); } } diff --git a/src/main/java/io/mailtrap/model/request/emaillogs/EmailLogFilter.java b/src/main/java/io/mailtrap/model/request/emaillogs/EmailLogFilter.java new file mode 100644 index 0000000..246b38a --- /dev/null +++ b/src/main/java/io/mailtrap/model/request/emaillogs/EmailLogFilter.java @@ -0,0 +1,13 @@ +package io.mailtrap.model.request.emaillogs; + +/** + * Common contract for email log filters so the API can serialize operator and value. + */ +public interface EmailLogFilter { + + /** API operator string (e.g. "equal", "ci_contain"). */ + String getOperatorString(); + + /** Filter value; may be null for operators like empty/not_empty. */ + Object getValue(); +} diff --git a/src/main/java/io/mailtrap/model/request/emaillogs/EmailLogsListFilters.java b/src/main/java/io/mailtrap/model/request/emaillogs/EmailLogsListFilters.java new file mode 100644 index 0000000..eeba3db --- /dev/null +++ b/src/main/java/io/mailtrap/model/request/emaillogs/EmailLogsListFilters.java @@ -0,0 +1,36 @@ +package io.mailtrap.model.request.emaillogs; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Filters for listing email logs. All fields are optional. + * Date range uses sent_after and sent_before (ISO 8601 date-time strings). + * Other filters use concrete types so operators are enforced per field. + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class EmailLogsListFilters { + + private String sentAfter; + private String sentBefore; + private FilterCiString to; + private FilterCiString from; + private FilterOptionalString subject; + private FilterStatus status; + private FilterEvents events; + private FilterNumber clicksCount; + private FilterNumber opensCount; + private FilterString clientIp; + private FilterString sendingIp; + private FilterCiString emailServiceProviderResponse; + private FilterExactString emailServiceProvider; + private FilterExactString recipientMx; + private FilterExactString category; + private FilterSendingDomainId sendingDomainId; + private FilterSendingStream sendingStream; +} diff --git a/src/main/java/io/mailtrap/model/request/emaillogs/FilterCiString.java b/src/main/java/io/mailtrap/model/request/emaillogs/FilterCiString.java new file mode 100644 index 0000000..f6cf4b8 --- /dev/null +++ b/src/main/java/io/mailtrap/model/request/emaillogs/FilterCiString.java @@ -0,0 +1,31 @@ +package io.mailtrap.model.request.emaillogs; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Filter with case-insensitive string operators (e.g. to, from). Value may be single or list for ci_equal, ci_not_equal. + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class FilterCiString implements EmailLogFilter { + + public enum Operator { + ci_contain, ci_not_contain, ci_equal, ci_not_equal + } + + private Operator operator; + private Object value; + + @Override + public String getOperatorString() { + return operator == null ? null : operator.name(); + } + + @Override + public Object getValue() { + return value; + } +} diff --git a/src/main/java/io/mailtrap/model/request/emaillogs/FilterEvents.java b/src/main/java/io/mailtrap/model/request/emaillogs/FilterEvents.java new file mode 100644 index 0000000..8bc4ce9 --- /dev/null +++ b/src/main/java/io/mailtrap/model/request/emaillogs/FilterEvents.java @@ -0,0 +1,61 @@ +package io.mailtrap.model.request.emaillogs; + +import io.mailtrap.model.response.emaillogs.EmailLogEventType; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * Filter for events. Value may be single or list for include_event, + * not_include_event. + * Use {@link EmailLogEventType} for type-safe event values. + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class FilterEvents implements EmailLogFilter { + + public enum Operator { + include_event, not_include_event + } + + private Operator operator; + /** + * {@link EmailLogEventType}, List of {@link EmailLogEventType}, or string + * value. + */ + private Object value; + + /** Convenience constructor for a single event type. */ + public FilterEvents(Operator operator, EmailLogEventType eventType) { + this.operator = operator; + this.value = eventType; + } + + /** Convenience constructor for multiple event types. */ + public FilterEvents(Operator operator, List eventTypes) { + this.operator = operator; + this.value = eventTypes; + } + + @Override + public String getOperatorString() { + return operator == null ? null : operator.name(); + } + + @Override + public Object getValue() { + if (value instanceof EmailLogEventType) { + return ((EmailLogEventType) value).getValue(); + } + if (value instanceof List list) { + return list.stream() + .map(v -> v instanceof EmailLogEventType ? ((EmailLogEventType) v).getValue() : String.valueOf(v)) + .collect(Collectors.toList()); + } + return value; + } +} diff --git a/src/main/java/io/mailtrap/model/request/emaillogs/FilterExactString.java b/src/main/java/io/mailtrap/model/request/emaillogs/FilterExactString.java new file mode 100644 index 0000000..ac62d51 --- /dev/null +++ b/src/main/java/io/mailtrap/model/request/emaillogs/FilterExactString.java @@ -0,0 +1,31 @@ +package io.mailtrap.model.request.emaillogs; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Filter with exact match (e.g. category, email_service_provider). Value may be single or list. + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class FilterExactString implements EmailLogFilter { + + public enum Operator { + equal, not_equal + } + + private Operator operator; + private Object value; + + @Override + public String getOperatorString() { + return operator == null ? null : operator.name(); + } + + @Override + public Object getValue() { + return value; + } +} diff --git a/src/main/java/io/mailtrap/model/request/emaillogs/FilterNumber.java b/src/main/java/io/mailtrap/model/request/emaillogs/FilterNumber.java new file mode 100644 index 0000000..9aa97fe --- /dev/null +++ b/src/main/java/io/mailtrap/model/request/emaillogs/FilterNumber.java @@ -0,0 +1,28 @@ +package io.mailtrap.model.request.emaillogs; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class FilterNumber implements EmailLogFilter { + + public enum Operator { + equal, greater_than, less_than + } + + private Operator operator; + private Integer value; + + @Override + public String getOperatorString() { + return operator == null ? null : operator.name(); + } + + @Override + public Object getValue() { + return value; + } +} diff --git a/src/main/java/io/mailtrap/model/request/emaillogs/FilterOptionalString.java b/src/main/java/io/mailtrap/model/request/emaillogs/FilterOptionalString.java new file mode 100644 index 0000000..15d0d00 --- /dev/null +++ b/src/main/java/io/mailtrap/model/request/emaillogs/FilterOptionalString.java @@ -0,0 +1,37 @@ +package io.mailtrap.model.request.emaillogs; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Filter for optional string (e.g. subject). Value optional for empty, not_empty. + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class FilterOptionalString implements EmailLogFilter { + + public enum Operator { + ci_contain, ci_not_contain, ci_equal, ci_not_equal, empty, not_empty + } + + private Operator operator; + private String value; + + /** Constructor for operators that do not require a value (e.g. empty, not_empty). */ + public FilterOptionalString(Operator operator) { + this.operator = operator; + this.value = null; + } + + @Override + public String getOperatorString() { + return operator == null ? null : operator.name(); + } + + @Override + public Object getValue() { + return value; + } +} diff --git a/src/main/java/io/mailtrap/model/request/emaillogs/FilterSendingDomainId.java b/src/main/java/io/mailtrap/model/request/emaillogs/FilterSendingDomainId.java new file mode 100644 index 0000000..fc9b79b --- /dev/null +++ b/src/main/java/io/mailtrap/model/request/emaillogs/FilterSendingDomainId.java @@ -0,0 +1,31 @@ +package io.mailtrap.model.request.emaillogs; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Filter for sending_domain_id. Value may be single integer or list. + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class FilterSendingDomainId implements EmailLogFilter { + + public enum Operator { + equal, not_equal + } + + private Operator operator; + private Object value; + + @Override + public String getOperatorString() { + return operator == null ? null : operator.name(); + } + + @Override + public Object getValue() { + return value; + } +} diff --git a/src/main/java/io/mailtrap/model/request/emaillogs/FilterSendingStream.java b/src/main/java/io/mailtrap/model/request/emaillogs/FilterSendingStream.java new file mode 100644 index 0000000..32a2d2e --- /dev/null +++ b/src/main/java/io/mailtrap/model/request/emaillogs/FilterSendingStream.java @@ -0,0 +1,31 @@ +package io.mailtrap.model.request.emaillogs; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Filter for sending_stream. Value typically "transactional" or "bulk"; may be single or list. + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class FilterSendingStream implements EmailLogFilter { + + public enum Operator { + equal, not_equal + } + + private Operator operator; + private Object value; + + @Override + public String getOperatorString() { + return operator == null ? null : operator.name(); + } + + @Override + public Object getValue() { + return value; + } +} diff --git a/src/main/java/io/mailtrap/model/request/emaillogs/FilterStatus.java b/src/main/java/io/mailtrap/model/request/emaillogs/FilterStatus.java new file mode 100644 index 0000000..43e4d4f --- /dev/null +++ b/src/main/java/io/mailtrap/model/request/emaillogs/FilterStatus.java @@ -0,0 +1,57 @@ +package io.mailtrap.model.request.emaillogs; + +import io.mailtrap.model.response.emaillogs.MessageStatus; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * Filter for status. Value may be single or list for equal, not_equal. + * Use {@link MessageStatus} for type-safe status values. + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class FilterStatus implements EmailLogFilter { + + public enum Operator { + equal, not_equal + } + + private Operator operator; + /** {@link MessageStatus}, List of {@link MessageStatus}, or string value. */ + private Object value; + + /** Convenience constructor for a single status. */ + public FilterStatus(Operator operator, MessageStatus status) { + this.operator = operator; + this.value = status; + } + + /** Convenience constructor for multiple statuses. */ + public FilterStatus(Operator operator, List statuses) { + this.operator = operator; + this.value = statuses; + } + + @Override + public String getOperatorString() { + return operator == null ? null : operator.name(); + } + + @Override + public Object getValue() { + if (value instanceof MessageStatus) { + return ((MessageStatus) value).getValue(); + } + if (value instanceof List list) { + return list.stream() + .map(v -> v instanceof MessageStatus ? ((MessageStatus) v).getValue() : String.valueOf(v)) + .collect(Collectors.toList()); + } + return value; + } +} diff --git a/src/main/java/io/mailtrap/model/request/emaillogs/FilterString.java b/src/main/java/io/mailtrap/model/request/emaillogs/FilterString.java new file mode 100644 index 0000000..79c17e9 --- /dev/null +++ b/src/main/java/io/mailtrap/model/request/emaillogs/FilterString.java @@ -0,0 +1,31 @@ +package io.mailtrap.model.request.emaillogs; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Filter with string operators (e.g. client_ip, sending_ip). + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class FilterString implements EmailLogFilter { + + public enum Operator { + equal, not_equal, contain, not_contain + } + + private Operator operator; + private String value; + + @Override + public String getOperatorString() { + return operator == null ? null : operator.name(); + } + + @Override + public Object getValue() { + return value; + } +} diff --git a/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogEventType.java b/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogEventType.java new file mode 100644 index 0000000..d70bd53 --- /dev/null +++ b/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogEventType.java @@ -0,0 +1,40 @@ +package io.mailtrap.model.response.emaillogs; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Type of event in an email log (e.g. delivery, open, click). + */ +public enum EmailLogEventType { + DELIVERY("delivery"), + OPEN("open"), + CLICK("click"), + SOFT_BOUNCE("soft_bounce"), + BOUNCE("bounce"), + SPAM("spam"), + UNSUBSCRIBE("unsubscribe"), + SUSPENSION("suspension"), + REJECT("reject"); + + private final String value; + + EmailLogEventType(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } + + @JsonCreator + public static EmailLogEventType fromValue(String value) { + for (EmailLogEventType type : EmailLogEventType.values()) { + if (type.value.equalsIgnoreCase(value)) { + return type; + } + } + throw new IllegalArgumentException("Unknown EmailLogEventType value: " + value); + } +} diff --git a/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessage.java b/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessage.java new file mode 100644 index 0000000..b5f7b74 --- /dev/null +++ b/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessage.java @@ -0,0 +1,79 @@ +package io.mailtrap.model.response.emaillogs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.mailtrap.model.response.suppressions.SendingStream; +import lombok.Data; + +import java.time.OffsetDateTime; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +@Data +public class EmailLogMessage { + + @JsonProperty("message_id") + private String messageId; + + @JsonProperty("status") + private MessageStatus status; + + @JsonProperty("subject") + private String subject; + + @JsonProperty("from") + private String from; + + @JsonProperty("to") + private String to; + + @JsonProperty("sent_at") + private OffsetDateTime sentAt; + + @JsonProperty("client_ip") + private String clientIp; + + @JsonProperty("category") + private String category; + + @JsonProperty("custom_variables") + private Map customVariables; + + @JsonProperty("sending_stream") + private SendingStream sendingStream; + + @JsonProperty("sending_domain_id") + private Integer sendingDomainId; + + @JsonProperty("template_id") + private Integer templateId; + + @JsonProperty("template_variables") + private Map templateVariables; + + @JsonProperty("opens_count") + private Integer opensCount; + + @JsonProperty("clicks_count") + private Integer clicksCount; + + @JsonProperty("raw_message_url") + private String rawMessageUrl; + + @JsonProperty("events") + private List events; + + /** + * Returns custom variables; never null (empty map if not set). + */ + public Map getCustomVariables() { + return customVariables == null ? Collections.emptyMap() : customVariables; + } + + /** + * Returns template variables; never null (empty map if not set). + */ + public Map getTemplateVariables() { + return templateVariables == null ? Collections.emptyMap() : templateVariables; + } +} diff --git a/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessageEvent.java b/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessageEvent.java new file mode 100644 index 0000000..eaf51c0 --- /dev/null +++ b/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessageEvent.java @@ -0,0 +1,38 @@ +package io.mailtrap.model.response.emaillogs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +import java.time.OffsetDateTime; + +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "event_type") +@JsonSubTypes({ + @JsonSubTypes.Type(value = EmailLogMessageEventDelivery.class, name = "delivery"), + @JsonSubTypes.Type(value = EmailLogMessageEventOpen.class, name = "open"), + @JsonSubTypes.Type(value = EmailLogMessageEventClick.class, name = "click"), + @JsonSubTypes.Type(value = EmailLogMessageEventSoftBounce.class, name = "soft_bounce"), + @JsonSubTypes.Type(value = EmailLogMessageEventBounce.class, name = "bounce"), + @JsonSubTypes.Type(value = EmailLogMessageEventSpam.class, name = "spam"), + @JsonSubTypes.Type(value = EmailLogMessageEventUnsubscribe.class, name = "unsubscribe"), + @JsonSubTypes.Type(value = EmailLogMessageEventSuspension.class, name = "suspension"), + @JsonSubTypes.Type(value = EmailLogMessageEventReject.class, name = "reject") +}) +public abstract class EmailLogMessageEvent { + + @JsonProperty("created_at") + public OffsetDateTime createdAt; + + public abstract EmailLogEventType getEventType(); + + public OffsetDateTime getCreatedAt() { + return createdAt; + } + + @JsonProperty("created_at") + public void setCreatedAt(OffsetDateTime createdAt) { + this.createdAt = createdAt; + } + + public abstract Object getDetails(); +} diff --git a/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessageEventBounce.java b/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessageEventBounce.java new file mode 100644 index 0000000..9b0df4c --- /dev/null +++ b/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessageEventBounce.java @@ -0,0 +1,23 @@ +package io.mailtrap.model.response.emaillogs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = false) +public class EmailLogMessageEventBounce extends EmailLogMessageEvent { + + @JsonProperty("details") + private EventDetailsBounce details; + + @Override + public EmailLogEventType getEventType() { + return EmailLogEventType.BOUNCE; + } + + @Override + public Object getDetails() { + return details; + } +} diff --git a/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessageEventClick.java b/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessageEventClick.java new file mode 100644 index 0000000..7b601af --- /dev/null +++ b/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessageEventClick.java @@ -0,0 +1,23 @@ +package io.mailtrap.model.response.emaillogs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = false) +public class EmailLogMessageEventClick extends EmailLogMessageEvent { + + @JsonProperty("details") + private EventDetailsClick details; + + @Override + public EmailLogEventType getEventType() { + return EmailLogEventType.CLICK; + } + + @Override + public Object getDetails() { + return details; + } +} diff --git a/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessageEventDelivery.java b/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessageEventDelivery.java new file mode 100644 index 0000000..aecec25 --- /dev/null +++ b/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessageEventDelivery.java @@ -0,0 +1,23 @@ +package io.mailtrap.model.response.emaillogs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = false) +public class EmailLogMessageEventDelivery extends EmailLogMessageEvent { + + @JsonProperty("details") + private EventDetailsDelivery details; + + @Override + public EmailLogEventType getEventType() { + return EmailLogEventType.DELIVERY; + } + + @Override + public Object getDetails() { + return details; + } +} diff --git a/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessageEventOpen.java b/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessageEventOpen.java new file mode 100644 index 0000000..9c775a4 --- /dev/null +++ b/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessageEventOpen.java @@ -0,0 +1,23 @@ +package io.mailtrap.model.response.emaillogs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = false) +public class EmailLogMessageEventOpen extends EmailLogMessageEvent { + + @JsonProperty("details") + private EventDetailsOpen details; + + @Override + public EmailLogEventType getEventType() { + return EmailLogEventType.OPEN; + } + + @Override + public Object getDetails() { + return details; + } +} diff --git a/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessageEventReject.java b/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessageEventReject.java new file mode 100644 index 0000000..1e2b018 --- /dev/null +++ b/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessageEventReject.java @@ -0,0 +1,23 @@ +package io.mailtrap.model.response.emaillogs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = false) +public class EmailLogMessageEventReject extends EmailLogMessageEvent { + + @JsonProperty("details") + private EventDetailsReject details; + + @Override + public EmailLogEventType getEventType() { + return EmailLogEventType.REJECT; + } + + @Override + public Object getDetails() { + return details; + } +} diff --git a/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessageEventSoftBounce.java b/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessageEventSoftBounce.java new file mode 100644 index 0000000..7b59c85 --- /dev/null +++ b/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessageEventSoftBounce.java @@ -0,0 +1,23 @@ +package io.mailtrap.model.response.emaillogs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = false) +public class EmailLogMessageEventSoftBounce extends EmailLogMessageEvent { + + @JsonProperty("details") + private EventDetailsBounce details; + + @Override + public EmailLogEventType getEventType() { + return EmailLogEventType.SOFT_BOUNCE; + } + + @Override + public Object getDetails() { + return details; + } +} diff --git a/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessageEventSpam.java b/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessageEventSpam.java new file mode 100644 index 0000000..64191d7 --- /dev/null +++ b/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessageEventSpam.java @@ -0,0 +1,23 @@ +package io.mailtrap.model.response.emaillogs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = false) +public class EmailLogMessageEventSpam extends EmailLogMessageEvent { + + @JsonProperty("details") + private EventDetailsSpam details; + + @Override + public EmailLogEventType getEventType() { + return EmailLogEventType.SPAM; + } + + @Override + public Object getDetails() { + return details; + } +} diff --git a/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessageEventSuspension.java b/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessageEventSuspension.java new file mode 100644 index 0000000..5f46168 --- /dev/null +++ b/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessageEventSuspension.java @@ -0,0 +1,23 @@ +package io.mailtrap.model.response.emaillogs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = false) +public class EmailLogMessageEventSuspension extends EmailLogMessageEvent { + + @JsonProperty("details") + private EventDetailsReject details; + + @Override + public EmailLogEventType getEventType() { + return EmailLogEventType.SUSPENSION; + } + + @Override + public Object getDetails() { + return details; + } +} diff --git a/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessageEventUnsubscribe.java b/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessageEventUnsubscribe.java new file mode 100644 index 0000000..5a1e99c --- /dev/null +++ b/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessageEventUnsubscribe.java @@ -0,0 +1,23 @@ +package io.mailtrap.model.response.emaillogs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = false) +public class EmailLogMessageEventUnsubscribe extends EmailLogMessageEvent { + + @JsonProperty("details") + private EventDetailsUnsubscribe details; + + @Override + public EmailLogEventType getEventType() { + return EmailLogEventType.UNSUBSCRIBE; + } + + @Override + public Object getDetails() { + return details; + } +} diff --git a/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessageSummary.java b/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessageSummary.java new file mode 100644 index 0000000..b17b810 --- /dev/null +++ b/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogMessageSummary.java @@ -0,0 +1,72 @@ +package io.mailtrap.model.response.emaillogs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.mailtrap.model.response.suppressions.SendingStream; +import lombok.Data; + +import java.time.OffsetDateTime; +import java.util.Collections; +import java.util.Map; + +@Data +public class EmailLogMessageSummary { + + @JsonProperty("message_id") + private String messageId; + + @JsonProperty("status") + private MessageStatus status; + + @JsonProperty("subject") + private String subject; + + @JsonProperty("from") + private String from; + + @JsonProperty("to") + private String to; + + @JsonProperty("sent_at") + private OffsetDateTime sentAt; + + @JsonProperty("client_ip") + private String clientIp; + + @JsonProperty("category") + private String category; + + @JsonProperty("custom_variables") + private Map customVariables; + + @JsonProperty("sending_stream") + private SendingStream sendingStream; + + @JsonProperty("sending_domain_id") + private Integer sendingDomainId; + + @JsonProperty("template_id") + private Integer templateId; + + @JsonProperty("template_variables") + private Map templateVariables; + + @JsonProperty("opens_count") + private Integer opensCount; + + @JsonProperty("clicks_count") + private Integer clicksCount; + + /** + * Returns custom variables; never null (empty map if not set). + */ + public Map getCustomVariables() { + return customVariables == null ? Collections.emptyMap() : customVariables; + } + + /** + * Returns template variables; never null (empty map if not set). + */ + public Map getTemplateVariables() { + return templateVariables == null ? Collections.emptyMap() : templateVariables; + } +} diff --git a/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogsListResponse.java b/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogsListResponse.java new file mode 100644 index 0000000..3e7809b --- /dev/null +++ b/src/main/java/io/mailtrap/model/response/emaillogs/EmailLogsListResponse.java @@ -0,0 +1,19 @@ +package io.mailtrap.model.response.emaillogs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +@Data +public class EmailLogsListResponse { + + @JsonProperty("messages") + private List messages; + + @JsonProperty("total_count") + private int totalCount; + + @JsonProperty("next_page_cursor") + private String nextPageCursor; +} diff --git a/src/main/java/io/mailtrap/model/response/emaillogs/EventDetailsBounce.java b/src/main/java/io/mailtrap/model/response/emaillogs/EventDetailsBounce.java new file mode 100644 index 0000000..1d516c5 --- /dev/null +++ b/src/main/java/io/mailtrap/model/response/emaillogs/EventDetailsBounce.java @@ -0,0 +1,26 @@ +package io.mailtrap.model.response.emaillogs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class EventDetailsBounce { + + @JsonProperty("sending_ip") + private String sendingIp; + + @JsonProperty("recipient_mx") + private String recipientMx; + + @JsonProperty("email_service_provider") + private String emailServiceProvider; + + @JsonProperty("email_service_provider_status") + private String emailServiceProviderStatus; + + @JsonProperty("email_service_provider_response") + private String emailServiceProviderResponse; + + @JsonProperty("bounce_category") + private String bounceCategory; +} diff --git a/src/main/java/io/mailtrap/model/response/emaillogs/EventDetailsClick.java b/src/main/java/io/mailtrap/model/response/emaillogs/EventDetailsClick.java new file mode 100644 index 0000000..3096535 --- /dev/null +++ b/src/main/java/io/mailtrap/model/response/emaillogs/EventDetailsClick.java @@ -0,0 +1,14 @@ +package io.mailtrap.model.response.emaillogs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class EventDetailsClick { + + @JsonProperty("click_url") + private String clickUrl; + + @JsonProperty("web_ip_address") + private String webIpAddress; +} diff --git a/src/main/java/io/mailtrap/model/response/emaillogs/EventDetailsDelivery.java b/src/main/java/io/mailtrap/model/response/emaillogs/EventDetailsDelivery.java new file mode 100644 index 0000000..d91f813 --- /dev/null +++ b/src/main/java/io/mailtrap/model/response/emaillogs/EventDetailsDelivery.java @@ -0,0 +1,17 @@ +package io.mailtrap.model.response.emaillogs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class EventDetailsDelivery { + + @JsonProperty("sending_ip") + private String sendingIp; + + @JsonProperty("recipient_mx") + private String recipientMx; + + @JsonProperty("email_service_provider") + private String emailServiceProvider; +} diff --git a/src/main/java/io/mailtrap/model/response/emaillogs/EventDetailsOpen.java b/src/main/java/io/mailtrap/model/response/emaillogs/EventDetailsOpen.java new file mode 100644 index 0000000..955c60b --- /dev/null +++ b/src/main/java/io/mailtrap/model/response/emaillogs/EventDetailsOpen.java @@ -0,0 +1,11 @@ +package io.mailtrap.model.response.emaillogs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class EventDetailsOpen { + + @JsonProperty("web_ip_address") + private String webIpAddress; +} diff --git a/src/main/java/io/mailtrap/model/response/emaillogs/EventDetailsReject.java b/src/main/java/io/mailtrap/model/response/emaillogs/EventDetailsReject.java new file mode 100644 index 0000000..9e8e03e --- /dev/null +++ b/src/main/java/io/mailtrap/model/response/emaillogs/EventDetailsReject.java @@ -0,0 +1,11 @@ +package io.mailtrap.model.response.emaillogs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class EventDetailsReject { + + @JsonProperty("reject_reason") + private String rejectReason; +} diff --git a/src/main/java/io/mailtrap/model/response/emaillogs/EventDetailsSpam.java b/src/main/java/io/mailtrap/model/response/emaillogs/EventDetailsSpam.java new file mode 100644 index 0000000..59debb6 --- /dev/null +++ b/src/main/java/io/mailtrap/model/response/emaillogs/EventDetailsSpam.java @@ -0,0 +1,11 @@ +package io.mailtrap.model.response.emaillogs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class EventDetailsSpam { + + @JsonProperty("spam_feedback_type") + private String spamFeedbackType; +} diff --git a/src/main/java/io/mailtrap/model/response/emaillogs/EventDetailsUnsubscribe.java b/src/main/java/io/mailtrap/model/response/emaillogs/EventDetailsUnsubscribe.java new file mode 100644 index 0000000..304b174 --- /dev/null +++ b/src/main/java/io/mailtrap/model/response/emaillogs/EventDetailsUnsubscribe.java @@ -0,0 +1,11 @@ +package io.mailtrap.model.response.emaillogs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class EventDetailsUnsubscribe { + + @JsonProperty("web_ip_address") + private String webIpAddress; +} diff --git a/src/main/java/io/mailtrap/model/response/emaillogs/MessageStatus.java b/src/main/java/io/mailtrap/model/response/emaillogs/MessageStatus.java new file mode 100644 index 0000000..fb1a00b --- /dev/null +++ b/src/main/java/io/mailtrap/model/response/emaillogs/MessageStatus.java @@ -0,0 +1,35 @@ +package io.mailtrap.model.response.emaillogs; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Status of an email in the sending log. + */ +public enum MessageStatus { + DELIVERED("delivered"), + NOT_DELIVERED("not_delivered"), + ENQUEUED("enqueued"), + OPTED_OUT("opted_out"); + + private final String value; + + MessageStatus(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } + + @JsonCreator + public static MessageStatus fromValue(String value) { + for (MessageStatus status : MessageStatus.values()) { + if (status.value.equalsIgnoreCase(value)) { + return status; + } + } + throw new IllegalArgumentException("Unknown MessageStatus value: " + value); + } +} diff --git a/src/test/java/io/mailtrap/api/emaillogs/EmailLogsImplTest.java b/src/test/java/io/mailtrap/api/emaillogs/EmailLogsImplTest.java new file mode 100644 index 0000000..2d3001a --- /dev/null +++ b/src/test/java/io/mailtrap/api/emaillogs/EmailLogsImplTest.java @@ -0,0 +1,123 @@ +package io.mailtrap.api.emaillogs; + +import io.mailtrap.Constants; +import io.mailtrap.config.MailtrapConfig; +import io.mailtrap.factory.MailtrapClientFactory; +import io.mailtrap.model.request.emaillogs.EmailLogsListFilters; +import io.mailtrap.model.request.emaillogs.FilterStatus; +import io.mailtrap.model.response.emaillogs.EmailLogEventType; +import io.mailtrap.model.response.emaillogs.EmailLogMessage; +import io.mailtrap.model.response.emaillogs.EmailLogMessageSummary; +import io.mailtrap.model.response.emaillogs.EmailLogsListResponse; +import io.mailtrap.model.response.emaillogs.MessageStatus; +import io.mailtrap.model.response.suppressions.SendingStream; +import io.mailtrap.testutils.BaseTest; +import io.mailtrap.testutils.DataMock; +import io.mailtrap.testutils.TestHttpClient; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class EmailLogsImplTest extends BaseTest { + + private static final String SENDING_MESSAGE_ID = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"; + + private EmailLogs api; + + @BeforeEach + void init() { + final String listUrl = Constants.GENERAL_HOST + "/api/accounts/" + accountId + "/email_logs"; + final String listWithCursorUrl = listUrl + "?search_after=cursor-123"; + final String listWithStatusFilterUrl = listUrl + + "?filters%5Bstatus%5D%5Boperator%5D=equal&filters%5Bstatus%5D%5Bvalue%5D=delivered"; + final String getMessageUrl = Constants.GENERAL_HOST + "/api/accounts/" + accountId + "/email_logs/" + + SENDING_MESSAGE_ID; + + final List mocks = List.of( + DataMock.build(listUrl, "GET", null, "api/emaillogs/listEmailLogsResponse.json"), + DataMock.build(listWithCursorUrl, "GET", null, "api/emaillogs/listEmailLogsResponse.json", + Collections.emptyMap()), + DataMock.build(listWithStatusFilterUrl, "GET", null, "api/emaillogs/listEmailLogsResponse.json"), + DataMock.build(getMessageUrl, "GET", null, "api/emaillogs/getEmailLogMessageResponse.json")); + + final TestHttpClient httpClient = new TestHttpClient(mocks); + final MailtrapConfig testConfig = new MailtrapConfig.Builder() + .httpClient(httpClient) + .token("dummy_token") + .build(); + + api = MailtrapClientFactory.createMailtrapClient(testConfig).sendingApi().emailLogs(); + } + + @Test + void list_withNoParams_returnsPaginatedResponse() { + final EmailLogsListResponse response = api.list(accountId, null, null); + + assertNotNull(response); + assertNotNull(response.getMessages()); + assertEquals(1, response.getMessages().size()); + assertEquals(1, response.getTotalCount()); + assertNull(response.getNextPageCursor()); + + final EmailLogMessageSummary msg = response.getMessages().get(0); + assertEquals(SENDING_MESSAGE_ID, msg.getMessageId()); + assertEquals(MessageStatus.DELIVERED, msg.getStatus()); + assertEquals("Welcome to our service", msg.getSubject()); + assertEquals("sender@example.com", msg.getFrom()); + assertEquals("recipient@example.com", msg.getTo()); + assertEquals(SendingStream.TRANSACTIONAL, msg.getSendingStream()); + assertEquals(3938, msg.getSendingDomainId()); + assertEquals(2, msg.getOpensCount()); + assertEquals(1, msg.getClicksCount()); + } + + @Test + void list_withSearchAfter_includesCursorInRequest() { + final EmailLogsListResponse response = api.list(accountId, "cursor-123", null); + + assertNotNull(response); + assertEquals(1, response.getMessages().size()); + } + + @Test + void list_withFilters_buildsQueryWithOperatorAndValue() { + final var filters = EmailLogsListFilters.builder() + .status(new FilterStatus(FilterStatus.Operator.equal, MessageStatus.DELIVERED)) + .build(); + final EmailLogsListResponse response = api.list(accountId, null, filters); + + assertNotNull(response); + assertEquals(1, response.getMessages().size()); + } + + @Test + void get_byMessageId_returnsEmailLogMessage() { + final EmailLogMessage message = api.get(accountId, SENDING_MESSAGE_ID); + + assertNotNull(message); + assertEquals(SENDING_MESSAGE_ID, message.getMessageId()); + assertEquals(MessageStatus.DELIVERED, message.getStatus()); + assertEquals("Welcome to our service", message.getSubject()); + assertNotNull(message.getRawMessageUrl()); + assertNotNull(message.getEvents()); + assertEquals(1, message.getEvents().size()); + assertEquals(EmailLogEventType.CLICK, message.getEvents().get(0).getEventType()); + } + + @Test + void get_withNullMessageId_throws() { + assertThrows(IllegalArgumentException.class, () -> api.get(accountId, null)); + } + + @Test + void get_withBlankMessageId_throws() { + assertThrows(IllegalArgumentException.class, () -> api.get(accountId, " ")); + } +} diff --git a/src/test/resources/api/emaillogs/getEmailLogMessageResponse.json b/src/test/resources/api/emaillogs/getEmailLogMessageResponse.json new file mode 100644 index 0000000..5a683fc --- /dev/null +++ b/src/test/resources/api/emaillogs/getEmailLogMessageResponse.json @@ -0,0 +1,28 @@ +{ + "message_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "status": "delivered", + "subject": "Welcome to our service", + "from": "sender@example.com", + "to": "recipient@example.com", + "sent_at": "2025-01-15T10:30:00Z", + "client_ip": "203.0.113.42", + "category": "Welcome Email", + "custom_variables": {}, + "sending_stream": "transactional", + "sending_domain_id": 3938, + "template_id": 100, + "template_variables": {}, + "opens_count": 2, + "clicks_count": 1, + "raw_message_url": "https://storage.example.com/signed/eml/a1b2c3d4-e5f6-7890-abcd-ef1234567890?token=...", + "events": [ + { + "event_type": "click", + "created_at": "2025-01-15T10:35:00Z", + "details": { + "click_url": "https://example.com/track/click/abc123", + "web_ip_address": "198.51.100.50" + } + } + ] +} diff --git a/src/test/resources/api/emaillogs/listEmailLogsResponse.json b/src/test/resources/api/emaillogs/listEmailLogsResponse.json new file mode 100644 index 0000000..026337c --- /dev/null +++ b/src/test/resources/api/emaillogs/listEmailLogsResponse.json @@ -0,0 +1,23 @@ +{ + "messages": [ + { + "message_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "status": "delivered", + "subject": "Welcome to our service", + "from": "sender@example.com", + "to": "recipient@example.com", + "sent_at": "2025-01-15T10:30:00Z", + "client_ip": "203.0.113.42", + "category": "Welcome Email", + "custom_variables": {}, + "sending_stream": "transactional", + "sending_domain_id": 3938, + "template_id": 100, + "template_variables": {}, + "opens_count": 2, + "clicks_count": 1 + } + ], + "total_count": 1, + "next_page_cursor": null +}