Java
java.net.http from the JDK, no dependencies, JDK 11 and up.
There is no emails.sh artifact to add to your build. The HTTP client in the JDK since 11 is enough, and this class compiles with javac and nothing else.
Send
// src/main/java/com/acme/Email.java
package com.acme;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
public final class Email {
private static final HttpClient CLIENT = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
/** The key comes from https://emails.sh/dashboard/api-keys, via EMAILSSH_API_KEY. */
private static final String KEY = System.getenv("EMAILSSH_API_KEY");
public static String send(String to, String subject, String html) throws Exception {
String body = """
{
"from": "Acme <hello@acme.com>",
"to": ["%s"],
"subject": "%s",
"html": "%s"
}
""".formatted(escape(to), escape(subject), escape(html));
HttpRequest request = HttpRequest.newBuilder(URI.create("https://emails.sh/v1/emails"))
.header("Authorization", "Bearer " + KEY)
.header("Content-Type", "application/json")
.timeout(Duration.ofSeconds(10))
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 400) {
// The body carries an error code and a message saying what to do
// about it, so keep the whole thing in the exception.
throw new IllegalStateException("emails.sh refused the send: " + response.body());
}
return response.body();
}
private static String escape(String value) {
return value.replace("\\", "\\\\").replace("\"", "\\\"");
}
public static void main(String[] args) throws Exception {
System.out.println(send("ada@example.com", "Your receipt from Acme", "<p>Thanks for your order.</p>"));
}
}With Jackson, if you already have it
Building JSON by hand stops being reasonable as soon as a body has user input in it. If Jackson is on the classpath, serialise a map instead.
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;
import java.util.Map;
private static final ObjectMapper MAPPER = new ObjectMapper();
String body = MAPPER.writeValueAsString(Map.of(
"from", "Acme <hello@acme.com>",
"to", List.of(to),
"subject", subject,
"html", html,
"idempotency_key", "receipt-" + orderId));