Skip to content

Send email with Java

Send transactional email from Java through PostStack.

Java 11 introduced java.net.http.HttpClient as a first-class HTTP client in the stdlib, removing the need for Apache HttpClient, OkHttp, or RestTemplate for simple REST integrations. PostStack accepts a small JSON POST with Bearer auth, so the stdlib client is plenty. For Spring Boot apps, integrating via JavaMailSender and SMTP is usually cleaner so @Async and @Retryable annotations Just Work — see the Spring Boot guide.

Quickstart#

Before you start

You need an API key (sk_live_…, or sk_test_… to try it without delivering anything) and a sending domain you have verified in PostStack. See Authentication and Domains.

  1. Install the SDK

    Nothing to install: java.net.http.HttpClient is part of the JDK since Java 11, and the examples use text blocks (Java 15+). Check your version:

    bash
    java -version   # 15 or newer
  2. Initialize the client

    java
    import java.net.http.HttpClient;
    import java.net.http.HttpRequest;
    import java.net.http.HttpResponse;
    import java.net.URI;
    
    HttpClient client = HttpClient.newHttpClient();
    String apiKey = System.getenv("POSTSTACK_API_KEY");
  3. Send an email

    java
    import java.net.URI;
    import java.net.http.HttpClient;
    import java.net.http.HttpRequest;
    import java.net.http.HttpResponse;
    
    public class SendEmail {
        public static void main(String[] args) throws Exception {
            String apiKey = System.getenv("POSTSTACK_API_KEY");
    
            String json = """
                {
                    "from": "hello@yourdomain.com",
                    "to": ["user@example.com"],
                    "subject": "Hello from Java!",
                    "html": "<h1>Welcome!</h1>"
                }
                """;
    
            HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://api.poststack.dev/emails"))
                .header("Authorization", "Bearer " + apiKey)
                .header("Content-Type", "application/json")
                .POST(HttpRequest.BodyPublishers.ofString(json))
                .build();
    
            HttpResponse<String> response = HttpClient.newHttpClient()
                .send(request, HttpResponse.BodyHandlers.ofString());
    
            System.out.println(response.body());
        }
    }
  4. Handle errors

    java
    import java.net.URI;
    import java.net.http.HttpClient;
    import java.net.http.HttpRequest;
    import java.net.http.HttpResponse;
    import java.time.Duration;
    
    public class PoststackClient {
        private static final HttpClient HTTP = HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(5))
            .build();
    
        public static String send(String json) throws Exception {
            // One key for every attempt, so a retry never delivers a second copy.
            String idempotencyKey = java.util.UUID.randomUUID().toString();
            for (int attempt = 0; attempt < 3; attempt++) {
                HttpRequest req = HttpRequest.newBuilder()
                    .uri(URI.create("https://api.poststack.dev/emails"))
                    .header("Authorization", "Bearer " + System.getenv("POSTSTACK_API_KEY"))
                    .header("Content-Type", "application/json")
                    .header("Idempotency-Key", idempotencyKey)
                    .timeout(Duration.ofSeconds(10))
                    .POST(HttpRequest.BodyPublishers.ofString(json))
                    .build();
    
                HttpResponse<String> res = HTTP.send(req, HttpResponse.BodyHandlers.ofString());
    
                if (res.statusCode() >= 200 && res.statusCode() < 300) {
                    return res.body();
                }
                if (res.statusCode() == 429 || res.statusCode() >= 500) {
                    Thread.sleep((long) Math.pow(2, attempt) * 1000);
                    continue;
                }
                throw new RuntimeException("PostStack " + res.statusCode() + ": " + res.body());
            }
            throw new RuntimeException("PostStack: retries exhausted");
        }
    }

Good to know

  • Uses the JDK’s built-in HttpClient (Java 11+) — no Maven or Gradle dependency. The JSON text block needs Java 15+
  • A successful send returns 202 with {"id":"em_..."}

Framework integrations#

Spring Boot — JavaMailSender + SMTP

In application.yml, set spring.mail.host: smtp.poststack.dev, port: 587, username: poststack, password: ${POSTSTACK_API_KEY} and properties.mail.smtp.starttls.enable: true (STARTTLS on 587). For port 465 set properties.mail.smtp.ssl.enable: true instead. Inject JavaMailSender into your services and use @Async to make sends non-blocking.

Spring Boot — WebClient

For non-blocking REST calls, use Spring WebFlux’s WebClient. Configure a @Bean with the API key on construction, then webClient.post().uri("/emails").bodyValue(payload).retrieve().bodyToMono(EmailResponse.class). Pair with Resilience4j for retry policies.

Quarkus / Micronaut

Both frameworks have first-class declarative HTTP clients. Define a @RegisterRestClient (Quarkus) or @Client (Micronaut) interface with the PostStack endpoint, then inject and call. CDI handles the singleton lifetime.

Plain JVM apps

For non-Spring apps, build an HttpClient singleton at startup and share it across threads. The Java stdlib HttpClient is thread-safe and pools connections internally.

Common pitfalls#

Creating a new HttpClient per request

HttpClient.newHttpClient() spins up an internal executor and connection pool — recreating it per request leaks threads. Cache a single instance at class level or via DI.

Forgetting .timeout(...)

Without .connectTimeout() and .timeout(), a stuck connection blocks the calling thread indefinitely. Always set both — 5 seconds connect, 10 seconds total is a reasonable default.

Mixing send() and sendAsync()

HttpClient.send blocks; sendAsync returns CompletableFuture. Don’t call .get() on the future inside a blocking handler — it just adds context-switch overhead. Pick one model and stay consistent.

FAQ#

Do I need Apache HttpClient or OkHttp?

No. Java 11+ has a stdlib HttpClient that is more than enough for PostStack’s REST API. Skip the extra dependency.

How do I integrate with Spring Boot?

For SMTP, configure JavaMailSender via application.yml. For REST, define a WebClient bean or use the stdlib HttpClient directly. Both are detailed in the integrations section.

Is HttpClient thread-safe?

Yes. A single HttpClient instance is thread-safe and pools connections internally. Share one instance across your application.

Ready to send from Java?

Create a free account and an API key — no card needed.

Get started free

Next steps

Was this page helpful?