# Spring Boot

A RestClient bean and a service, configured the way Spring expects.

### The key

application.properties:
```text
# src/main/resources/application.properties
# The value comes from the EMAILSSH_API_KEY environment variable, so the key
# itself is never in the repository. Get one at
# https://emails.sh/dashboard/api-keys.
emailssh.api-key=${EMAILSSH_API_KEY}
emailssh.from=Acme <hello@acme.com>
```

### The client bean

EmailConfig.java:
```text
// src/main/java/com/acme/EmailConfig.java
package com.acme;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClient;

@Configuration
public class EmailConfig {

    @Bean
    public RestClient emailsshClient(@Value("${emailssh.api-key}") String apiKey) {
        return RestClient.builder()
                .baseUrl("https://emails.sh")
                .defaultHeader("Authorization", "Bearer " + apiKey)
                .build();
    }
}
```

### The service

EmailService.java:
```text
// src/main/java/com/acme/EmailService.java
package com.acme;

import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestClientResponseException;

@Service
public class EmailService {

    private final RestClient client;
    private final String from;

    public EmailService(RestClient emailsshClient, @Value("${emailssh.from}") String from) {
        this.client = emailsshClient;
        this.from = from;
    }

    public record Queued(String id, String status) {}

    @Async
    public void sendWelcome(String to) {
        try {
            Queued queued = client.post()
                    .uri("/v1/emails")
                    .contentType(MediaType.APPLICATION_JSON)
                    .body(Map.of(
                            "from", from,
                            "to", List.of(to),
                            "subject", "Welcome to Acme",
                            "html", "<p>Confirm your address to finish signing up.</p>",
                            "idempotency_key", "signup-" + to))
                    .retrieve()
                    .body(Queued.class);

            System.out.println("queued " + queued.id());
        } catch (RestClientResponseException e) {
            // The body carries {"error", "message"}; the message says what to do.
            System.err.println("emails.sh refused the send: " + e.getResponseBodyAsString());
        }
    }
}
```

@Async needs @EnableAsync on a configuration class and a task executor. Without it the annotation is ignored silently and the send blocks the request thread.

---

Base URL: https://emails.sh/v1. Auth: `Authorization: Bearer esh_...`.
Whole API in one file: https://emails.sh/llms.txt. All documentation: https://emails.sh/docs.md.
