본문 바로가기
Java/Spring

AWS SES와 Thymeleaf를 사용하여 이메일 보내기

by 서사주 2022. 10. 29.

최근 구독 만료 일자가 얼마 남지 않은 사용자를 대상으로 구독기간 만료 예정 안내 메일을 보내야 했습니다.

한국에는 관련 법률이 있는지 잘 모르지만, 미국 특정 주에서는 관련 법률이 있다는 것을 확인했습니다.

 

2011 California Code :: Business and Professions Code :: DIVISION 7. GENERAL BUSINESS REGULATIONS [16000 - 18001] :: ARTICLE

Disclaimer: These codes may not be the most recent version. California may have more current or accurate information. We make no warranties or guarantees about the accuracy, completeness, or adequacy of the information contained on this site or the informa

law.justia.com

 

메일 서버는 AWS에서 제공하는 SES (Simple Email Service)를 사용하였습니다.

AWS에서 제공하는 API를 사용하거나, SMTP 인터페이스를 사용하는 두 가지 방법 중

추후 호환성을 위해 SMTP 인터페이스를 구현하였습니다.

 

SES SMTP 자격 증명을 얻는 것은 아래의 사이트를 참고해주시길 바랍니다.

 

Amazon SES SMTP 자격 증명 획득 - Amazon Simple Email Service

SMTP 엔드포인트는 현재 아프리카(케이프타운), 유럽(밀라노), 중동(바레인)에서는 사용할 수 없습니다.

docs.aws.amazon.com

 

프로그래밍 방식으로 Amazon SES SMTP 인터페이스를 통해 이메일 전송 - Amazon Simple Email Service

프로그래밍 방식으로 Amazon SES SMTP 인터페이스를 통해 이메일 전송 Amazon SES SMTP 인터페이스를 사용하여 이메일을 전송하기 위해 SMTP 지원 프로그래밍 언어, 이메일 서버 또는 애플리케이션을 사

docs.aws.amazon.com


SmtpMailSender.java

public void send(String to, String subject, String content) {

    Properties properties = System.getProperties();
    properties.put("mail.transport.protocol", "smtp");
    properties.put("mail.smtp.port", port);
    properties.put("mail.smtp.starttls.enable", "true");
    properties.put("mail.smtp.auth", "true");

    Session session = Session.getDefaultInstance(properties);

    MimeMessage mimeMessage;
    try {
        Address address = new InternetAddress(to);

        mimeMessage = new MimeMessage(session);
        mimeMessage.setFrom(new InternetAddress(fromEmail, fromName));
        mimeMessage.setRecipient(Message.RecipientType.TO, address);
        mimeMessage.setSubject(subject, StandardCharsets.UTF_8.name());
        mimeMessage.setContent(content, "text/html;charset=UTF-8");
    } catch (MessagingException | UnsupportedEncodingException e) {
        throw new RuntimeException(e);
    }

    try (Transport transport = session.getTransport()) {
        log.info("Sending...");

        transport.connect(host, username, password);

        transport.sendMessage(mimeMessage, mimeMessage.getAllRecipients());
        log.info("Email Sent!");
    } catch (MessagingException e) {
        log.error("The email was not sent.");
        log.error("Error message: " + e.getMessage());
    }
}

SMTP 인터페이스를 통해 메일을 보내는 코드입니다.

추후 메일 서버가 바뀌더라도 코드의 변경 없이 메일을 보낼 수 있습니다.

 

application.yml

aws:
  ses:
    from: 송신자 이메일
    from-name: 송신자 이름
    smtp:
      host: email-smtp.ap-northeast-2.amazonaws.com
      port: 587
      username: AWS SES SMTP USERNAME
      password: AWS SES SMTP PASSWORD

송신에 필요한 데이터는 application.yml 에 넣어 코드와 분리를 시켰습니다.


메일을 보내기 위한 준비는 끝났으니, 하나의 예시를 들어보겠습니다.

저희가 메일을 보낼 대상은 "곧 구독이 만료되는 사용자"입니다.

 

월간 구독, 연간 구독이 있다고 가정하면

월간 구독은 만료 10일 전, 연간 구독은 만료 25일 전에 메일을 보내야 합니다.

 

메일은 다음과 같이 보내려고 합니다.

<h1>월간 구독 만료 안내입니다.</h1>

<p>안녕하세요? 회원님의 구독이 2022-11-10에 만료가 됩니다. 감사합니다.</p>

반드시 변경이 되어야 하는 값날짜입니다.

추가로, 위의 제목도 구독 유형에 따라 변경이 될 수 있습니다.

 

즉, HTML 파일은 하나로만 관리하고, 구독 유형에 따라 제목과 날짜를 바꾼다고 하면

다음과 같이 이메일 템플릿을 작성할 수 있습니다.

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title th:text="${title}"></title>
</head>
<body>
    <h1 th:text="${title}"></h1>
    <p>안녕하세요? 회원님의 구독이 <span th:text="${expireDate}"></span>에 만료가 됩니다. 감사합니다.</p>
</body>
</html>

 

 

Sending email in Spring with Thymeleaf - Thymeleaf

Using Thymeleaf for processing our email templates would allow us to use some interesting features: Also, given the fact that Thymeleaf has no required dependencies on the servlet API, there would be no need at all to create or send email from a web applic

www.thymeleaf.org

Thymeleaf를 사용하여 이메일을 보내는 방법은 위 공식 블로그를 참고하였습니다.

 

TemplateEngine을 사용해 이메일 템플릿을 해석하여 변환합니다.

어떤 내용이 들어갈지는 천차만별이지만, 메일을 보내는 과정은 크게 다르지 않을 것입니다.

MailSenderController.java

@PostMapping
public ResponseEntity<Void> sendSubscriptionExpireMail(@Validated @RequestBody MailSenderRequest.Send request) {

    int threads = Runtime.getRuntime().availableProcessors() + 1;
    ExecutorService executorService = Executors.newFixedThreadPool(threads);

    request.getTo().forEach(to -> {
        executorService.submit(() -> {
            Context context = getSubscriptionExpireMailContext(request.getSubscriptionType());
            String html = templateEngine.process("subscription-expire", context);

            mailService.send(to, (String) context.getVariable("title"), html);
        });
    });

    executorService.shutdown();

    return null;
}

private Context getSubscriptionExpireMailContext(SubscriptionType subscriptionType) {

    Context context = new Context();

    int daysToAdd;
    String title;

    switch (subscriptionType) {
        case MONTHLY:
            daysToAdd = 10;
            title = "월간 구독 만료 안내입니다.";
            break;
        case ANNUALLY:
            daysToAdd = 25;
            title = "연간 구독 만료 안내입니다.";
            break;
        default:
            throw new IllegalStateException("처리할 수 없는 메일 타입입니다.");
    }

    context.setVariable("expireDate", LocalDate.now().plusDays(daysToAdd));
    context.setVariable("title", title);

    return context;
}

 

대상자가 많을 경우를 대비해 멀티 스레드로 작동되도록 했습니다.

스레드는 ExecutorService를 사용하여 스레드 풀 관리를 하였습니다.

 

Java] ExecutorService란?

❓ ExecutorService란? 병렬 작업 시 여러 개의 작업을 효율적으로 처리하기 위해 제공되는 JAVA 라이브러리이다. ❔ ExecutorService가 없었다면? 각기 다른 Thread를 생성해서 작업을 처리하고, 처리가 완

simyeju.tistory.com


 

Spring Web 의존성을 추가했기 때문에, POST 요청을 보내면 메일을 보낼 수 있습니다.

POST http://localhost:8080/mail
Content-Type: application/json

{
  "to": [
    "test@test.com",
    "test2@test.com"
  ],
  "subscriptionType": "MONTHLY"	// ANNUALLY는 연간
}

위 포스트에 적힌 코드는 아래의 Repo에서 확인할 수 있습니다.

 

GitHub - seosaju/aws-ses-using-thymeleaf: AWS SES와 Thymeleaf를 사용하여 이메일 보내기

AWS SES와 Thymeleaf를 사용하여 이메일 보내기. Contribute to seosaju/aws-ses-using-thymeleaf development by creating an account on GitHub.

github.com