Skip to content
Spring Boot sb production 4 min read

Sending Email

Almost every application eventually needs to send email — password resets, order confirmations, alerts. Spring Boot ships a thin, reliable abstraction over JavaMail through the spring-boot-starter-mail dependency. Adding it auto-configures a JavaMailSender from your spring.mail.* properties, so you inject one bean and send messages without dealing with Session, Transport, or protocol details directly.

Adding the starter

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

The starter pulls in jakarta.mail and configures a JavaMailSenderImpl bean as soon as spring.mail.host is set.

SMTP configuration

Point Spring at an SMTP server. For development, a fake SMTP server like MailHog or Mailpit catches everything locally so you never email real users by accident.

spring:
  mail:
    host: smtp.gmail.com
    port: 587
    username: ${MAIL_USER}
    password: ${MAIL_PASSWORD}
    properties:
      mail:
        smtp:
          auth: true
          starttls:
            enable: true
        connectiontimeout: 5000
        timeout: 5000
        writetimeout: 5000

app:
  mail:
    from: [email protected]

Warning: Never hard-code SMTP credentials. Resolve them from environment variables or a secret manager — see Secrets & Vault and Externalized Configuration.

Sending a plain text email

The simplest case uses a SimpleMailMessage.

import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;

@Service
public class EmailService {

    private final JavaMailSender mailSender;

    @Value("${app.mail.from}")
    private String from;

    public EmailService(JavaMailSender mailSender) {
        this.mailSender = mailSender;
    }

    public void sendPlainText(String to, String subject, String body) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom(from);
        message.setTo(to);
        message.setSubject(subject);
        message.setText(body);
        mailSender.send(message);
    }
}

Output (Mailpit captures the message):

From:    [email protected]
To:      [email protected]
Subject: Welcome to DevCraftly
Body:    Thanks for signing up!

HTML email with MimeMessageHelper

SimpleMailMessage can only send plain text. For HTML, inline images, or attachments you need a MimeMessage, wrapped by the convenient MimeMessageHelper.

import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import org.springframework.mail.javamail.MimeMessageHelper;

public void sendHtml(String to, String subject, String html) throws MessagingException {
    MimeMessage message = mailSender.createMimeMessage();
    // true = multipart so we can add attachments; UTF-8 for non-ASCII subjects
    MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
    helper.setFrom(from);
    helper.setTo(to);
    helper.setSubject(subject);
    helper.setText(html, true);   // second arg true => HTML body
    mailSender.send(message);
}

The true flag on setText tells the mail client to render the body as HTML rather than escaping it.

Adding attachments

With a multipart helper you can attach files or embed inline content.

import org.springframework.core.io.FileSystemResource;
import java.io.File;

public void sendWithAttachment(String to, String subject, String html, File invoice)
        throws MessagingException {
    MimeMessage message = mailSender.createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
    helper.setFrom(from);
    helper.setTo(to);
    helper.setSubject(subject);
    helper.setText(html, true);

    helper.addAttachment("invoice.pdf", new FileSystemResource(invoice));
    // Inline image referenced in HTML via <img src="cid:logo">
    helper.addInline("logo", new FileSystemResource(new File("static/logo.png")));

    mailSender.send(message);
}
MethodPurpose
setText(body, false)Plain text body
setText(body, true)HTML body
setText(plain, html)Multipart alternative (both versions)
addAttachment(name, res)File attachment
addInline(cid, res)Inline content referenced by cid:

Templated emails with Thymeleaf

Building HTML with string concatenation is painful and error-prone. The Thymeleaf template engine renders a real template into a String you hand to the helper.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

Create src/main/resources/templates/email/welcome.html:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
  <h2>Welcome, <span th:text="${name}">User</span>!</h2>
  <p>Your account <strong th:text="${email}">[email protected]</strong> is active.</p>
  <a th:href="${verifyUrl}">Verify your email</a>
</body>
</html>

Render it with the TemplateEngine and send the result:

import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

private final TemplateEngine templateEngine;   // injected via constructor

public void sendWelcome(String to, String name, String verifyUrl) throws MessagingException {
    Context context = new Context();
    context.setVariable("name", name);
    context.setVariable("email", to);
    context.setVariable("verifyUrl", verifyUrl);

    String html = templateEngine.process("email/welcome", context);
    sendHtml(to, "Welcome to DevCraftly", html);
}

Tip: Keep email templates in a dedicated templates/email/ folder so they don’t collide with MVC view templates, and test rendering separately from sending.

Sending asynchronously

SMTP round-trips take hundreds of milliseconds — far too long to block an HTTP request thread. Mark the send method @Async so the caller returns immediately while a background thread does the work.

import org.springframework.scheduling.annotation.Async;

@Async
public void sendWelcomeAsync(String to, String name, String verifyUrl) throws MessagingException {
    sendWelcome(to, name, verifyUrl);
}

Enable async support once on a configuration class with @EnableAsync (and ideally configure a dedicated executor and an error handler so swallowed failures are logged). Because async runs through an AOP proxy, the call must come from another bean — self-invocation bypasses it. See Async Methods for the full setup, including thread pools and exception handling.

@Configuration
@EnableAsync
public class AsyncMailConfig {
}

Note: A failed async send returns nothing to the caller. Log failures, and for critical mail (password resets) consider persisting an outbox row and retrying, rather than firing and forgetting.

Best Practices

  • Use a fake SMTP server in development; never point tests at a real provider.
  • Send via @Async so mail latency never blocks request threads.
  • Build HTML with Thymeleaf templates, not string concatenation.
  • Resolve credentials from the environment or a secret manager, never source.
  • For high-volume or critical mail, use a transactional outbox with retries instead of fire-and-forget.
Last updated June 13, 2026
Was this helpful?