Всеосяжний посібник з електронної пошти у Spring

Надсилання електронних листів з вашого застосунку досі є однією з найважливіших нефункціональних вимог, яку повинен мати ваш додаток.

У цій статті ми розглянемо підтримку відправлення електронних листів у Spring Boot.

Ми дізнаємось, як відправляти прості текстові електронні листи, листи з вкладеннями та вбудованими зображеннями, а також шаблонізовані HTML-електронні листи, які ви можете перекладати та персоналізувати, впроваджуючи динамічні значення, специфічні для користувача.

Ми розглянемо, що потрібно для налаштування локального середовища та середовища для тестування за допомогою сервісів, таких як MailPit та GreenMail, а також продемонструємо, як налаштувати безкоштовний сервер SMTP Gmail, який наш додаток може використовувати для відправлення електронних листів.

Якщо ви віддаєте перевагу відеоконтенту, доступний відеоурок по цій темі.

Поштові сервери

Для відправлення пошти нам буде потрібен доступ до SMTP сервера (Simple Mail Transfer Protocol).

Для локальної розробки ми можемо налаштувати інструменти, такі як MailPit, які діють як локальний SMTP сервер і перехоплюють листи, які ми відправляємо.

Google також надає безкоштовну SMTP послугу для наших облікових записів Gmail, яку ми можемо використовувати.

Коли нам потрібно масштабуватися, ми можемо перейти на спеціалізовані поштові сервіси, такі як MailGun.

Локальний поштовий сервер

Під час розробки коду для відправлення електронних листів ми не хочемо перевантажувати реальні поштові сервери нашими тестовими повідомленнями.

Замість цього ми можемо використовувати інструменти, які діють як локальні SMTP сервери, перехоплюючи листи, які ми відправляємо, і надаючи веб-інтерфейс, що дозволяє розробникам переглядати електронні листи без відправлення їх на реальні адреси.

Одним із перших таких інструментів був MailHog, який зараз більше не підтримується активно, але надихнув інші подібні інструменти, такі як MailPit.

Ми все ще можемо використовувати MailHog, але MailPit має більше функцій і активно розвивається.

Ми можемо створити визначення для нашого сервісу mailpit у файлі docker-compose.yml:

services:  
 mailpit:  
 image: axllent/mailpit  
 container_name: mailpit  
 ports:  
 - 8025:8025  
 - 1025:1025

Запустити його можна командою docker compose up -d, а зупинити та очистити ресурси за допомогою docker compose down -v.

MailPit надає зручний веб-інтерфейс на порту 8025, де ви можете переглядати, шукати та перевіряти електронні листи, що були захоплені, включаючи їх вміст та заголовки.

Він працює як локальний SMTP сервер, що означає, що ви можете налаштувати ваш додаток для відправлення електронних листів безпосередньо на Mailpit замість реального поштового сервера.

Відправка пошти для Java та Spring

У Java, Jakarta Mail (раніше JavaMail) використовується для відправлення та отримання електронної пошти через протоколи SMTP, POP3 та IMAP.

Spring надає залежність для роботи з поштою, яка полегшує роботу з Jakarta Mail.

Основною перевагою, яку надає абстракція Spring, є те, що нам не потрібно безпосередньо працювати з Jakarta Session.

Крім того, Spring надає допоміжні класи для легшого створення повідомлень електронної пошти.

Додавши залежність у ваш проект:


 org.springframework.boot  
 spring-boot-starter-mail  

та визначивши властивості spring.mail.* для хоста, порту, імені користувача та паролю:

spring.mail.host=localhost  
spring.mail.port=1025  
spring.mail.username=user123  
spring.mail.password=admin

Spring автоматично налаштовує та надає bean JavaMailSender, який ми можемо впровадити та використовувати для відправлення пошти.

Якщо потрібно відправляти прості текстові повідомлення, можна використовувати SimpleMailMessage, а для HTML повідомлень та повідомлень з вбудованими зображеннями і вкладеннями — MimeMessage (багаточастинне повідомлення).

Текстові повідомлення електронної пошти

Spring надає допоміжний клас SimpleMailMessage для спрощення відправлення текстових електронних листів.

У класі SimpleMailMessage можна налаштовувати параметри електронної пошти, такі як from, to, subject та text, і передавати їх екземпляру JavaMailSender для відправлення текстових електронних листів.

@Service  
@Value  
public class SimpleMailService {  
 JavaMailSender javaMailSender;  

public void sendTextMessage(String to, String subject, String text) {  
 SimpleMailMessage message = new SimpleMailMessage();  
 message.setFrom("[email protected]");  
 message.setTo(to);  
 message.setSubject(subject);  
 message.setText(text);  
 javaMailSender.send(message);  
 }  
}

HTML повідомлення електронної пошти

MIME (Multipurpose Internet Mail Extensions) повідомлення — це формат електронної пошти, який дозволяє включати різні типи вмісту, такі як текст, зображення, аудіо та відео, а не лише звичайний текст.

Spring надає допоміжний клас MimeMessageHelper для полегшення відправлення HTML-повідомлень з вкладеннями та вбудованими ресурсами.

Ми створюємо MimeMessageHelper для MimeMessage, який ми отримуємо від екземпляра JavaMailSender.

Ми можемо налаштувати параметри електронної пошти, такі як from, to, subject та, що найголовніше, text як html (другий параметр методу setText встановлюється в true), і передати це екземпляру JavaMailSender для відправлення MIME-повідомлень.

Ми можемо додавати вкладення та вбудовані ресурси за допомогою відповідних методів.

Важливо пам'ятати, що порядок для вбудованих ресурсів має значення і вони повинні бути додані ПІСЛЯ того, як ми встановимо текст у форматі HTML.
Inline Content id needs to match html cid: attribute (in our example logo).

@Service  
@Value  
public class SimpleMailService {  
 JavaMailSender javaMailSender;  

 public void hardcodedHtmlExample() throws MessagingException, FileNotFoundException {  
 MimeMessage message = javaMailSender.createMimeMessage();  
 MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");  
 helper.setFrom("[email protected]");  
 helper.setTo("[email protected]");  
 helper.setSubject("html mail with resources");  

 helper.addAttachment("junit-cheat-sheet.pdf", ResourceUtils.getFile("classpath:templates/mail/attachments/junit-cheat-sheet.pdf"));  

 helper.setText("", true);  
 // Ordering is important for inlining  
 // we first need to add html text, then resources  
 helper.addInline("logo", ResourceUtils.getFile("classpath:templates/mail/attachments/email-logo.png"));  

 javaMailSender.send(message);  
 }  
}

Шаблон повідомлення електронної пошти

Зазвичай ми не хочемо створювати вміст повідомлень електронної пошти в коді Java, оскільки це може бути трудомістким і схильним до помилок.

Натомість, ми можемо використати бібліотеку шаблонів (таку як Thymeleaf або FreeMarker), щоб визначити структуру відображення вмісту повідомлення електронної пошти.

У коді наша задача полягає тільки в створенні даних, які потрібно відобразити в шаблоні електронної пошти.

pic

Коли ми використовуємо бібліотеку шаблонів, у нашому прикладі thymeleaf:


 org.springframework.boot  
 spring-boot-starter-thymeleaf  

Spring автоматично налаштовує bean SpringTemplateEngine, який ми можемо впровадити в нашому сервісі.

Ми можемо використовувати шаблонний двигун для заповнення моделі значеннями та обробки шаблону, щоб отримати кінцевий HTML, який ми можемо передати нашому сервісу для відправки електронних листів.

@Service  
@Value  
public class TemplateMailService {  
 SpringTemplateEngine thymeleafTemplateEngine;  
 SimpleMailService simpleMailService;  

 record SingleColumnTemplate(String title, String preview, String body){}  

 public void sendHtmlTemplateMessage(String to, String subject, SingleColumnTemplate singleColumnTemplate) throws MessagingException {  
 Map templateModel = new HashMap<>();  
 templateModel.put("title", singleColumnTemplate.title);  
 templateModel.put("preview", singleColumnTemplate.preview);  
 templateModel.put("body", singleColumnTemplate.body);  

 Context thymeleafContext = new Context();  
 thymeleafContext.setVariables(templateModel);  
 String templateHtmlBody = thymeleafTemplateEngine.process("mail/single-column-template.html", thymeleafContext);  

 simpleMailService.sendHtmlMessage(to, subject, templateHtmlBody);  
 }  
}

Шаблони — це HTML файли, що містять правила відповідно до бібліотеки шаблонів.

Для нашого прикладу mail/single-column-template.html thymeleaf очікує:

  • значення title, preview та body для моделі
  • файл шаблону має бути розміщений в директорії resources/templates/mail, оскільки thymeleaf за замовчуванням налаштований на пошук шаблонів в директорії resources/templates

single-column-template.html — це HTML файл, який очікує значення моделі та використовує атрибути data-th-text і data-th-utext thymeleaf для їх вставки.





......  




    This will be displayed underneath the subject line. Use it wisely.   
   ......        
Lorem  
......


## Інтернаціоналізація

Щоб відправляти перекладені повідомлення, нам потрібно визначити ресурси повідомлень для кожної мови, яку ми хочемо підтримувати, та використовувати вбудовану бібліотеку для вставлення правильних значень для кожної мови.

У папці ресурсів визначаємо файл `message.properties` з ключами повідомлень для мови за замовчуванням, в цьому прикладі: _greetings_, _signature_ і _regards_.

greetings=Hello {0}
signature=yours truly {0}
regards=kind regards
```

Створюємо файли message_.properties і переклади для кожної мови, яку хочемо підтримувати.

Ми можемо використовувати повідомлення в наших шаблонах thymeleaf за допомогою #{message_key()}:








 ```  і ми встановлюємо локаль, коли обробляємо шаблони за допомогою:  ``` Context thymeleafContext = new Context();   thymeleafContext.setLocale();   String templateHtmlBody = thymeleafTemplateEngine.process("
Тексти повідомлень можуть бути витягнуті, перевірені та змінені в наших тестових випадках.

Щоб використовувати GreenMail у інтеграційних тестах Spring, ми можемо зареєструвати розширення GreenMail, якщо нам потрібно перевірити, що правильне повідомлення відправляється.

Ми починаємо з того, що додаємо GreenMail як залежність до нашого проєкту:

com.icegreen
greenmail-junit5
2.0.1
test


і визначаємо відповідні параметри `spring.mail.*` для **тестового** файлу `application.properties`:

spring.mail.host=localhost

стандартний порт протоколу greenmail + 3000 як зсув

spring.mail.port=3025
spring.mail.username=user
spring.mail.password=pass
spring.mail.protocol=smtp

не встановлюйте це в true при використанні серверу GreenMail для кожного тесту

spring.mail.test-connection=false
```

Тепер ми можемо використовувати розширення GreenMail у наших інтеграційних тестах:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)  
class MailControllerIT {  

 @RegisterExtension  
 static GreenMailExtension greenMail =  
 new GreenMailExtension(ServerSetupTest.SMTP)  
 .withConfiguration(GreenMailConfiguration  
 .aConfig()  
 // те саме, що в тестовому application.properties  
 .withUser("user", "pass"))  
 .withPerMethodLifecycle(false);  

 @Autowired  
 private TestRestTemplate testRestTemplate;  

 @Test  
 void shouldSendEmailWithCorrectPayloadToUser() {  
 String payload = """  
 {  
 "to": "[email protected]",  
 "subject": "text mail",  
 "text": "example of plain text mail"  
 }  
 """;  

 HttpHeaders headers = new HttpHeaders();  
 headers.setContentType(MediaType.APPLICATION_JSON);  
 HttpEntity request = new HttpEntity<>(payload, headers);  

 ResponseEntity response =  
 this.testRestTemplate.postForEntity("/api/v1/mail/text", request, Void.class);  

 assertEquals(200, response.getStatusCode().value());  

 await()  
 .atMost(2, SECONDS)  
 .untilAsserted(  
 () -> {  
 MimeMessage[] receivedMessages = greenMail.getReceivedMessages();  
 assertEquals(1, receivedMessages.length);  

 MimeMessage receivedMessage = receivedMessages[0];  
 assertEquals(1, receivedMessage.getAllRecipients().length);  
 assertEquals("[email protected]", receivedMessage.getAllRecipients()[0].toString());  
 assertEquals("example of plain text mail", GreenMailUtil.getBody(receivedMessage));  
 assertEquals("text mail", receivedMessage.getSubject());  
 });  

 }  

}

Testcontainers — це бібліотека для надання одноразових, легких екземплярів всього, що може працювати в контейнері Docker.

За допомогою Testcontainers нам не потрібно мокати наші тестові середовища.
Замість того, щоб мокати наші тестові середовища, ми визначаємо залежності тесту як код, а потім просто запускаємо тести, і контейнери будуть створені, а потім видалені.

Spring надає інтеграцію з Testcontainers:


 org.springframework.boot  
 spring-boot-testcontainers  
 test  

На даний момент немає спеціального модуля Testcontainers для GreenMail, але ми можемо використовувати GenericContainer, щоб перевірити, що наш сервіс електронної пошти дійсно відправляє електронні листи.

@SpringBootTest  
@Testcontainers  
public class MailServiceIT {  

 @Autowired  
 MailService mailService;  
 static final String MAIL_USERNAME = "username";  
 static final String MAIL_PASSWORD = "user_password";  

 @Container  
 static GenericContainer greenMailContainer = new GenericContainer(DockerImageName.parse("greenmail/standalone:2.1.2"))  
 .waitingFor(Wait.forLogMessage(".*Starting GreenMail standalone.*", 1))  
 .withEnv("GREENMAIL_OPTS", "-Dgreenmail.setup.test.smtp -Dgreenmail.hostname=0.0.0.0 -Dgreenmail.users=" + MAIL_USERNAME + ":" + MAIL_PASSWORD)  
 .withExposedPorts(ServerSetupTest.SMTP.getPort());  

 @DynamicPropertySource  
 static void configureMailHost(DynamicPropertyRegistry registry) {  
 registry.add("spring.mail.host", greenMailContainer::getHost);  
 registry.add("spring.mail.port", greenMailContainer::getFirstMappedPort);  
 registry.add("spring.mail.username", () -> MAIL_USERNAME);  
 registry.add("spring.mail.password", () -> MAIL_PASSWORD);  
 }  


 @Test  
 void sendingMailWithTextMessageWorks() throws MessagingException, IOException {  
 mailService.sendTextMessage("[email protected]", "text message test", "plain text in mail body");  
 }  
}

Оскільки модулі Testcontainers стартують на випадкових портах для забезпечення паралельного тестування та уникнення зіткнень портів, нам потрібно почекати на запуск контейнера Docker для GreenMail, слухаючи відповідне повідомлення в логах, і після запуску динамічно реєструвати параметри spring.mail.*, використовуючи DynamicPropertyRegistry від Spring.

Після налаштування GreenMail за допомогою Testcontainers у Spring ми можемо перевірити, що наш сервіс електронної пошти дійсно відправляє листи.

Gmail SMTP сервер

Посилання, яке описує, як використовувати підтримку Email через Google Workspace.

Ви можете відправляти електронні листи будь-кому всередині або поза вашою організацією, використовуючи smtp.gmail.com як ваш SMTP сервер.

Цей варіант вимагає автентифікації за допомогою вашого акаунта Gmail або Google Workspace та пароля під час налаштування.

Пристрій використовує ці облікові дані щоразу, коли намагається відправити електронний лист.

Ліміт відправки складає 2000 повідомлень на день.

Пароль додатка — це 16-значний код доступу, який надає менш безпечному додатку або пристрою дозвіл доступу до вашого акаунта Google.

Паролі додатків можна використовувати тільки з акаунтами, для яких увімкнено 2-крокову перевірку.

pic

Керуйте паролями додатків на: https://myaccount.google.com/apppasswords

pic

Після створення пароля додатка вам буде надано одноразовий 16-значний код, який ви можете використовувати як пароль для вашого акаунта Google:

pic

Інструкції щодо того, що таке паролі додатків і як їх використовувати

Gmail SMTP параметри

Для адреси сервера введіть smtp.gmail.com.

Для порту введіть одне з наступних значень:

  • Для SSL введіть 465
  • Для TLS введіть 587

Для автентифікації введіть вашу повну адресу електронної пошти Google (наприклад: [email protected]) та пароль додатка.

Приклад файлу application.properties для Gmail SMTP для Spring Boot додатка:

НАЛАШТУВАННЯ ПОШТИ ДЛЯ GMAIL ##

####################

---

spring.config.activate.on-profile=gmail
spring.mail.username=${GMAILUSERNAME:[email protected]}
spring.mail.password=${GMAIL
PASSWORD:yourGmailPassword}
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.properties.mail.transport.protocol=smtp
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.ssl.trust=smtp.gmail.com
```

Це налаштування очікує, що змінні середовища GMAILUSERNAME_ та GMAILPASSWORD_ будуть заповнені відповідними значеннями, а профіль gmail буде активний.

GitHub репозиторій з прикладами коду.

Перекладено з: Comprehensive Guide To Email in Spring

Leave a Reply

Your email address will not be published. Required fields are marked *