Skip to content
Browse the docs

Frameworks and agents · Published 2026-10-31 · Updated 2026-10-31 · AgentiSend

Send email with Java

A Java program that posts one ticket with java.net.http, an idempotency key, and the refusal code and fix.

To send email with Java, post JSON to POST /emails with java.net.http.HttpClient. That client has been in the JDK since 11, so this file has no Maven coordinate and no Gradle dependency. There is no Java package for this API, and this page does not invent one. main checks the address, sends a ticket with an idempotency key derived from the recipient, and prints the message id or the refusal's code and fix. The suite compiles Send.java and runs it against a local AgentiSend server.

A Spring controller would call the same send method and write the printed fields into its response. This page stops at the JDK, because that is the part you can compile with javac alone. Store the id and read the ticket later with GET /emails/{id}.

Install

No dependency to declare. JDK 11 or newer.

Environment

VariableRequiredWhat it is
AGENTISEND_API_KEYyesA key with sending_access.
MAIL_FROMyesThe From address, on a domain you have verified.
AGENTISEND_BASE_URLnoDefaults to https://api.agentisend.com.

The route

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Java — one ticket with java.net.http.
 *
 * There is no Java package for this API. HttpClient has been in the JDK
 * since 11, so this file has no Maven or Gradle dependency.
 * AGENTISEND_API_KEY is the key. MAIL_FROM is an address on a domain you
 * have verified. AGENTISEND_BASE_URL is optional.
 *
 * Run: javac Send.java && java Send
 */
public class Send {
    private static final Pattern ADDRESS = Pattern.compile("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$");
    private static final HttpClient HTTP = HttpClient.newHttpClient();

    static String env(String name) {
        String value = System.getenv(name);
        if (value == null || value.isEmpty()) {
            throw new IllegalStateException("Set " + name + ".");
        }
        return value;
    }

    static String baseUrl() {
        String base = System.getenv("AGENTISEND_BASE_URL");
        if (base == null || base.isEmpty()) base = "https://api.agentisend.com";
        return base.replaceAll("/+$", "");
    }

    static String jsonEscape(String value) {
        return value.replace("\\", "\\\\").replace("\"", "\\\"");
    }

    static String jsonString(String raw, String key) {
        Matcher matcher = Pattern.compile("\"" + key + "\"\\s*:\\s*\"([^\"]*)\"").matcher(raw);
        return matcher.find() ? matcher.group(1) : null;
    }

    /** status, then id, code, fix, or a local error — empty strings when absent. */
    static String[] send(String email, String note) throws Exception {
        email = email.trim();
        if (!ADDRESS.matcher(email).matches()) {
            return new String[] {"400", "", "", "", "email must be one address, like you@example.com"};
        }
        String payload = "{"
            + "\"from\":\"" + jsonEscape(env("MAIL_FROM")) + "\","
            + "\"to\":\"" + jsonEscape(email) + "\","
            + "\"subject\":\"Your ticket\","
            + "\"text\":\"Ticket note: " + jsonEscape(note) + "\""
            + "}";
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(baseUrl() + "/emails"))
            .header("authorization", "Bearer " + env("AGENTISEND_API_KEY"))
            .header("content-type", "application/json")
            .header("accept", "application/json")
            // The recipient, not the moment: a retry of the same note replays.
            .header("idempotency-key", "ticket/" + email)
            .POST(HttpRequest.BodyPublishers.ofString(payload))
            .build();
        HttpResponse<String> response = HTTP.send(request, HttpResponse.BodyHandlers.ofString());
        String body = response.body() == null ? "" : response.body();
        if (response.statusCode() >= 400) {
            return new String[] {
                Integer.toString(response.statusCode()),
                "",
                jsonString(body, "code"),
                jsonString(body, "fix"),
                ""
            };
        }
        return new String[] {Integer.toString(response.statusCode()), jsonString(body, "id"), "", "", ""};
    }

    static void emit(String step, String[] result) {
        StringBuilder out = new StringBuilder();
        out.append("{\"step\":\"").append(step).append("\",\"status\":").append(result[0]);
        if (result[1] != null && !result[1].isEmpty()) out.append(",\"id\":\"").append(jsonEscape(result[1])).append('"');
        if (result[2] != null && !result[2].isEmpty()) out.append(",\"code\":\"").append(jsonEscape(result[2])).append('"');
        if (result[3] != null && !result[3].isEmpty()) out.append(",\"fix\":\"").append(jsonEscape(result[3])).append('"');
        if (result[4] != null && !result[4].isEmpty()) out.append(",\"error\":\"").append(jsonEscape(result[4])).append('"');
        out.append('}');
        System.out.println(out);
    }

    public static void main(String[] args) throws Exception {
        emit("send", send("java@example.com", "first"));
        emit("retry", send("java@example.com", "first"));
        emit("invalid", send("not-an-address", "first"));
        emit("refused", send("java@example.com", "second"));
    }
}
examples/java/Send.java, run by the test suite against a local AgentiSend server

Try it

javac Send.java && java Send

The program prints one JSON object per step: the send, the same request again, a malformed address answered before any request, and a changed note under the same key.

What comes back when it goes wrong

Every refused request carries code, message, fix and docs_url. The program prints code and fix with the API's status. The error catalogue lists every code.

Next