Skip to content

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

bash
<!-- 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

java
# 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

java
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.

java
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.

java
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.

java
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.

java
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.password must be a PostStack API key with sending permission. Check that POSTSTACK_API_KEY is 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.timeout and mail.smtp.writetimeout as 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. SimpleMailMessage without setFrom falls back to the JVM’s default user, which is rejected.

  • SSL on port 587 vs 465

    Use starttls.enable=true on 587, or ssl.enable=true on 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 in javax.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.