Tutorial
Invia email con Java
Invia email da Java con l’SDK emailit-java o con Spring Boot e Jakarta Mail via SMTP, e verifica i webhook di Emailit in un controller Spring.
Questa guida mostra come inviare email da Java con l’SDK ufficiale emailit-java, come usare in alternativa lo starter mail di Spring Boot o Jakarta Mail via SMTP e come verificare i webhook in un controller Spring.
Prerequisiti
- Java 11 o versioni successive. Gli esempi Spring usano Spring Boot 3, che richiede Java 17.
- Un dominio di invio verificato, ad esempio
acme.com. - Una chiave API. Basta una chiave di solo invio limitata al tuo dominio.
- Finché il workspace non ha l’accesso alla produzione, puoi inviare solo agli indirizzi email degli account dei membri del workspace.
Installa l’SDK
Aggiungi la dipendenza, con l’ultima versione indicata nel repository emailit-java:
<dependency>
<groupId>com.emailit</groupId>
<artifactId>emailit-java</artifactId>
<version>VERSION</version>
</dependency>implementation 'com.emailit:emailit-java:VERSION'Configura la chiave API
Fornisci la chiave come variabile d’ambiente:
export EMAILIT_API_KEY=secret_••••••••••••••••••••••••••••••••In Spring Boot, associala a una proprietà per poterla iniettare:
emailit.api-key=${EMAILIT_API_KEY}Invia un’email
import com.emailit.*;
import com.emailit.exception.*;
import com.emailit.params.*;
import java.util.List;
public class SendEmail {
public static void main(String[] args) throws EmailitException {
EmailitClient emailit = new EmailitClient(System.getenv("EMAILIT_API_KEY"));
EmailSendParams params = EmailSendParams.builder()
.setFrom("Acme <hello@acme.com>")
.setTo(List.of("ada@example.com"))
.setSubject("Your order has shipped")
.setHtml("<p>Order #1042 is on its way.</p>")
.build();
EmailitObject email = emailit.emails().send(params);
System.out.println(email.getString("id")); // em_…
}
}In Spring Boot, registra il client una sola volta come bean e iniettalo dove invii:
import com.emailit.EmailitClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class EmailitConfig {
@Bean
EmailitClient emailitClient(@Value("${emailit.api-key}") String apiKey) {
return new EmailitClient(apiKey);
}
}Gestisci gli errori
L’SDK lancia eccezioni tipizzate che estendono EmailitException:
try {
emailit.emails().send(params);
} catch (RateLimitException e) {
// 429: back off and retry
} catch (AuthenticationException e) {
// 401: the API key is missing or invalid
} catch (UnprocessableEntityException e) {
// 422: for example, the from domain isn't verified
} catch (EmailitException e) {
log.error("Emailit error {}: {}", e.getHttpStatus(), e.getHttpBody());
}Invia con SMTP
Spring Boot
Aggiungi spring-boot-starter-mail e configura il relay:
spring.mail.host=smtp.emailit.com
spring.mail.port=587
spring.mail.username=emailit
spring.mail.password=${EMAILIT_API_KEY}
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=truePoi invia con JavaMailSender:
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
@Service
public class WelcomeMailer {
private final JavaMailSender mailSender;
public WelcomeMailer(JavaMailSender mailSender) {
this.mailSender = mailSender;
}
public void sendWelcome(String to) {
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom("Acme <hello@acme.com>");
message.setTo(to);
message.setSubject("Welcome to Acme");
message.setText("Thanks for signing up.");
mailSender.send(message);
}
}Usa MimeMessageHelper per HTML e allegati.
Jakarta Mail senza Spring
Jakarta Mail da solo (in passato JavaMail) usa le stesse proprietà mail.smtp.*:
Properties props = new Properties();
props.put("mail.smtp.host", "smtp.emailit.com");
props.put("mail.smtp.port", "587");
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.starttls.required", "true");
Session session = Session.getInstance(props, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("emailit", System.getenv("EMAILIT_API_KEY"));
}
});Se la porta 587 è bloccata sulla tua rete, usa la 2525 o la 2587 con le stesse impostazioni. Vedi Impostazioni SMTP.
Ricevi i webhook
Crea un webhook e salva il suo secret di firma in EMAILIT_WEBHOOK_SECRET. Accetta il corpo come String, così verifichi esattamente ciò che Emailit ha firmato:
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.HexFormat;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
public class EmailitWebhookController {
private final ObjectMapper mapper = new ObjectMapper();
private final String secret = System.getenv("EMAILIT_WEBHOOK_SECRET");
@PostMapping("/webhooks/emailit")
public ResponseEntity<Void> receive(
@RequestBody String body,
@RequestHeader(value = "X-Emailit-Signature", defaultValue = "") String signature,
@RequestHeader(value = "X-Emailit-Timestamp", defaultValue = "0") long timestamp) throws Exception {
if (Math.abs(System.currentTimeMillis() / 1000 - timestamp) > 300 || !isValid(body, signature, timestamp)) {
return ResponseEntity.status(401).build();
}
// The body is a JSON array of up to 100 events.
for (JsonNode event : mapper.readTree(body)) {
if ("email.bounced".equals(event.path("type").asText())) {
String address = event.path("data").path("object").path("to").asText();
// Stop emailing this address.
}
}
return ResponseEntity.ok().build();
}
private boolean isValid(String body, String signature, long timestamp) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
byte[] digest = mac.doFinal((timestamp + "." + body).getBytes(StandardCharsets.UTF_8));
String expected = HexFormat.of().formatHex(digest);
return MessageDigest.isEqual(
expected.getBytes(StandardCharsets.UTF_8),
signature.getBytes(StandardCharsets.UTF_8));
}
}Se Spring Security è attivo, consenti questo percorso ed escludilo dalla protezione CSRF. Restituisci un 2xx entro 30 secondi; le altre risposte vengono ritentate. Vedi Firma delle richieste.
Consigli per la produzione
- Riutilizza i client. Crea un solo
EmailitClient(o affidati all’unicoJavaMailSenderdi Spring) invece di uno per messaggio. - Invia in modo asincrono. Per gli invii massivi usa
@Async, una coda di messaggi o un job programmato, e limita il throughput per restare entro i limiti di invio (2 email al secondo per impostazione predefinita). - Rendi sicuri i nuovi tentativi. Se ritenti dopo un timeout, invia un header
Idempotency-Keyconjava.net.http.HttpClient; vedi Idempotenza. - Elimina i webhook duplicati salvando ogni
event_idche elabori.