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
47 changes: 46 additions & 1 deletion src/main/java/com/resend/core/net/RequestOptions.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package com.resend.core.net;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
* Represents a request to create a request options.
*/
public class RequestOptions {
private final String idempotencyKey;
private final Map<String, String> additionalHeaders;

/**
* Constructs a RequestOptions object using the provided builder.
Expand All @@ -13,6 +18,7 @@ public class RequestOptions {
*/
public RequestOptions(Builder builder) {
this.idempotencyKey = builder.idempotencyKey;
this.additionalHeaders = Collections.unmodifiableMap(new HashMap<>(builder.additionalHeaders));
}

/**
Expand All @@ -24,6 +30,15 @@ public String getIdempotencyKey() {
return idempotencyKey;
}

/**
* Get the additional headers map.
*
* @return An unmodifiable map of additional headers.
*/
public Map<String, String> getAdditionalHeaders() {
return additionalHeaders;
}

/**
* Create a new builder instance for constructing RequestOptions objects.
*
Expand All @@ -38,6 +53,14 @@ public static Builder builder() {
*/
public static class Builder {
private String idempotencyKey;
private final Map<String, String> additionalHeaders;

/**
* Constructs a new Builder with empty additional headers map.
*/
public Builder() {
this.additionalHeaders = new HashMap<>();
}

/**
* Set the idempotencyKey.
Expand All @@ -50,6 +73,28 @@ public Builder setIdempotencyKey(String idempotencyKey) {
return this;
}

/**
* Add a custom header to the additional headers map.
*
* @param name The header name.
* @param value The header value.
* @return The builder instance.
*/
public Builder add(String name, String value) {
this.additionalHeaders.put(name, value);
return this;
}

/**
* Add multiple custom headers to the additional headers map.
*
* @param headers A map of headers to add.
* @return The builder instance.
*/
public Builder addAll(Map<String, String> headers) {
this.additionalHeaders.putAll(headers);
return this;
}

/**
* Build a new RequestOptions object.
Expand All @@ -60,4 +105,4 @@ public RequestOptions build() {
return new RequestOptions(this);
}
}
}
}
7 changes: 7 additions & 0 deletions src/main/java/com/resend/core/net/impl/HttpClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,13 @@ public AbstractHttpResponse perform(
requestBuilder.addHeader("Idempotency-Key", requestOptions.getIdempotencyKey());
}

if (requestOptions.getAdditionalHeaders() != null
&& !requestOptions.getAdditionalHeaders().isEmpty()) {
for (Map.Entry<String, String> entry : requestOptions.getAdditionalHeaders().entrySet()) {
requestBuilder.addHeader(entry.getKey(), entry.getValue());
}
}

Request request = requestBuilder.build();

try {
Expand Down
8 changes: 2 additions & 6 deletions src/main/java/com/resend/services/batch/Batch.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,7 @@ public CreateBatchEmailsResponse send(List<CreateEmailOptions> emails) throws Re

String responseBody = response.getBody();

CreateBatchEmailsResponse createBatchEmailsResponse = resendMapper.readValue(responseBody, CreateBatchEmailsResponse.class);

return createBatchEmailsResponse;
return resendMapper.readValue(responseBody, CreateBatchEmailsResponse.class);
}

/**
Expand All @@ -67,9 +65,7 @@ public CreateBatchEmailsResponse send(List<CreateEmailOptions> emails, RequestOp

String responseBody = response.getBody();

CreateBatchEmailsResponse createBatchEmailsResponse = resendMapper.readValue(responseBody, CreateBatchEmailsResponse.class);

return createBatchEmailsResponse;
return resendMapper.readValue(responseBody, CreateBatchEmailsResponse.class);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.resend.services.batch.model;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;

import java.util.List;

/**
* Base class for batch email responses.
* The actual implementation depends on the validation mode used.
*/
@JsonSubTypes({
@JsonSubTypes.Type(CreateBatchEmailsResponse.class)
})
public abstract class AbstractBatchEmailsResponse {

/**
* The list of e-mail ids created.
*/
@JsonProperty("data")
protected List<BatchEmail> data;

/**
* Default constructor.
*/
public AbstractBatchEmailsResponse() {
}

/**
* Constructor with data.
*
* @param data A list of successfully created batch emails.
*/
public AbstractBatchEmailsResponse(final List<BatchEmail> data) {
this.data = data;
}

/**
* Get the list of successfully created batch emails.
*
* @return A list of batch emails.
*/
public List<BatchEmail> getData() {
return data;
}

}
50 changes: 50 additions & 0 deletions src/main/java/com/resend/services/batch/model/BatchError.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.resend.services.batch.model;

import com.fasterxml.jackson.annotation.JsonProperty;

/**
* Represents a validation error for a batch email in permissive mode.
*/
public class BatchError {

@JsonProperty("index")
private Integer index;

@JsonProperty("message")
private String message;

/**
* Default constructor.
*/
public BatchError() {
}

/**
* Constructor with index and message.
*
* @param index The index of the email in the batch request that failed validation.
* @param message The error message identifying the validation error.
*/
public BatchError(final Integer index, final String message) {
this.index = index;
this.message = message;
}

/**
* Get the index of the email in the batch request that failed validation.
*
* @return The index of the failed email.
*/
public Integer getIndex() {
return index;
}

/**
* Get the error message identifying the validation error.
*
* @return The error message.
*/
public String getMessage() {
return message;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,57 @@
import java.util.List;

/**
* Represents the response for creating batch emails.
* Response for batch emails sent in permissive mode.
* In permissive mode, partial success is allowed and validation errors are returned.
*/
public class CreateBatchEmailsResponse {
public class CreateBatchEmailsResponse extends AbstractBatchEmailsResponse {

@JsonProperty("data")
private List<BatchEmail> data;
@JsonProperty("errors")
private List<BatchError> errors;

/**
* Default constructor.
*/
public CreateBatchEmailsResponse() {
super();
}

/**
* Constructor with a list of batch emails.
* Constructor with data and errors.
*
* @param data A list of batch emails.
* @param data A list of successfully created batch emails.
* @param errors A list of validation errors.
*/
public CreateBatchEmailsResponse(final List<BatchEmail> data) {
this.data = data;
public CreateBatchEmailsResponse(final List<BatchEmail> data, final List<BatchError> errors) {
super(data);
this.errors = errors;
}

/**
* Get the list of batch emails.
* Get the list of validation errors.
*
* @return A list of batch emails.
* @return A list of batch email errors, never null (but may be empty).
*/
public List<BatchEmail> getData() {
return data;
public List<BatchError> getErrors() {
return errors;
}
}

/**
* Check if the response has any validation errors.
*
* @return true if there are validation errors, false otherwise.
*/
public boolean hasErrors() {
return errors != null && !errors.isEmpty();
}

/**
* Get the total number of failed emails.
*
* @return The count of emails that failed validation.
*/
public int getErrorCount() {
return errors != null ? errors.size() : 0;
}

}
19 changes: 18 additions & 1 deletion src/test/java/com/resend/services/emails/EmailsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.resend.core.net.ListParams;
import com.resend.core.net.RequestOptions;
import com.resend.services.batch.Batch;
import com.resend.services.batch.model.AbstractBatchEmailsResponse;
import com.resend.services.batch.model.CreateBatchEmailsResponse;
import com.resend.services.emails.model.*;
import com.resend.services.util.EmailsUtil;
Expand Down Expand Up @@ -86,6 +87,22 @@ public void testCreateBatchEmails_Success() throws ResendException {
assertEquals(expectedRes.getData().size(), sendBatchEmailsResponse.getData().size());
}

@Test
public void testCreatePermissiveBatchEmails_Success() throws ResendException {
RequestOptions options = RequestOptions.builder()
.add("x-batch-validation", "permissive").build();

List<CreateEmailOptions> batchEmailsRequest = EmailsUtil.createBatchEmailOptions();
CreateBatchEmailsResponse expectedRes = EmailsUtil.createPermissiveBatchEmailsResponse();

when(batch.send(batchEmailsRequest, options)).thenReturn(expectedRes);

CreateBatchEmailsResponse sendBatchEmailsResponse = batch.send(batchEmailsRequest, options);

assertNotNull(sendBatchEmailsResponse);
assertEquals(expectedRes.getData().size(), sendBatchEmailsResponse.getData().size());
}

@Test
public void testCreateBatchEmailsWithIdempotencyKey_Success() throws ResendException {
List<CreateEmailOptions> batchEmailsRequest = EmailsUtil.createBatchEmailOptions();
Expand All @@ -94,7 +111,7 @@ public void testCreateBatchEmailsWithIdempotencyKey_Success() throws ResendExcep

when(batch.send(batchEmailsRequest, requestOptions)).thenReturn(expectedRes);

CreateBatchEmailsResponse sendBatchEmailsResponse = batch.send(batchEmailsRequest, requestOptions);
AbstractBatchEmailsResponse sendBatchEmailsResponse = batch.send(batchEmailsRequest, requestOptions);

assertNotNull(sendBatchEmailsResponse);
assertEquals(expectedRes.getData().size(), sendBatchEmailsResponse.getData().size());
Expand Down
17 changes: 16 additions & 1 deletion src/test/java/com/resend/services/util/EmailsUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.resend.core.net.RequestOptions;
import com.resend.services.batch.model.BatchEmail;
import com.resend.services.batch.model.BatchError;
import com.resend.services.batch.model.CreateBatchEmailsResponse;
import com.resend.services.emails.model.*;
import com.resend.core.net.AbstractHttpResponse;
Expand Down Expand Up @@ -65,7 +66,21 @@ public static List<CreateEmailOptions> createBatchEmailOptions() {
}

public static CreateBatchEmailsResponse createBatchEmailsResponse() {
return new CreateBatchEmailsResponse(Arrays.asList(new BatchEmail("123"), new BatchEmail("321")));
return new CreateBatchEmailsResponse(Arrays.asList(new BatchEmail("123"), new BatchEmail("321")), new ArrayList<>());
}

public static CreateBatchEmailsResponse createPermissiveBatchEmailsResponse() {
List<BatchEmail> successes = Arrays.asList(
new BatchEmail("123"),
new BatchEmail("321")
);

List<BatchError> errors = Arrays.asList(
new BatchError(456, "Invalid recipient address"),
new BatchError(789, "Domain not reachable")
);

return new CreateBatchEmailsResponse(successes, errors);
}

public static Email createTestEmail() {
Expand Down