diff --git a/src/main/java/com/resend/services/domains/DomainClaims.java b/src/main/java/com/resend/services/domains/DomainClaims.java new file mode 100644 index 0000000..1fbe1b9 --- /dev/null +++ b/src/main/java/com/resend/services/domains/DomainClaims.java @@ -0,0 +1,79 @@ +package com.resend.services.domains; + +import com.resend.core.exception.ResendException; +import com.resend.core.net.AbstractHttpResponse; +import com.resend.core.net.HttpMethod; +import com.resend.core.service.BaseService; +import com.resend.services.domains.model.ClaimDomainOptions; +import com.resend.services.domains.model.DomainClaimResponseSuccess; +import okhttp3.MediaType; + +/** + * Represents the Resend Domain Claims module. + */ +public final class DomainClaims extends BaseService { + + /** + * Constructs an instance of the {@code DomainClaims} class. + * + * @param apiKey The apiKey used for authentication. + */ + public DomainClaims(final String apiKey) { + super(apiKey); + } + + /** + * Claims a domain already verified by another team. + * + * @param claimDomainOptions The request object containing the domain claim details. + * @return A DomainClaimResponseSuccess representing the created claim. + * @throws ResendException If an error occurs during the domain claim process. + */ + public DomainClaimResponseSuccess create(ClaimDomainOptions claimDomainOptions) throws ResendException { + String payload = super.resendMapper.writeValue(claimDomainOptions); + AbstractHttpResponse response = httpClient.perform("/domains/claim", super.apiKey, HttpMethod.POST, payload, MediaType.get("application/json")); + + if (!response.isSuccessful()) { + throw new ResendException(response.getCode(), response.getBody()); + } + + String responseBody = response.getBody(); + return resendMapper.readValue(responseBody, DomainClaimResponseSuccess.class); + } + + /** + * Retrieves the latest claim for a domain. + * + * @param domainId The placeholder domain ID returned when the claim was created. + * @return A DomainClaimResponseSuccess representing the current claim state. + * @throws ResendException If an error occurs during the retrieval process. + */ + public DomainClaimResponseSuccess get(String domainId) throws ResendException { + AbstractHttpResponse response = httpClient.perform("/domains/" + domainId + "/claim", super.apiKey, HttpMethod.GET, null, MediaType.get("application/json")); + + if (!response.isSuccessful()) { + throw new ResendException(response.getCode(), response.getBody()); + } + + String responseBody = response.getBody(); + return resendMapper.readValue(responseBody, DomainClaimResponseSuccess.class); + } + + /** + * Triggers DNS verification for a domain claim. + * + * @param domainId The placeholder domain ID returned when the claim was created. + * @return A DomainClaimResponseSuccess representing the claim after verification is triggered. + * @throws ResendException If an error occurs during the verification process. + */ + public DomainClaimResponseSuccess verify(String domainId) throws ResendException { + AbstractHttpResponse response = httpClient.perform("/domains/" + domainId + "/claim/verify", super.apiKey, HttpMethod.POST, "", null); + + if (!response.isSuccessful()) { + throw new ResendException(response.getCode(), response.getBody()); + } + + String responseBody = response.getBody(); + return resendMapper.readValue(responseBody, DomainClaimResponseSuccess.class); + } +} diff --git a/src/main/java/com/resend/services/domains/Domains.java b/src/main/java/com/resend/services/domains/Domains.java index a3976f9..cb43638 100644 --- a/src/main/java/com/resend/services/domains/Domains.java +++ b/src/main/java/com/resend/services/domains/Domains.java @@ -136,6 +136,15 @@ public UpdateDomainResponseSuccess update(UpdateDomainOptions updateDomainOption return resendMapper.readValue(responseBody, UpdateDomainResponseSuccess.class); } + /** + * Returns a DomainClaims object that can be used to interact with the Domain Claims service. + * + * @return A DomainClaims object. + */ + public DomainClaims claims() { + return new DomainClaims(apiKey); + } + /** * Deletes a domain based on the provided domain ID and returns a RemoveDomainResponse. * diff --git a/src/main/java/com/resend/services/domains/model/ClaimDomainOptions.java b/src/main/java/com/resend/services/domains/model/ClaimDomainOptions.java new file mode 100644 index 0000000..61a253d --- /dev/null +++ b/src/main/java/com/resend/services/domains/model/ClaimDomainOptions.java @@ -0,0 +1,200 @@ +package com.resend.services.domains.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Represents a request to claim a domain already verified by another team. + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ClaimDomainOptions { + + @JsonProperty("name") + private final String name; + + @JsonProperty("region") + private final String region; + + @JsonProperty("custom_return_path") + private final String customReturnPath; + + @JsonProperty("open_tracking") + private final Boolean openTracking; + + @JsonProperty("click_tracking") + private final Boolean clickTracking; + + @JsonProperty("tracking_subdomain") + private final String trackingSubdomain; + + /** + * Constructs a ClaimDomainOptions object using the provided builder. + * + * @param builder The builder to construct the ClaimDomainOptions from. + */ + public ClaimDomainOptions(Builder builder) { + this.name = builder.name; + this.region = builder.region; + this.customReturnPath = builder.customReturnPath; + this.openTracking = builder.openTracking; + this.clickTracking = builder.clickTracking; + this.trackingSubdomain = builder.trackingSubdomain; + } + + /** + * Get the domain name to claim. + * + * @return The domain name. + */ + public String getName() { + return name; + } + + /** + * Get the region where emails will be sent from. + * + * @return The region. + */ + public String getRegion() { + return region; + } + + /** + * Get the custom return path subdomain. + * + * @return The custom return path. + */ + public String getCustomReturnPath() { + return customReturnPath; + } + + /** + * Get whether open tracking is enabled. + * + * @return The open tracking setting. + */ + public Boolean getOpenTracking() { + return openTracking; + } + + /** + * Get whether click tracking is enabled. + * + * @return The click tracking setting. + */ + public Boolean getClickTracking() { + return clickTracking; + } + + /** + * Get the subdomain used for click and open tracking. + * + * @return The tracking subdomain. + */ + public String getTrackingSubdomain() { + return trackingSubdomain; + } + + /** + * Create a new builder instance for constructing ClaimDomainOptions objects. + * + * @return A new builder instance. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder class for constructing ClaimDomainOptions objects. + */ + public static class Builder { + + /** + * Creates a new Builder instance. + */ + public Builder() { + } + + private String name; + private String region; + private String customReturnPath; + private Boolean openTracking; + private Boolean clickTracking; + private String trackingSubdomain; + + /** + * Set the domain name to claim. + * + * @param name The domain name. + * @return The builder instance. + */ + public Builder name(String name) { + this.name = name; + return this; + } + + /** + * Set the region where emails will be sent from. + * + * @param region The region. + * @return The builder instance. + */ + public Builder region(String region) { + this.region = region; + return this; + } + + /** + * Set the custom return path subdomain. + * + * @param customReturnPath The custom return path. + * @return The builder instance. + */ + public Builder customReturnPath(String customReturnPath) { + this.customReturnPath = customReturnPath; + return this; + } + + /** + * Set whether open tracking is enabled. + * + * @param openTracking The open tracking setting. + * @return The builder instance. + */ + public Builder openTracking(Boolean openTracking) { + this.openTracking = openTracking; + return this; + } + + /** + * Set whether click tracking is enabled. + * + * @param clickTracking The click tracking setting. + * @return The builder instance. + */ + public Builder clickTracking(Boolean clickTracking) { + this.clickTracking = clickTracking; + return this; + } + + /** + * Set the subdomain used for click and open tracking. + * + * @param trackingSubdomain The tracking subdomain. + * @return The builder instance. + */ + public Builder trackingSubdomain(String trackingSubdomain) { + this.trackingSubdomain = trackingSubdomain; + return this; + } + + /** + * Build a new ClaimDomainOptions object. + * + * @return A new ClaimDomainOptions object. + */ + public ClaimDomainOptions build() { + return new ClaimDomainOptions(this); + } + } +} diff --git a/src/main/java/com/resend/services/domains/model/DomainClaimRecord.java b/src/main/java/com/resend/services/domains/model/DomainClaimRecord.java new file mode 100644 index 0000000..cfa82f2 --- /dev/null +++ b/src/main/java/com/resend/services/domains/model/DomainClaimRecord.java @@ -0,0 +1,81 @@ +package com.resend.services.domains.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Represents the TXT DNS record returned within a domain claim response. + */ +public class DomainClaimRecord { + + @JsonProperty("type") + private String type; + + @JsonProperty("name") + private String name; + + @JsonProperty("value") + private String value; + + @JsonProperty("ttl") + private String ttl; + + /** + * Default constructor. + */ + public DomainClaimRecord() { + } + + /** + * Constructs a DomainClaimRecord with all fields. + * + * @param type The DNS record type. + * @param name The DNS record name. + * @param value The DNS record value. + * @param ttl The TTL for the DNS record. + */ + public DomainClaimRecord(final String type, + final String name, + final String value, + final String ttl) { + this.type = type; + this.name = name; + this.value = value; + this.ttl = ttl; + } + + /** + * Get the DNS record type. + * + * @return The DNS record type. + */ + public String getType() { + return type; + } + + /** + * Get the DNS record name. + * + * @return The DNS record name. + */ + public String getName() { + return name; + } + + /** + * Get the DNS record value. + * + * @return The DNS record value. + */ + public String getValue() { + return value; + } + + /** + * Get the TTL for the DNS record. + * + * @return The TTL. + */ + public String getTtl() { + return ttl; + } +} diff --git a/src/main/java/com/resend/services/domains/model/DomainClaimResponseSuccess.java b/src/main/java/com/resend/services/domains/model/DomainClaimResponseSuccess.java new file mode 100644 index 0000000..3176f9a --- /dev/null +++ b/src/main/java/com/resend/services/domains/model/DomainClaimResponseSuccess.java @@ -0,0 +1,186 @@ +package com.resend.services.domains.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Represents a domain claim response returned by the claim, get, and verify claim endpoints. + */ +public class DomainClaimResponseSuccess { + + @JsonProperty("object") + private String object; + + @JsonProperty("id") + private String id; + + @JsonProperty("name") + private String name; + + @JsonProperty("status") + private String status; + + @JsonProperty("domain_id") + private String domainId; + + @JsonProperty("region") + private String region; + + @JsonProperty("record") + private DomainClaimRecord record; + + @JsonProperty("blocked_reason") + private String blockedReason; + + @JsonProperty("failure_reason") + private String failureReason; + + @JsonProperty("created_at") + private String createdAt; + + @JsonProperty("expires_at") + private String expiresAt; + + /** + * Default constructor. + */ + public DomainClaimResponseSuccess() { + } + + /** + * Constructs a DomainClaimResponseSuccess with all fields. + * + * @param object The object type identifier. + * @param id The claim ID. + * @param name The domain name. + * @param status The claim status. + * @param domainId The placeholder domain ID. + * @param region The region. + * @param record The TXT DNS record for verification. + * @param blockedReason The reason the claim is blocked, if any. + * @param failureReason The reason the claim failed, if any. + * @param createdAt The creation timestamp. + * @param expiresAt The expiration timestamp. + */ + public DomainClaimResponseSuccess(final String object, + final String id, + final String name, + final String status, + final String domainId, + final String region, + final DomainClaimRecord record, + final String blockedReason, + final String failureReason, + final String createdAt, + final String expiresAt) { + this.object = object; + this.id = id; + this.name = name; + this.status = status; + this.domainId = domainId; + this.region = region; + this.record = record; + this.blockedReason = blockedReason; + this.failureReason = failureReason; + this.createdAt = createdAt; + this.expiresAt = expiresAt; + } + + /** + * Get the object type identifier. + * + * @return The object type. + */ + public String getObject() { + return object; + } + + /** + * Get the claim ID. + * + * @return The claim ID. + */ + public String getId() { + return id; + } + + /** + * Get the domain name. + * + * @return The domain name. + */ + public String getName() { + return name; + } + + /** + * Get the claim status. + * + * @return The claim status. + */ + public String getStatus() { + return status; + } + + /** + * Get the placeholder domain ID. + * + * @return The domain ID. + */ + public String getDomainId() { + return domainId; + } + + /** + * Get the region. + * + * @return The region. + */ + public String getRegion() { + return region; + } + + /** + * Get the TXT DNS record for verification. + * + * @return The DNS record. + */ + public DomainClaimRecord getRecord() { + return record; + } + + /** + * Get the reason the claim is blocked, if any. + * + * @return The blocked reason. + */ + public String getBlockedReason() { + return blockedReason; + } + + /** + * Get the reason the claim failed, if any. + * + * @return The failure reason. + */ + public String getFailureReason() { + return failureReason; + } + + /** + * Get the creation timestamp. + * + * @return The creation timestamp. + */ + public String getCreatedAt() { + return createdAt; + } + + /** + * Get the expiration timestamp. + * + * @return The expiration timestamp. + */ + public String getExpiresAt() { + return expiresAt; + } +} diff --git a/src/test/java/com/resend/services/domains/DomainClaimsTest.java b/src/test/java/com/resend/services/domains/DomainClaimsTest.java new file mode 100644 index 0000000..3d371c4 --- /dev/null +++ b/src/test/java/com/resend/services/domains/DomainClaimsTest.java @@ -0,0 +1,73 @@ +package com.resend.services.domains; + +import com.resend.core.exception.ResendException; +import com.resend.services.domains.model.ClaimDomainOptions; +import com.resend.services.domains.model.DomainClaimResponseSuccess; +import com.resend.services.util.DomainsUtil; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class DomainClaimsTest { + + @Mock + private DomainClaims domainClaims; + + @BeforeEach + public void setUp() { + MockitoAnnotations.openMocks(this); + domainClaims = mock(DomainClaims.class); + } + + @Test + public void testClaimDomain_Success() throws ResendException { + DomainClaimResponseSuccess expected = DomainsUtil.claimDomainResponse(); + ClaimDomainOptions request = DomainsUtil.claimDomainRequest(); + + when(domainClaims.create(request)).thenReturn(expected); + + DomainClaimResponseSuccess response = domainClaims.create(request); + + assertNotNull(response); + assertEquals(expected.getId(), response.getId()); + assertEquals(expected.getName(), response.getName()); + assertEquals(expected.getStatus(), response.getStatus()); + assertEquals(expected.getDomainId(), response.getDomainId()); + } + + @Test + public void testGetDomainClaim_Success() throws ResendException { + DomainClaimResponseSuccess expected = DomainsUtil.claimDomainResponse(); + String domainId = expected.getDomainId(); + + when(domainClaims.get(domainId)).thenReturn(expected); + + DomainClaimResponseSuccess response = domainClaims.get(domainId); + + assertNotNull(response); + assertEquals(expected.getId(), response.getId()); + assertEquals(expected.getDomainId(), response.getDomainId()); + assertEquals(expected.getStatus(), response.getStatus()); + } + + @Test + public void testVerifyDomainClaim_Success() throws ResendException { + DomainClaimResponseSuccess expected = DomainsUtil.claimDomainResponse(); + String domainId = expected.getDomainId(); + + when(domainClaims.verify(domainId)).thenReturn(expected); + + DomainClaimResponseSuccess response = domainClaims.verify(domainId); + + assertNotNull(response); + assertEquals(expected.getId(), response.getId()); + assertEquals(expected.getDomainId(), response.getDomainId()); + assertEquals(expected.getStatus(), response.getStatus()); + } +} diff --git a/src/test/java/com/resend/services/util/DomainsUtil.java b/src/test/java/com/resend/services/util/DomainsUtil.java index c6145f8..3ac7468 100644 --- a/src/test/java/com/resend/services/util/DomainsUtil.java +++ b/src/test/java/com/resend/services/util/DomainsUtil.java @@ -5,6 +5,8 @@ import java.util.ArrayList; import java.util.List; + + public class DomainsUtil { public static final CreateDomainOptions createDomainRequest() { @@ -101,4 +103,33 @@ public static final UpdateDomainOptions updateDomainRequest() { .build(); } + public static final ClaimDomainOptions claimDomainRequest() { + return ClaimDomainOptions.builder() + .name("example.com") + .region("us-east-1") + .build(); + } + + public static final DomainClaimResponseSuccess claimDomainResponse() { + DomainClaimRecord record = new DomainClaimRecord( + "TXT", + "example.com", + "resend-domain-verification=3f8a1c2d4e5b6a7f8091a2b3c4d5e6f7", + "Auto" + ); + return new DomainClaimResponseSuccess( + "domain_claim", + "dacf4072-4119-4d88-932f-6c6126d3a9d1", + "example.com", + "pending", + "d91cd9bd-1176-453e-8fc1-35364d380206", + "us-east-1", + record, + null, + null, + "2026-06-16 17:12:02.059593+00", + "2026-06-23 17:12:02.059593+00" + ); + } + }