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
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,21 @@ SendEmailResponse response = email

// Metadata and tags
.metadata(metadata)
.tag("welcome", "onboarding")
.tag("legacy-tag")
.tags(
new MessageTag("campaign", "welcome"),
new MessageTag("customer", "new")
)

// Idempotency
.idempotencyKey("unique-request-key")

.send();
```

`tag()` remains available for the legacy single tag. The previous map form of
`tags()` also remains available.

Existing constructor-based sending usage still works:

```java
Expand Down
27 changes: 26 additions & 1 deletion src/main/java/co/lettermint/endpoints/EmailEndpoint.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import co.lettermint.client.LettermintClient;
import co.lettermint.models.Attachment;
import co.lettermint.models.SendEmailResponse;
import co.lettermint.models.MessageTag;
import co.lettermint.models.api.SendMailRequest;
import co.lettermint.models.api.SendMailResponse;
import com.fasterxml.jackson.core.type.TypeReference;
Expand All @@ -28,7 +29,7 @@
private String route;
private Map<String, Object> metadata;
private String tag;
private List<Map<String, String>> tags;
private List<MessageTag> tags;
private Map<String, Object> settings;
private String idempotencyKey;

Expand Down Expand Up @@ -64,7 +65,7 @@
* Set the sender email address.
* Format: {@code "Name <email>"} or just {@code "email"}
*/
public EmailEndpoint from(String from) {

Check warning on line 68 in src/main/java/co/lettermint/endpoints/EmailEndpoint.java

View workflow job for this annotation

GitHub Actions / Test (Java 8 - ubuntu-latest)

Check warning on line 68 in src/main/java/co/lettermint/endpoints/EmailEndpoint.java

View workflow job for this annotation

GitHub Actions / Test (Java 8 - ubuntu-latest)

no @PARAM for from

Check warning on line 68 in src/main/java/co/lettermint/endpoints/EmailEndpoint.java

View workflow job for this annotation

GitHub Actions / Test (Java 11 - ubuntu-latest)

Check warning on line 68 in src/main/java/co/lettermint/endpoints/EmailEndpoint.java

View workflow job for this annotation

GitHub Actions / Test (Java 11 - ubuntu-latest)

no @PARAM for from
this.from = from;
return this;
}
Expand All @@ -72,7 +73,7 @@
/**
* Set recipient email addresses. Replaces any existing recipients.
*/
public EmailEndpoint to(String... emails) {

Check warning on line 76 in src/main/java/co/lettermint/endpoints/EmailEndpoint.java

View workflow job for this annotation

GitHub Actions / Test (Java 8 - ubuntu-latest)

Check warning on line 76 in src/main/java/co/lettermint/endpoints/EmailEndpoint.java

View workflow job for this annotation

GitHub Actions / Test (Java 8 - ubuntu-latest)

no @PARAM for emails

Check warning on line 76 in src/main/java/co/lettermint/endpoints/EmailEndpoint.java

View workflow job for this annotation

GitHub Actions / Test (Java 11 - ubuntu-latest)

Check warning on line 76 in src/main/java/co/lettermint/endpoints/EmailEndpoint.java

View workflow job for this annotation

GitHub Actions / Test (Java 11 - ubuntu-latest)

no @PARAM for emails
this.to = new ArrayList<>(Arrays.asList(emails));
return this;
}
Expand All @@ -80,7 +81,7 @@
/**
* Set CC email addresses. Replaces any existing CC recipients.
*/
public EmailEndpoint cc(String... emails) {

Check warning on line 84 in src/main/java/co/lettermint/endpoints/EmailEndpoint.java

View workflow job for this annotation

GitHub Actions / Test (Java 8 - ubuntu-latest)

Check warning on line 84 in src/main/java/co/lettermint/endpoints/EmailEndpoint.java

View workflow job for this annotation

GitHub Actions / Test (Java 8 - ubuntu-latest)

no @PARAM for emails

Check warning on line 84 in src/main/java/co/lettermint/endpoints/EmailEndpoint.java

View workflow job for this annotation

GitHub Actions / Test (Java 11 - ubuntu-latest)

Check warning on line 84 in src/main/java/co/lettermint/endpoints/EmailEndpoint.java

View workflow job for this annotation

GitHub Actions / Test (Java 11 - ubuntu-latest)

no @PARAM for emails
this.cc = new ArrayList<>(Arrays.asList(emails));
return this;
}
Expand All @@ -88,7 +89,7 @@
/**
* Set BCC email addresses. Replaces any existing BCC recipients.
*/
public EmailEndpoint bcc(String... emails) {

Check warning on line 92 in src/main/java/co/lettermint/endpoints/EmailEndpoint.java

View workflow job for this annotation

GitHub Actions / Test (Java 8 - ubuntu-latest)

Check warning on line 92 in src/main/java/co/lettermint/endpoints/EmailEndpoint.java

View workflow job for this annotation

GitHub Actions / Test (Java 8 - ubuntu-latest)

no @PARAM for emails

Check warning on line 92 in src/main/java/co/lettermint/endpoints/EmailEndpoint.java

View workflow job for this annotation

GitHub Actions / Test (Java 11 - ubuntu-latest)

Check warning on line 92 in src/main/java/co/lettermint/endpoints/EmailEndpoint.java

View workflow job for this annotation

GitHub Actions / Test (Java 11 - ubuntu-latest)

no @PARAM for emails
this.bcc = new ArrayList<>(Arrays.asList(emails));
return this;
}
Expand All @@ -96,7 +97,7 @@
/**
* Set reply-to email addresses. Replaces any existing reply-to addresses.
*/
public EmailEndpoint replyTo(String... emails) {

Check warning on line 100 in src/main/java/co/lettermint/endpoints/EmailEndpoint.java

View workflow job for this annotation

GitHub Actions / Test (Java 8 - ubuntu-latest)

Check warning on line 100 in src/main/java/co/lettermint/endpoints/EmailEndpoint.java

View workflow job for this annotation

GitHub Actions / Test (Java 8 - ubuntu-latest)

no @PARAM for emails

Check warning on line 100 in src/main/java/co/lettermint/endpoints/EmailEndpoint.java

View workflow job for this annotation

GitHub Actions / Test (Java 11 - ubuntu-latest)

Check warning on line 100 in src/main/java/co/lettermint/endpoints/EmailEndpoint.java

View workflow job for this annotation

GitHub Actions / Test (Java 11 - ubuntu-latest)

no @PARAM for emails
this.replyTo = new ArrayList<>(Arrays.asList(emails));
return this;
}
Expand Down Expand Up @@ -211,6 +212,9 @@
* Set the email tag.
*/
public EmailEndpoint tag(String tag) {
if (tag != null && tags.size() >= 20) {
throw new IllegalArgumentException("A legacy tag and no more than 19 message tags are permitted");
}
this.tag = tag;
return this;
}
Expand All @@ -220,6 +224,27 @@
*/
@SafeVarargs
public final EmailEndpoint tags(Map<String, String>... tags) {
MessageTag[] normalized = new MessageTag[tags.length];
for (int index = 0; index < tags.length; index++) {
normalized[index] = new MessageTag(tags[index].get("name"), tags[index].get("value"));
}
return tags(normalized);
}

/**
* Set typed reusable name-value tags for the email.
*/
public final EmailEndpoint tags(MessageTag... tags) {
int maximum = tag == null ? 20 : 19;
if (tags.length > maximum) {
throw new IllegalArgumentException("No more than " + maximum + " message tags are permitted");
}
Set<String> names = new HashSet<>();
for (MessageTag messageTag : tags) {
if (!names.add(messageTag.getName())) {
throw new IllegalArgumentException("Message tag names must be unique and case-sensitive");
}
}
this.tags = new ArrayList<>(Arrays.asList(tags));
return this;
}
Expand Down
35 changes: 35 additions & 0 deletions src/main/java/co/lettermint/models/MessageTag.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package co.lettermint.models;

import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.Locale;
import java.util.regex.Pattern;

/** A reusable exact-match message tag. */
public final class MessageTag {
private static final Pattern NAME = Pattern.compile("^[A-Za-z0-9_-]{1,32}$");
private static final Pattern VALUE = Pattern.compile("^[A-Za-z0-9_-]{1,64}$");

@JsonProperty("name")
private final String name;

@JsonProperty("value")
private final String value;

public MessageTag(String name, String value) {
if (name == null || !NAME.matcher(name).matches()) {
throw new IllegalArgumentException("Message tag names must match ^[A-Za-z0-9_-]{1,32}$");
}
if (name.toLowerCase(Locale.ROOT).startsWith("__lettermint")) {
throw new IllegalArgumentException("Message tag names must not start with __lettermint");
}
if (value == null || !VALUE.matcher(value).matches()) {
throw new IllegalArgumentException("Message tag values must match ^[A-Za-z0-9_-]{1,64}$");
}
this.name = name;
this.value = value;
}

public String getName() { return name; }
public String getValue() { return value; }
}
33 changes: 33 additions & 0 deletions src/test/java/co/lettermint/EmailEndpointTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package co.lettermint;

import co.lettermint.models.SendEmailResponse;
import co.lettermint.models.MessageTag;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
Expand Down Expand Up @@ -263,4 +264,36 @@ void testSingleMetadataMethod() throws Exception {
assertTrue(body.contains("\"key1\":\"value1\""));
assertTrue(body.contains("\"key2\":123"));
}

@Test
void testTypedMessageTags() throws Exception {
mockWebServer.enqueue(new MockResponse()
.setBody("{\"message_id\": \"msg_123\", \"status\": \"queued\"}")
.setHeader("Content-Type", "application/json"));

lettermint.email()
.from("sender@example.com")
.to("recipient@example.com")
.subject("Test")
.text("Test")
.tags(new MessageTag("campaign", "welcome"), new MessageTag("customer", "new"))
.send();

String body = mockWebServer.takeRequest().getBody().readUtf8();
assertTrue(body.contains("\"tags\":[{\"name\":\"campaign\",\"value\":\"welcome\"}"));
}

@Test
void testMessageTagValidation() {
assertThrows(IllegalArgumentException.class, () -> new MessageTag("invalid name", "value"));
assertThrows(IllegalArgumentException.class, () -> new MessageTag("__LETTERMINT_internal", "value"));
assertThrows(IllegalArgumentException.class, () -> lettermint.email().tags(
new MessageTag("same", "one"), new MessageTag("same", "two")));

MessageTag[] tags = new MessageTag[20];
for (int index = 0; index < tags.length; index++) {
tags[index] = new MessageTag("tag_" + index, "value");
}
assertThrows(IllegalArgumentException.class, () -> lettermint.email().tag("legacy").tags(tags));
}
}
Loading