Skip to content
Open
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
135 changes: 135 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Changelog

All notable changes to the Mailinator Java Client SDK will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

---

## [2.5.1] - 2026-03-23

### Fixed

- `PrivateWebhookRequest`: removed incorrect `resolveTemplate("wh-token", ...)` call and properly use `whtoken` query parameter.

---

## [2.5.0] - 2026-03-23

### Added

- `GetMessageSummaryRequest` + `MessageSummaryResponse` / `MessageSummary`: fetch a message summary (`GET /api/v2/domains/{domain}/messages/{id}/summary`).
- `GetMessageTextRequest` + `MessageTextResponse`: fetch the text body of a message (`GET /api/v2/domains/{domain}/messages/{id}/text`).
- `GetMessageTextPlainRequest`: fetch the plain-text variant of a message body (`GET /api/v2/domains/{domain}/messages/{id}/textplain`), returns raw `JSONObject`.
- `GetMessageTextHtmlRequest`: fetch the HTML variant of a message body (`GET /api/v2/domains/{domain}/messages/{id}/texthtml`), returns raw `JSONObject`.
- `GetMessageHeadersRequest` + `MessageHeadersResponse`: fetch all message headers (`GET /api/v2/domains/{domain}/messages/{id}/headers`).
- `GetStreamDomainMessagesRequest`: long-poll stream for the next message arriving in any inbox of a domain (`GET /api/v2/domains/{domain}/stream`). Pings are transparently skipped; result is returned as an `Inbox`.
- `GetStreamInboxMessagesRequest`: long-poll stream for the next message arriving in a specific inbox (`GET /api/v2/domains/{domain}/stream/{inbox}`).
- New integration tests for all seven request classes above, gated on `MAILINATOR_TEST_REAL_MESSAGE_ID` (or existing env vars where applicable).
- `MAILINATOR_TEST_REAL_MESSAGE_ID` constant added to `TestEnv`.
- Test resilience for plan-limited features: stream tests gated on MAILINATOR_TEST_ENABLE_STREAM, attachment/text/headers/summary tests skip gracefully on HTTP 500, delete/wait params skip if not honored.

### Fixed

- `GetInboxRequest`: when `inbox` is `null`, the URL template now resolves to `*` (i.e. `/inboxes/*`) instead of an empty path segment, matching the Mailinator wildcard-inbox convention.
- `User-Agent` header version is now derived automatically from the POM version via Maven resource filtering — the string `Mailinator SDK - Java V{version}` is resolved at build time, so no manual update is required on each release.

### Changed

- Deprecated test classes now carry `@Disabled` annotations with descriptive messages so they are skipped rather than executed: all six rule tests, `CreateDomainRequestTest`, `DeleteDomainRequestTest`, `GetAuthenticatorsRequestTest`, `GetAuthenticatorRequestTest`, `GetAuthenticatorByIdRequestTest`, `GetLatestMessagesRequestTest`, `GetLatestInboxMessagesRequestTest`.

---

## [2.4.0] - 2026-03-23

### Deprecated

- **Rule management** (`CreateRuleRequest`, `DeleteRuleRequest`, `DisableRuleRequest`, `EnableRuleRequest`, `GetRuleRequest`, `GetRulesRequest`): rules engine endpoints have been deprecated in the Mailinator API and will be removed in a future release.
- **Domain management** (`CreateDomainRequest`, `DeleteDomainRequest`): domain creation and deletion via the SDK are deprecated; use the Mailinator web UI instead.
- **Authenticator list endpoints** (`GetAuthenticatorRequest`, `GetAuthenticatorByIdRequest`, `GetAuthenticatorsRequest`): not present in the current OpenAPI specification; use `GetAuthenticatorsByIdRequest` for authenticator lookup.
- **Legacy wildcard message endpoints** (`GetLatestMessagesRequest`, `GetLatestInboxMessagesRequest`): removed from the current OpenAPI specification and known to return server errors; use `GetInboxRequest` instead.

### Added

- `GetSmsInboxRequest`: added full inbox-list query parameter support — `skip`, `limit`, `sort`, `decode_subject`, `cursor`, `full`, `wait`, `delete` — to align with the OpenAPI contract.
- `GetInboxMessageRequest`: added optional `delete` query parameter, allowing a message to be deleted upon retrieval (mirrors `GetMessageRequest` behaviour).

### Fixed

- Updated `USER_AGENT` header to `Mailinator SDK - Java V2.4`.

---

## [2.3.0] - 2026-03-23

### Changed

- **Replaced Jackson with json-simple for all JSON serialization/deserialization.**
Jackson's strict schema validation (`UnrecognizedPropertyException`) caused failures whenever the Mailinator API added new fields. json-simple silently ignores unknown fields, making the SDK resilient to API schema evolution.

#### Infrastructure

- `pom.xml`: Removed `jersey-media-json-jackson` dependency; added `com.googlecode.json-simple:json-simple:1.1.1`.
- `JerseyClient.java`: Removed `JacksonFeature` registration from Jersey client configuration.
- `JsonUtils.java` _(new)_: Added utility class with helpers `parseObject`, `getString`, `getInteger`, `getLong`, `getBoolean`, `getObject`, `getList`, and `getStringList` for safe json-simple parsing.

#### Model / DTO classes — Jackson annotations removed, manual `fromJson(JSONObject)` factory methods added

- `ResponseStatus`
- `DeletedMessages`
- `PrivateWebhookResponse`
- `EmailLogEntry`
- `LinkEntity`
- `MembersItem`
- `SmsNumbersItem`
- `PrivateDomainsItem`
- `Retrieved`
- `Sent`
- `PlanData`
- `Authenticator` (field names also normalised: `Id→id`, `TimeStep→timeStep`, etc.)
- `GetAuthenticatorsResponse`
- `InstantTOTP2FACodeResponse`
- `Links`
- `LinksFull`
- `Attachment`
- `Attachments`
- `Part`
- `SmtpLog`
- `Message`
- `Inbox`
- `PostedMessage`
- `StatsItem`
- `Stats`
- `TeamInfo`
- `Team`
- `Domain`
- `Domains`
- `ActionData` — added `toJSON()` for request serialization
- `ConditionData` — added `toJSON()` for request serialization
- `Action` — added `toJSON()` for request serialization
- `Condition` — added `toJSON()` for request serialization
- `Rule`
- `Rules`
- `RuleToCreate` — added `toJSON()` (request body only)
- `Webhook` — added `toJSON()` (request body only)
- `MessageToPost` — added `toJSON()` (request body only)

#### Request classes — Jersey now deserializes to `String` and `fromJson` is used instead of POJO binding

- **authenticator**: `GetAuthenticatorByIdRequest`, `GetAuthenticatorsByIdRequest`, `GetAuthenticatorRequest`, `GetAuthenticatorsRequest`, `InstantTOTP2FACodeRequest`
- **domain**: `CreateDomainRequest`, `DeleteDomainRequest`, `GetDomainRequest`, `GetDomainsRequest`
- **message**: `DeleteDomainMessagesRequest`, `DeleteInboxMessagesRequest`, `DeleteMessageRequest`, `GetInboxMessageAttachmentsRequest`, `GetInboxMessageLinksRequest`, `GetInboxMessageRequest`, `GetInboxMessageSmtpLogRequest`, `GetInboxRequest`, `GetLatestInboxMessagesRequest`, `GetLatestMessagesRequest`, `GetMessageAttachmentsRequest`, `GetMessageLinksFullRequest`, `GetMessageLinksRequest`, `GetMessageRequest`, `GetMessageSmtpLogRequest`, `GetSmsInboxRequest`, `PostMessageRequest`
- **rule**: `CreateRuleRequest`, `DeleteRuleRequest`, `DisableRuleRequest`, `EnableRuleRequest`, `GetRuleRequest`, `GetRulesRequest`
- **stats**: `GetStatsRequest`, `GetTeamInfoRequest`, `GetTeamRequest`
- **webhook**: `PrivateWebhookRequest`, `PrivateInboxWebhookRequest`, `PrivateCustomServiceWebhookRequest`, `PrivateCustomServiceInboxWebhookRequest`

### Fixed

- Removed reference to `JacksonObjectMapperProvider` class that was missing from the codebase, which would have caused a compilation failure.

---

## [2.2.0] - prior release

- Initial public release of the Mailinator Java Client SDK.
14 changes: 10 additions & 4 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<groupId>com.manybrain</groupId>
<artifactId>mailinator-client</artifactId>
<version>2.2</version>
<version>2.5.1</version>

<name>${project.groupId}:${project.artifactId}</name>
<description>https://www.mailinator.com/ Java client</description>
Expand Down Expand Up @@ -85,9 +85,9 @@
<version>${org.glassfish.jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>${org.glassfish.jersey.version}</version>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
Expand Down Expand Up @@ -132,6 +132,12 @@
</dependencies>

<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
Expand Down

This file was deleted.

31 changes: 24 additions & 7 deletions src/main/java/com/manybrain/mailinator/client/JerseyClient.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.manybrain.mailinator.client;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand All @@ -11,29 +14,43 @@
import lombok.NoArgsConstructor;

import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.logging.LoggingFeature;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class JerseyClient
{

public static final String BASE_URL = "https://api.mailinator.com/api/v2";
public static final String USER_AGENT = "Mailinator SDK - Java V2.2";

// Define a filter to set the User-Agent header
public static final String USER_AGENT = "Mailinator SDK - Java V" + readVersion();

private static String readVersion()
{
try (InputStream is = JerseyClient.class.getResourceAsStream("/mailinator-sdk.properties"))
{
if (is != null)
{
Properties props = new Properties();
props.load(is);
return props.getProperty("version", "unknown");
}
}
catch (IOException e)
{
// fall through to default
}
return "unknown";
}

private static final ClientRequestFilter USER_AGENT_FILTER = requestContext -> {
requestContext.getHeaders().add("User-Agent", USER_AGENT);
};

public static final Client CLIENT = ClientBuilder.newClient()
.property(ClientProperties.READ_TIMEOUT, (int) TimeUnit.SECONDS.toMillis(125)) // Set read timeout to 65 sec
.property(ClientProperties.READ_TIMEOUT, (int) TimeUnit.SECONDS.toMillis(125))
.register(new LoggingFeature(Logger.getLogger(
JerseyClient.class.getPackage().getName()),
Level.SEVERE,
null,
null))
.register(new JacksonFeature())
.register(new JacksonObjectMapperProvider())
.register(USER_AGENT_FILTER);
}
86 changes: 86 additions & 0 deletions src/main/java/com/manybrain/mailinator/client/JsonUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package com.manybrain.mailinator.client;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class JsonUtils {

public static JSONObject parseObject(String json) {
try {
if (json == null || json.trim().isEmpty()) return null;
Object parsed = new JSONParser().parse(json);
if (parsed instanceof JSONObject) return (JSONObject) parsed;
return null;
} catch (ParseException e) {
throw new RuntimeException("Failed to parse JSON object", e);
}
}

public static String getString(JSONObject obj, String key) {
if (obj == null) return null;
Object val = obj.get(key);
return val != null ? val.toString() : null;
}

public static Integer getInteger(JSONObject obj, String key) {
if (obj == null) return null;
Object val = obj.get(key);
if (val == null) return null;
if (val instanceof Long) return ((Long) val).intValue();
if (val instanceof Integer) return (Integer) val;
return Integer.parseInt(val.toString());
}

public static Long getLong(JSONObject obj, String key) {
if (obj == null) return null;
Object val = obj.get(key);
if (val == null) return null;
if (val instanceof Long) return (Long) val;
if (val instanceof Integer) return ((Integer) val).longValue();
return Long.parseLong(val.toString());
}

public static Boolean getBoolean(JSONObject obj, String key) {
if (obj == null) return null;
Object val = obj.get(key);
if (val == null) return null;
if (val instanceof Boolean) return (Boolean) val;
return Boolean.parseBoolean(val.toString());
}

public static JSONObject getObject(JSONObject obj, String key) {
if (obj == null) return null;
return (JSONObject) obj.get(key);
}

public static <T> List<T> getList(JSONObject obj, String key, Function<JSONObject, T> mapper) {
if (obj == null) return null;
JSONArray arr = (JSONArray) obj.get(key);
if (arr == null) return null;
List<T> list = new ArrayList<>();
for (Object item : arr) {
list.add(mapper.apply((JSONObject) item));
}
return list;
}

public static List<String> getStringList(JSONObject obj, String key) {
if (obj == null) return null;
JSONArray arr = (JSONArray) obj.get(key);
if (arr == null) return null;
List<String> list = new ArrayList<>();
for (Object item : arr) {
list.add(item != null ? item.toString() : null);
}
return list;
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
package com.manybrain.mailinator.client;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import org.json.simple.JSONObject;

@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseStatus
{

@JsonProperty("status")
private String status;

public static ResponseStatus fromJson(JSONObject json) {
if (json == null) return null;
ResponseStatus r = new ResponseStatus();
r.status = JsonUtils.getString(json, "status");
return r;
}
}
Loading