How to Send Emails in Java / Spring Boot (2026 Guide)

Java has JavaMail for SMTP-based email sending. Spring Boot wraps it with spring-boot-starter-mail. Both work, but they leave you managing SMTP servers, TLS, and deliverability.
For production apps, API-based providers handle all of that. This guide covers Spring Mail for SMTP, REST API calls for modern providers, Thymeleaf templates, and async sending. All examples use Spring Boot 3+ with Java 17+.
Spring Mail vs API Providers
// Spring Mail: SMTP-based, you manage the server
@Autowired JavaMailSender mailSender;
SimpleMailMessage msg = new SimpleMailMessage();
msg.setTo("user@example.com");
msg.setSubject("Hello");
msg.setText("Body");
mailSender.send(msg);
// API provider: one HTTP call, they handle everything
restTemplate.postForObject(
"https://api.sequenzy.com/v1/transactional/send",
new EmailRequest("user@example.com", "Hello", "<p>Body</p>"),
Map.class
);Use Spring Mail if you're talking to an internal SMTP server. Use an API provider for everything else.
Pick a Provider
- Sequenzy is built for SaaS. Transactional emails, marketing campaigns, automated sequences from one API. Native Stripe integration.
- Resend is developer-friendly with a clean API. They have one-off broadcast campaigns but no automations or sequences.
- SendGrid is the enterprise option. Has an official Java SDK. Good for high volume.
Add Dependencies
<!-- Spring WebClient for HTTP calls -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency><!-- Resend Java SDK -->
<dependency>
<groupId>com.resend</groupId>
<artifactId>resend-java</artifactId>
<version>3.1.0</version>
</dependency><!-- SendGrid Java SDK -->
<dependency>
<groupId>com.sendgrid</groupId>
<artifactId>sendgrid-java</artifactId>
<version>4.10.2</version>
</dependency>Add your API key to application.properties:
sequenzy.api-key=${SEQUENZY_API_KEY}resend.api-key=${RESEND_API_KEY}sendgrid.api-key=${SENDGRID_API_KEY}Create an Email Service
package com.example.service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import java.util.Map;
@Service
public class EmailService {
private final WebClient webClient;
public EmailService(@Value("${sequenzy.api-key}") String apiKey) {
this.webClient = WebClient.builder()
.baseUrl("https://api.sequenzy.com/v1")
.defaultHeader("Authorization", "Bearer " + apiKey)
.defaultHeader("Content-Type", "application/json")
.build();
}
public Map<String, Object> send(String to, String subject, String body) {
return webClient.post()
.uri("/transactional/send")
.bodyValue(Map.of("to", to, "subject", subject, "body", body))
.retrieve()
.bodyToMono(Map.class)
.block();
}
}package com.example.service;
import com.resend.Resend;
import com.resend.core.exception.ResendException;
import com.resend.services.emails.model.CreateEmailOptions;
import com.resend.services.emails.model.CreateEmailResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
public class EmailService {
private final Resend resend;
private static final String FROM = "Your App <noreply@yourdomain.com>";
public EmailService(@Value("${resend.api-key}") String apiKey) {
this.resend = new Resend(apiKey);
}
public CreateEmailResponse send(String to, String subject, String html)
throws ResendException {
CreateEmailOptions params = CreateEmailOptions.builder()
.from(FROM)
.to(to)
.subject(subject)
.html(html)
.build();
return resend.emails().send(params);
}
}package com.example.service;
import com.sendgrid.*;
import com.sendgrid.helpers.mail.Mail;
import com.sendgrid.helpers.mail.objects.Content;
import com.sendgrid.helpers.mail.objects.Email;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.IOException;
@Service
public class EmailService {
private final SendGrid sg;
private static final String FROM = "noreply@yourdomain.com";
public EmailService(@Value("${sendgrid.api-key}") String apiKey) {
this.sg = new SendGrid(apiKey);
}
public Response send(String to, String subject, String html) throws IOException {
Email from = new Email(FROM);
Email toEmail = new Email(to);
Content content = new Content("text/html", html);
Mail mail = new Mail(from, subject, toEmail, content);
Request request = new Request();
request.setMethod(Method.POST);
request.setEndpoint("mail/send");
request.setBody(mail.build());
return sg.api(request);
}
}REST Controller
package com.example.controller;
import com.example.service.EmailService;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/api/email")
public class EmailController {
private final EmailService emailService;
public EmailController(EmailService emailService) {
this.emailService = emailService;
}
record WelcomeRequest(String email, String name) {}
@PostMapping("/send-welcome")
public Map<String, Object> sendWelcome(@RequestBody WelcomeRequest request) {
return emailService.send(
request.email(),
"Welcome, " + request.name(),
"<h1>Welcome, " + request.name() + "</h1><p>Your account is ready.</p>"
);
}
}package com.example.controller;
import com.example.service.EmailService;
import com.resend.core.exception.ResendException;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/api/email")
public class EmailController {
private final EmailService emailService;
public EmailController(EmailService emailService) {
this.emailService = emailService;
}
record WelcomeRequest(String email, String name) {}
@PostMapping("/send-welcome")
public ResponseEntity<Map<String, String>> sendWelcome(@RequestBody WelcomeRequest request)
throws ResendException {
var result = emailService.send(
request.email(),
"Welcome, " + request.name(),
"<h1>Welcome, " + request.name() + "</h1><p>Your account is ready.</p>"
);
return ResponseEntity.ok(Map.of("id", result.getId()));
}
}package com.example.controller;
import com.example.service.EmailService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.Map;
@RestController
@RequestMapping("/api/email")
public class EmailController {
private final EmailService emailService;
public EmailController(EmailService emailService) {
this.emailService = emailService;
}
record WelcomeRequest(String email, String name) {}
@PostMapping("/send-welcome")
public ResponseEntity<Map<String, Boolean>> sendWelcome(@RequestBody WelcomeRequest request)
throws IOException {
emailService.send(
request.email(),
"Welcome, " + request.name(),
"<h1>Welcome, " + request.name() + "</h1><p>Your account is ready.</p>"
);
return ResponseEntity.ok(Map.of("sent", true));
}
}Thymeleaf Email Templates
Spring Boot's Thymeleaf integration works for email templates too.
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency><!-- src/main/resources/templates/emails/welcome.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body style="font-family: sans-serif; background: #f6f9fc; padding: 40px 0;">
<div style="max-width: 480px; margin: 0 auto; background: #fff; padding: 40px; border-radius: 8px;">
<h1 style="font-size: 24px;" th:text="'Welcome, ' + ${name}">Welcome</h1>
<p style="font-size: 16px; line-height: 1.6; color: #374151;">
Your account is ready. Click below to get started.
</p>
<a th:href="${loginUrl}"
style="display:inline-block; background:#f97316; color:#fff; padding:12px 24px; border-radius:6px; text-decoration:none;">
Go to Dashboard
</a>
</div>
</body>
</html>// Render Thymeleaf template to HTML string
@Service
public class EmailTemplateService {
private final SpringTemplateEngine templateEngine;
public EmailTemplateService(SpringTemplateEngine templateEngine) {
this.templateEngine = templateEngine;
}
public String renderWelcome(String name, String loginUrl) {
Context context = new Context();
context.setVariable("name", name);
context.setVariable("loginUrl", loginUrl);
return templateEngine.process("emails/welcome", context);
}
}Async Email Sending
Don't block your request thread. Use @Async for fire-and-forget email sending.
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean
public Executor emailExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(5);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("email-");
executor.initialize();
return executor;
}
}@Service
public class AsyncEmailService {
private final EmailService emailService;
public AsyncEmailService(EmailService emailService) {
this.emailService = emailService;
}
@Async("emailExecutor")
public void sendAsync(String to, String subject, String body) {
try {
emailService.send(to, subject, body);
} catch (Exception e) {
log.error("Failed to send email to {}: {}", to, e.getMessage());
}
}
}Going to Production
1. Verify Your Domain
Add SPF, DKIM, DMARC records. Required for deliverability.
2. Use Spring Profiles for Config
# application-prod.properties
sequenzy.api-key=${SEQUENZY_API_KEY}3. Add Retry with Spring Retry
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>@Retryable(maxAttempts = 3, backoff = @Backoff(delay = 1000, multiplier = 2))
public Map<String, Object> send(String to, String subject, String body) {
// ... existing send logic
}Beyond Transactional
Sequenzy handles transactional sends, marketing campaigns, automated sequences, and subscriber management from one API. Native Stripe integration for SaaS.
Wrapping Up
- Spring Mail vs API providers and when to use each
- REST controllers for email endpoints
- Thymeleaf templates for maintainable HTML
- @Async for non-blocking sends
- Spring Retry for production reliability
Pick your provider, add the dependency, and start sending.