Send email with Spring Boot
Configure spring-boot-starter-mail for the PostStack SMTP relay and send HTML email with JavaMailSender — or call the REST API with RestClient.
Spring Boot’s mail starter auto-configures a JavaMailSender from spring.mail.* properties, so sending through PostStack is configuration: point it at smtp.poststack.dev with your API key as the password and inject the sender where you need it. Everything that already uses JavaMailSender — including Spring Security password-reset flows you have written on top of it — sends through PostStack unchanged. When you need the em_… email id back to match delivery and bounce webhooks, call the REST API with Spring’s RestClient instead. There is no Java SDK; both paths below use only Spring.
1. Add spring-boot-starter-mail
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
// Gradle: implementation("org.springframework.boot:spring-boot-starter-mail")2. Configure application.properties
# src/main/resources/application.properties
spring.mail.host=smtp.poststack.dev
spring.mail.port=587
spring.mail.username=poststack
spring.mail.password=${POSTSTACK_API_KEY}
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true
spring.mail.properties.mail.smtp.connectiontimeout=5000
spring.mail.properties.mail.smtp.timeout=10000
spring.mail.properties.mail.smtp.writetimeout=10000
# Port 465 instead: spring.mail.port=465 and
# spring.mail.properties.mail.smtp.ssl.enable=true (drop the starttls lines)3. Send with JavaMailSender
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
@Service
public class MailService {
private final JavaMailSender mailSender;
public MailService(JavaMailSender mailSender) {
this.mailSender = mailSender;
}
public void sendWelcome(String to, String name) throws MessagingException {
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setFrom("hello@yourdomain.com"); // must be on a verified domain
helper.setTo(to);
helper.setSubject("Welcome to Acme");
helper.setText(
"Welcome, " + name + "!", // plain-text part
"<h1>Welcome, " + name + "!</h1>" // HTML part
);
message.addHeader("X-PostStack-Tags", "welcome,onboarding");
mailSender.send(message);
}
}4. Handle errors
Spring Boot idioms for error handling, retries, and structured logging when sending through PostStack.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.mail.MailAuthenticationException;
import org.springframework.mail.MailSendException;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
// Needs @EnableAsync and @EnableRetry on a @Configuration class,
// plus org.springframework.retry:spring-retry and spring-boot-starter-aop.
@Service
public class WelcomeMailer {
private static final Logger log = LoggerFactory.getLogger(WelcomeMailer.class);
private final MailService mail;
public WelcomeMailer(MailService mail) {
this.mail = mail;
}
@Async
@Retryable(
retryFor = MailSendException.class, // timeouts, 4xx temporary replies
noRetryFor = MailAuthenticationException.class, // 535: bad API key, retrying won't help
maxAttempts = 4,
backoff = @Backoff(delay = 30_000, multiplier = 2))
public void send(String to, String name) throws Exception {
mail.sendWelcome(to, name);
}
@Recover
public void giveUp(Exception e, String to, String name) {
// 550 = permanent rejection, e.g. the From domain is not verified
log.error("PostStack could not send the welcome email to {}: {}", to, e.getMessage());
}
}Attachments and inline images
Use a multipart MimeMessageHelper. PostStack accepts up to 10 attachments per message, 10 MB per file and 25 MB in total. X-PostStack-Tags, X-PostStack-Idempotency-Key and X-PostStack-Schedule headers are read by the relay.
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setFrom("billing@yourdomain.com");
helper.setTo(order.getEmail());
helper.setReplyTo("support@yourdomain.com");
helper.setSubject("Invoice #" + order.getNumber());
helper.setText(
"Your invoice is attached.",
"<p>Your invoice is attached.</p><img src='cid:logo'>");
helper.addInline("logo", new ClassPathResource("static/logo.png"));
helper.addAttachment("invoice-" + order.getNumber() + ".pdf",
new ByteArrayResource(order.getInvoicePdf()), "application/pdf");
message.addHeader("X-PostStack-Idempotency-Key", "invoice-" + order.getId());
mailSender.send(message);Send through the REST API with RestClient
Spring Boot 3.2+ includes RestClient. A successful send returns 202 with { "id": "em_…" }; recipients are always an array.
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;
@Service
public class PostStackApi {
record SendResponse(String id) {}
private final RestClient client;
public PostStackApi(@Value("${poststack.api-key}") String apiKey) {
this.client = RestClient.builder()
.baseUrl("https://api.poststack.dev")
.defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey)
.build();
}
public String sendWelcome(String to) {
return client.post()
.uri("/emails")
.contentType(MediaType.APPLICATION_JSON)
.header("Idempotency-Key", UUID.randomUUID().toString()) // reuse it on retry
.body(Map.of(
"from", "Acme <hello@yourdomain.com>",
"to", List.of(to),
"subject", "Welcome to Acme",
"html", "<h1>Welcome!</h1>",
"tags", List.of("welcome")))
.retrieve() // throws RestClientResponseException on 4xx/5xx
.body(SendResponse.class)
.id();
}
}
// application.properties: poststack.api-key=${POSTSTACK_API_KEY}Templates with Thymeleaf
Render the HTML body with the TemplateEngine that spring-boot-starter-thymeleaf provides, then pass it to the helper.
Context ctx = new Context(Locale.ENGLISH);
ctx.setVariable("name", user.getName());
String html = templateEngine.process("mail/welcome", ctx); // templates/mail/welcome.html
helper.setText("Welcome, " + user.getName() + "!", html);Framework integrations
Auto-configured JavaMailSender
Setting spring.mail.host is enough for Spring Boot to create a JavaMailSender bean. Inject it by constructor; one instance is safe to share across threads.
Profiles for development
Use application-dev.properties to point spring.mail.host at a local catcher such as Mailpit on port 1025, and keep the PostStack settings in the production profile.
@Async and @Retryable
Sending over SMTP takes a network round-trip. Annotate the sending method with @Async so requests do not wait, and @Retryable so temporary failures are retried with backoff.
Health indicator
The mail starter adds a mail health indicator that connects to the SMTP server. It needs the correct TLS settings to report UP; disable it with management.health.mail.enabled=false if you do not want an SMTP connection on every health check.
Common pitfalls
MailAuthenticationException: 535
spring.mail.passwordmust be a PostStack API key with sending permission. Check thatPOSTSTACK_API_KEYis set in the environment the app actually runs in.Timeouts with the default settings
JavaMail waits forever by default. Always set
mail.smtp.connectiontimeout,mail.smtp.timeoutandmail.smtp.writetimeoutas shown, so a blocked port fails fast instead of hanging a thread.Rejected From address
The From address must be on a domain verified in PostStack.
SimpleMailMessagewithoutsetFromfalls back to the JVM’s default user, which is rejected.SSL on port 587 vs 465
Use
starttls.enable=trueon 587, orssl.enable=trueon 465. Enabling STARTTLS on 465 fails the handshake.
Notes
- The password is your PostStack API key; the username can be any non-empty value
- Port 587 accepts STARTTLS or implicit TLS; port 465 is implicit TLS (
mail.smtp.ssl.enable=true) - Spring Boot 3 uses
jakarta.mail; on Spring Boot 2 the same classes live injavax.mail - An
sk_test_...key accepts and logs mail without delivering it — use it on staging
FAQ
How do I send email in Spring Boot?
Add spring-boot-starter-mail, set spring.mail.host=smtp.poststack.dev, port 587, username poststack and your API key as the password with STARTTLS enabled, then inject JavaMailSender and send a MimeMessage built with MimeMessageHelper.
How do I send HTML email with JavaMailSender?
Create a MimeMessageHelper(message, true, "UTF-8") and call setText(plainText, html) — Spring builds a multipart message with both parts.
Is there a Java SDK?
No. Use SMTP through JavaMailSender, or the REST API with RestClient, WebClient or the JDK HttpClient. The API is a single JSON POST with a Bearer token.
Does this work with Spring Boot 2?
Yes. The spring.mail.* properties are the same; import javax.mail instead of jakarta.mail, and use RestTemplate or WebClient instead of RestClient.
Related guides
Ready to send emails with Spring Boot?
Create a free account and get your API key in under a minute.