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.

Quickstart#

Before you start

You need an API key (sk_live_…, or sk_test_… to try it without delivering anything) and a sending domain you have verified in PostStack. See Authentication and Domains.

  1. Add spring-boot-starter-mail

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

    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;
    
    // Spring Boot 3: needs @EnableAsync and @EnableRetry on a @Configuration class,
    // plus org.springframework.retry:spring-retry and spring-boot-starter-aop.
    // (Spring Boot 4 has @Retryable built into Spring Framework 7 instead —
    // org.springframework.resilience.annotation, enabled with @EnableResilientMethods.)
    @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,                 // connection failures and rejected sends
            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());
        }
    }

Good to know

  • 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

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.

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.

Ready to send from Spring Boot?

Create a free account and an API key — no card needed.

Get started free

Next steps

Was this page helpful?