Документация / Spring Boot

Отправка email в Spring Boot через SMTP

Стартер spring-boot-starter-mail собирает JavaMailSender из нескольких свойств, поэтому своего кода клиента писать не нужно. Ниже — подключение к fmailer, HTML-письма на Thymeleaf, вложения и асинхронная отправка.

Логин и пароль можно получить на странице управления токенами домена.

Параметры подключения

ПараметрЗначение
SMTP-хостsmtp.fmailer.ru
Порт587
ШифрованиеSTARTTLS (соединение переводится в TLS до авторизации)
ЛогинЛогин SMTP-токена вашего домена
ПарольПароль этого токена — показывается один раз, при создании
Каждый режим доступен и на высоком порту: 8465 (SSL/TLS), 8587 (STARTTLS) и 8025 (без шифрования). Многие хостинги и облака блокируют исходящие 25, 465 и 587 — если соединение отваливается по таймауту, используйте соответствующий высокий порт

Зависимость

Без неё бин JavaMailSender просто не создаётся:

pom.xmljava
<!-- Maven -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

Настройка application.properties

application.propertiesproperties
spring.mail.host=smtp.fmailer.ru
spring.mail.port=587
spring.mail.username=${SMTP_USER}
spring.mail.password=${SMTP_PASS}
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=10000
spring.mail.properties.mail.smtp.timeout=10000
spring.mail.properties.mail.smtp.writetimeout=10000

app.mail.from=Магазин <noreply@mail.example.ru>

Для шифрования с первого байта на порту 465:

properties
# Порт 465 — шифрование сразу, без STARTTLS
spring.mail.port=465
spring.mail.properties.mail.smtp.ssl.enable=true
spring.mail.properties.mail.smtp.starttls.enable=false
У JavaMail нет таймаутов по умолчанию. Если порт заблокирован, поток отправки зависает навсегда вместо того, чтобы упасть, — под нагрузкой это выедает пул потоков и кладёт приложение целиком.

Первое письмо

EmailService.javajava
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 text) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom(from);
        message.setTo(to);
        message.setSubject(subject);
        message.setText(text);
        mailSender.send(message);
    }
}

HTML-письмо

SimpleMailMessage умеет только текст. Для HTML соберите MimeMessage через MimeMessageHelper: метод setText с двумя аргументами принимает текстовую и HTML-часть и собирает multipart/alternative:

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

public void sendHtml(String to, String subject, String html, String text) throws Exception {
    MimeMessage message = mailSender.createMimeMessage();

    // true = multipart, чтобы в одно письмо поместились обе части
    MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
    helper.setFrom(from);
    helper.setTo(to);
    helper.setSubject(subject);
    helper.setText(text, html);   // (текст, HTML) — отправляйте обе части

    mailSender.send(message);
}

Письмо из шаблона Thymeleaf

java
import org.thymeleaf.context.Context;
import org.thymeleaf.spring6.SpringTemplateEngine;

private final SpringTemplateEngine templateEngine;

public void sendOrderConfirmation(Order order) throws Exception {
    Context ctx = new Context();
    ctx.setVariable("order", order);

    String html = templateEngine.process("email/order-confirmed", ctx);

    sendHtml(order.getCustomerEmail(),
             "Подтверждение заказа №" + order.getId(),
             html,
             "Заказ принят. Доставка — завтра до 18:00.");
}

Вложения и картинки в теле письма

java
public void sendInvoice(String to, File pdf) throws Exception {
    MimeMessage message = mailSender.createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");

    helper.setFrom(from);
    helper.setTo(to);
    helper.setSubject("Счёт на оплату");
    helper.setText("Счёт за март во вложении.");

    helper.addAttachment("invoice.pdf", pdf);
    helper.addInline("logo", new ClassPathResource("static/logo.png"));  // <img src="cid:logo">

    mailSender.send(message);
}

Асинхронная отправка

mailSender.send() блокирует поток на всё время обмена с SMTP. В веб-приложении это сотни миллисекунд удерживаемого потока запроса — вынесите отправку в отдельный executor:

java
@Configuration
@EnableAsync
public class AsyncConfig {

    @Bean(name = "mailExecutor")
    public Executor mailExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(5);
        executor.setQueueCapacity(500);
        executor.setThreadNamePrefix("mail-");
        executor.initialize();
        return executor;
    }
}

// В сервисе — вызывающий поток не ждёт SMTP.
@Async("mailExecutor")
public void sendOrderConfirmationAsync(Order order) throws Exception {
    sendOrderConfirmation(order);
}

Разбор ошибок

ОшибкаПричина и решение
NoSuchBeanDefinitionException: JavaMailSender Нет зависимости spring-boot-starter-mail или не задан spring.mail.host.
AuthenticationFailedException (535) Неверные доступы, либо mail.smtp.auth не равен true.
Отправка зависает Нет таймаутов, порт заблокирован. Задайте таймауты и используйте 8587.
MailSendException: Could not convert socket to TLS STARTTLS требуется на порту, где его нет. Проверьте пару порт — режим.
Кириллица приходит «кракозябрами» Передайте "UTF-8" в MimeMessageHelper.
SMTPSendFailedException (550)Домен отправителя не подтверждён.

Частые вопросы

Почему Spring Boot не может внедрить JavaMailSender?

Автоконфигурация срабатывает только когда в classpath есть spring-boot-starter-mail и задан spring.mail.host. Без любого из двух бин JavaMailSender не создаётся, и приложение падает с NoSuchBeanDefinitionException.

Нужно ли задавать mail.smtp.starttls.enable?

На порту 587 — обязательно. Spring Boot не включает STARTTLS сам, поэтому без этого свойства сессия остаётся открытой и сервер отказывает в авторизации. Ставьте ещё и starttls.required=true, чтобы неудачный переход в TLS был ошибкой, а не молчаливым откатом.

Почему отправка зависает вместо того, чтобы упасть с ошибкой?

У JavaMail нет таймаутов по умолчанию — при заблокированном порте поток ждёт бесконечно. Всегда задавайте mail.smtp.connectiontimeout, mail.smtp.timeout и mail.smtp.writetimeout. Если после этого начинается таймаут, значит хостинг блокирует исходящий 587: используйте 8587.

Чем SimpleMailMessage отличается от MimeMessage?

SimpleMailMessage отправляет только простой текст. Всё, где есть HTML, вложения, встроенные картинки или своя кодировка, требует MimeMessage — обычно через MimeMessageHelper с флагом multipart, равным true.

Как отправлять письма асинхронно в Spring Boot?

Пометьте метод отправки аннотацией @Async и заведите под него отдельный бин Executor, чтобы обмен с SMTP шёл в почтовом потоке, а не в потоке запроса. Помните, что @Async работает только при вызове из другого бина — вызов внутри того же класса минует прокси.

Почему авторизация падает с 535, хотя пароль верный?

Проверьте, задано ли spring.mail.properties.mail.smtp.auth=true — без него JavaMail вообще не отправляет учётные данные. И убедитесь, что в username указан полный логин SMTP-токена домена, а не только его первая часть.

Что дальше