Send email with Java
java.net.http, no dependencies, Java 11 and up.
You need a verified sending domain and an API key from Settings › API Keys. If neither exists yet, the quickstart covers both in a couple of minutes.
Send
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public final class SendingClient {
private static final HttpClient CLIENT = HttpClient.newHttpClient();
public static HttpResponse<String> sendWelcome(String to, String userId) throws Exception {
String body = """
{"from":"Acme <[email protected]>","to":"%s","subject":"Welcome",
"html":"<p>You're in.</p>","idempotencyKey":"welcome-%s"}
""".formatted(to, userId);
HttpRequest request = HttpRequest.newBuilder(URI.create("https://sending.dev/api/v1/emails"))
.header("Authorization", "Bearer " + System.getenv("SENDING_API_KEY"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
return CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
}
}A successful call answers 202 with the message id: accepted and queued, not yet delivered. Delivery, opens, bounces and complaints arrive later, on webhooks or in the dashboard.
What changes in Java
HttpClient is thread safe and meant to be a singleton. In Spring, register it as a bean and inject it rather than building one per call.
The thing that catches people
Interpolating user input into a JSON string is how a quote in someone's name becomes a malformed request. Use Jackson or your framework's mapper for anything that is not a fixed example.
Next
- All the fields: cc and bcc, reply-to, attachments, templates, scheduling.
- Webhooks: delivery, bounce and complaint events, signed with HMAC.
- Domains: SPF, DKIM and MAIL FROM, and why sending is refused until they are in place.
- MCP: the same operations as tools, when the one writing the code is an agent.