Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions examples/java/io/mailtrap/examples/suppressions/Suppressions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.mailtrap.examples.suppressions;

import io.mailtrap.config.MailtrapConfig;
import io.mailtrap.factory.MailtrapClientFactory;

public class Suppressions {

private static final String TOKEN = "<YOUR MAILTRAP TOKEN>";
private static final long ACCOUNT_ID = 1L;
private static final String EMAIL = "example@mailtrap.io";

public static void main(String[] args) {
final var config = new MailtrapConfig.Builder()
.token(TOKEN)
.build();

final var client = MailtrapClientFactory.createMailtrapClient(config);

var searchResponse = client.sendingApi().suppressions()
.search(ACCOUNT_ID, EMAIL);

System.out.println(searchResponse);

if (!searchResponse.isEmpty()) {
var deletedSuppression = client.sendingApi().suppressions()
.deleteSuppression(ACCOUNT_ID, searchResponse.get(0).getId());

System.out.println(deletedSuppression);
}
}
}
27 changes: 27 additions & 0 deletions src/main/java/io/mailtrap/api/suppressions/Suppressions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.mailtrap.api.suppressions;

import io.mailtrap.model.response.suppressions.SuppressionsResponse;

import java.util.List;

public interface Suppressions {

/**
* List and search suppressions by email. The endpoint returns up to 1000 suppressions per request.
*
* @param accountId - unique account ID
* @param email - search suppressions for this email
* @return a list of suppressions
*/
List<SuppressionsResponse> search(long accountId, String email);

/**
* Delete a suppression by ID. Mailtrap will no longer prevent sending to this email unless it's recorded in suppressions again.
*
* @param accountId - unique account ID
* @param suppressionId - unique suppression ID
* @return the attributes of the deleted suppression
*/
SuppressionsResponse deleteSuppression(long accountId, String suppressionId);

}
44 changes: 44 additions & 0 deletions src/main/java/io/mailtrap/api/suppressions/SuppressionsImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package io.mailtrap.api.suppressions;

import io.mailtrap.Constants;
import io.mailtrap.api.apiresource.ApiResource;
import io.mailtrap.config.MailtrapConfig;
import io.mailtrap.http.RequestData;
import io.mailtrap.model.response.suppressions.SuppressionsResponse;

import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.List;
Comment on lines +9 to +11
Copy link

@coderabbitai coderabbitai bot Sep 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Encode path segment correctly and use UTF-8 (URLEncoder here is incorrect).

URLEncoder is for form/query encoding and will turn spaces into '+', which is wrong for path segments. Also avoid platform-default charset.

Apply:

-import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
-            String.format(apiHost + "/api/accounts/%d/suppressions/%s", accountId, URLEncoder.encode(suppressionId, Charset.defaultCharset())),
+            String.format(apiHost + "/api/accounts/%d/suppressions/%s", accountId, encodePathSegment(suppressionId)),

Add this helper inside the class:

private static String encodePathSegment(String segment) {
  return URLEncoder.encode(segment, StandardCharsets.UTF_8).replace("+", "%20");
}

Also applies to: 39-39

🤖 Prompt for AI Agents
In src/main/java/io/mailtrap/api/suppressions/SuppressionsImpl.java around lines
9-11 (and also line 39), the code imports and uses URLEncoder with the
platform-default Charset which is incorrect for path segments and may produce
'+' for spaces; replace usages with a dedicated helper that URL-encodes using
StandardCharsets.UTF_8 and converts '+' to '%20', and add that helper as a
private static method inside the class (named e.g. encodePathSegment) so all
path segment encodings call it instead of raw URLEncoder calls.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vitalii-t Out of curiosity, how valid is this AI suggestion for modern Java?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vittorius, It is a valid workaround, but since the suppressionId here is a UUID, encoding is not necessary and is done "just in case."
In general, this AI bot suggests valid things, especially in the "nitpicks" section. I collect some of them, and will edit for all APIs - like adding @JsonIgnoreProperties(ignoreUnknown = true) for the response classes, null checks for enums, and so on - some polishing stuff

import java.util.Optional;

import static io.mailtrap.http.RequestData.entry;

public class SuppressionsImpl extends ApiResource implements Suppressions {

public SuppressionsImpl(MailtrapConfig config) {
super(config);
this.apiHost = Constants.GENERAL_HOST;
}

@Override
public List<SuppressionsResponse> search(long accountId, String email) {
var queryParams = RequestData.buildQueryParams(entry("email", Optional.ofNullable(email)));

return
httpClient.getList(
String.format(apiHost + "/api/accounts/%d/suppressions", accountId),
new RequestData(queryParams),
SuppressionsResponse.class
);
}

@Override
public SuppressionsResponse deleteSuppression(long accountId, String suppressionId) {
return
httpClient.delete(
String.format(apiHost + "/api/accounts/%d/suppressions/%s", accountId, URLEncoder.encode(suppressionId, Charset.defaultCharset())),
new RequestData(),
SuppressionsResponse.class
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import io.mailtrap.api.sendingdomains.SendingDomains;
import io.mailtrap.api.sendingemails.SendingEmails;
import io.mailtrap.api.suppressions.Suppressions;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Accessors;
Expand All @@ -15,4 +16,5 @@
public class MailtrapEmailSendingApi {
private final SendingEmails emails;
private final SendingDomains domains;
private final Suppressions suppressions;
}
4 changes: 3 additions & 1 deletion src/main/java/io/mailtrap/factory/MailtrapClientFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import io.mailtrap.api.projects.ProjectsImpl;
import io.mailtrap.api.sendingdomains.SendingDomainsImpl;
import io.mailtrap.api.sendingemails.SendingEmailsImpl;
import io.mailtrap.api.suppressions.SuppressionsImpl;
import io.mailtrap.api.testingemails.TestingEmailsImpl;
import io.mailtrap.client.MailtrapClient;
import io.mailtrap.client.api.*;
Expand Down Expand Up @@ -73,8 +74,9 @@ private static MailtrapGeneralApi createGeneralApi(MailtrapConfig config) {
private static MailtrapEmailSendingApi createSendingApi(MailtrapConfig config, CustomValidator customValidator) {
final var emails = new SendingEmailsImpl(config, customValidator);
final var domains = new SendingDomainsImpl(config);
final var suppressions = new SuppressionsImpl(config);

return new MailtrapEmailSendingApi(emails, domains);
return new MailtrapEmailSendingApi(emails, domains, suppressions);
}

private static MailtrapEmailTestingApi createTestingApi(MailtrapConfig config, CustomValidator customValidator) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.mailtrap.model.response.suppressions;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;

public enum SendingStream {
ANY("any"),
TRANSACTIONAL("transactional"),
BULK("bulk");

private final String value;

SendingStream(String value) {
this.value = value;
}

@JsonValue
public String getValue() {
return value;
}

@JsonCreator
public static SendingStream fromValue(String value) {
for (SendingStream type : SendingStream.values()) {
if (type.value.equalsIgnoreCase(value)) {
return type;
}
}
throw new IllegalArgumentException("Unknown value: " + value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package io.mailtrap.model.response.suppressions;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;

public enum SuppressionType {
HARD_BOUNCE("hard bounce"),
SPAM_COMPLAINT("spam complaint"),
UNSUBSCRIPTION("unsubscription"),
MANUAL_IMPORT("manual import");

private final String value;

SuppressionType(String value) {
this.value = value;
}

@JsonValue
public String getValue() {
return value;
}

@JsonCreator
public static SuppressionType fromValue(String value) {
for (SuppressionType type : SuppressionType.values()) {
if (type.value.equalsIgnoreCase(value)) {
return type;
}
}
throw new IllegalArgumentException("Unknown value: " + value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package io.mailtrap.model.response.suppressions;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;

import java.time.OffsetDateTime;

@Data
public class SuppressionsResponse {

@JsonProperty("id")
private String id;

@JsonProperty("type")
private SuppressionType type;

@JsonProperty("created_at")
private OffsetDateTime createdAt;

@JsonProperty("email")
private String email;

@JsonProperty("sending_stream")
private SendingStream sendingStream;

@JsonProperty("domain_name")
private String domainName;

@JsonProperty("message_bounce_category")
private String messageBounceCategory;

@JsonProperty("message_category")
private String messageCategory;

@JsonProperty("message_client_ip")
private String messageClientIp;

@JsonProperty("message_created_at")
private OffsetDateTime messageCreatedAt;

@JsonProperty("message_outgoing_ip")
private String messageOutgoingIp;

@JsonProperty("message_recipient_mx_name")
private String messageRecipientMxName;

@JsonProperty("message_sender_email")
private String messageSenderEmail;

@JsonProperty("message_subject")
private String messageSubject;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package io.mailtrap.api.suppressions;

import io.mailtrap.Constants;
import io.mailtrap.config.MailtrapConfig;
import io.mailtrap.factory.MailtrapClientFactory;
import io.mailtrap.model.response.suppressions.SendingStream;
import io.mailtrap.model.response.suppressions.SuppressionType;
import io.mailtrap.model.response.suppressions.SuppressionsResponse;
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.List;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

class SuppressionsImplTest extends BaseTest {

private Suppressions api;

@BeforeEach
public void init() {
TestHttpClient httpClient = new TestHttpClient(List.of(
DataMock.build(
Constants.GENERAL_HOST + "/api/accounts/" + accountId + "/suppressions",
"GET", null, "api/suppressions/searchSuppressions.json",
Map.of("email", email)
),
DataMock.build(
Constants.GENERAL_HOST + "/api/accounts/" + accountId + "/suppressions/" + suppressionIdEncoded,
"DELETE", null, "api/suppressions/deleteSuppression.json"
)
));

MailtrapConfig testConfig = new MailtrapConfig.Builder()
.httpClient(httpClient)
.token("dummy_token")
.build();

api = MailtrapClientFactory.createMailtrapClient(testConfig).sendingApi().suppressions();
}

@Test
void test_search() {
List<SuppressionsResponse> searchResponse = api.search(accountId, email);

assertEquals(1, searchResponse.size());
assertEquals(suppressionId, searchResponse.get(0).getId());
assertEquals(email, searchResponse.get(0).getEmail());
assertEquals(SendingStream.BULK, searchResponse.get(0).getSendingStream());
assertEquals(SuppressionType.SPAM_COMPLAINT, searchResponse.get(0).getType());
}

@Test
void test_deleteSuppression() {
SuppressionsResponse deleted = api.deleteSuppression(accountId, suppressionId);

assertNotNull(deleted);
assertEquals(suppressionId, deleted.getId());
assertEquals(email, deleted.getEmail());
assertEquals(SendingStream.BULK, deleted.getSendingStream());
assertEquals(SuppressionType.SPAM_COMPLAINT, deleted.getType());
}
}
3 changes: 2 additions & 1 deletion src/test/java/io/mailtrap/testutils/BaseTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ public class BaseTest {
protected final String contactUUID = "018dd5e3-f6d2-7c00-8f9b-e5c3f2d8a132";
protected final String contactUUIDEncoded = URLEncoder.encode(contactUUID, Charset.defaultCharset());
protected final long importId = 1L;
protected final long fieldId = 1L;
protected final long getFieldId = 777L;
protected final long updateFieldId = 999L;
protected final long deleteFieldId = 1111L;
protected final String suppressionId = "2fe148b8-b019-431f-ab3f-107663fdf868";
protected final String suppressionIdEncoded = URLEncoder.encode(suppressionId, Charset.defaultCharset());
}
16 changes: 16 additions & 0 deletions src/test/resources/api/suppressions/deleteSuppression.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"id": "2fe148b8-b019-431f-ab3f-107663fdf868",
"type": "spam complaint",
"created_at": "2025-09-03T00:40:44.161Z",
"email": "email@mailtrap.io",
"sending_stream": "bulk",
"domain_name": "sender.com",
"message_bounce_category": null,
"message_category": "Welcome email",
"message_client_ip": "123.111.123.111",
"message_created_at": "2025-09-02T00:40:44.161Z",
"message_outgoing_ip": "1.1.1.1",
"message_recipient_mx_name": "Other Providers",
"message_sender_email": "hello-sender@sender.com",
"message_subject": "Welcome!"
}
18 changes: 18 additions & 0 deletions src/test/resources/api/suppressions/searchSuppressions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[
{
"id": "2fe148b8-b019-431f-ab3f-107663fdf868",
"type": "spam complaint",
"created_at": "2025-09-03T00:40:44.161Z",
"email": "email@mailtrap.io",
"sending_stream": "bulk",
"domain_name": "sender.com",
"message_bounce_category": null,
"message_category": "Welcome email",
"message_client_ip": "123.111.123.111",
"message_created_at": "2025-09-02T00:40:44.161Z",
"message_outgoing_ip": "1.1.1.1",
"message_recipient_mx_name": "Other Providers",
"message_sender_email": "hello-sender@sender.com",
"message_subject": "Welcome!"
}
]