Spring Boot에서 메일보내기

뾰족머리삼돌이·2025년 1월 14일

Spring

목록 보기
14/14
post-thumbnail

Spring Framework 문서Spring Boot 문서를 참고해서 메일을 보내보자

의존성 및 환경세팅

우선, Spring Boot환경에서 진행할 것이기 때문에 메일을 보내기위한 Starter를 받아온다.

Spring이 메일기능을 위해 제공하는 최상의 패키지는 org.springframework.mail 다.
해당 패키지 내에는 최상위 인터페이스로 MailSender가 존재하며, MIME 메시지 지원기능을 위한 확장으로 JavaMailSender 인터페이스를 제공한다.

해당 인터페이스를 사용하기 위해서는 Bean으로 등록해줘야하는데, Spring Boot의 자동설정을 알아보기 이전에 Configuration 파일로 Bean을 등록하는 것을 알아보자.

@Bean
JavaMailSender mailSender() throws MessagingException {
    JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
    mailSender.setHost("smtp.gmail.com");
    mailSender.setUsername("{구글 계정}");
    mailSender.setPassword("{앱 비밀번호}");

    Properties properties = mailSender.getJavaMailProperties();
    properties.put("mail.smtp.starttls.enable", "true");
    properties.put("mail.smtp.auth", "true");

    return mailSender;
}

Bean을 등록하기위해 필요한 정보는 host, username, password다.
각각 메일전송을 위한 메일서버(SMTP) 호스트 정보, 그리고 해당 호스트에 접속할 수 있는 계정정보를 의미한다.

이 포스팅에서는 구글 메일서버를 이용해 메일을 보낼 것이다.
따라서, 호스트로는 smtp.gmail.com을 입력하고 username과 password는 각각 구글 계정과 앱 비밀번호르 입력하자

앱 비밀번호는 구글 계정정보에서 2단계 보안을 클릭 및 활성화하고, 하단에 위치하는 앱 비밀번호를 클릭해서 생성하면 된다.

예제

@Service @Slf4j
@RequiredArgsConstructor
public class MailTestService {

    private final MailSender mailSender;

    public void sendMail(String email) {
        SimpleMailMessage msg = new SimpleMailMessage();
        msg.setTo(email);
        msg.setText("테스트용 메일 메시지");

        log.info("{}, {}", mailSender.getClass(), msg);
        try{
            log.info("Send To : {}, message : {}", email, msg.getText());
            mailSender.send(msg);
        }catch(MailException e){
            log.error(e.getMessage());
        }
    }
}

POST /mail로 요청을 보내면 body에 담긴 email로 메일을 전송하는 예제를 작성했다.

이후, 요청을 전송하면 정상적으로 메일이 대상자에게 도착하는 것을 확인할 수 있다.

Failed messages: org.eclipse.angus.mail.smtp.SMTPSendFailedException: 530-5.7.0 Must issue a STARTTLS command first. For more information, go to
530-5.7.0  https://support.google.com/a/answer/3221692 and review RFC 3207
530 5.7.0 specifications. d9443c01a7336-21a9f2618d3sm61410685ad.255 - gsmtp

메일을 전송헀는데 서버에서 이런 로그가 찍히면서 실패할 수도 있다. 로그를 살펴보면 RFC 3207 문서를 확인하라는 것을 알 수 있다. 이는 SMTP 서버와 클라이언트가 TLS계층을 통해 개인정보를 제공하기 위한 설정이 누락되어서 발생하는 에러다. 즉, 메일기능을 위해 JavaMailSender Bean을 생성하는 과정에서 계정정보를 SMTP서버로 전송해야하는데, 이 과정에서 SMTP서버에서 보안연결을 요구했으나 클라이언트에 관련 설정이 누락되어 발생하는 에러다.

JavaMailSender Bean 등록과정에 하단의 설정을 추가하면 정상적으로 동작한다.

Properties properties = mailSender.getJavaMailProperties();
properties.put("mail.smtp.starttls.enable", "true");
properties.put("mail.smtp.auth", "true");

프로퍼티와 자동설정을 이용한 Bean 등록

Configuration 파일로 JavaMailSender Bean을 등록해도 무방하나, SpringBoot 환경이므로 자동설정과 프로퍼티를 이용해 Bean을 등록해보자. SpringBoot에서 자동설정을 위한 프로퍼티 설정값은 MailProperties 클래스에서 확인할 수 있다.

prefix로는 spring.mail를 사용하면 되므로, 이를 이용해 프로퍼티 파일을 작성해보자

spring:
  mail:
    host: smtp.gmail.com
    username: {구글 계정}
    password: {앱 비밀번호}
    properties[mail.smtp.starttls.enable]: true
    properties[mail.smtp.auth]: true

여기까지 작성하면 SpringBoot 자동설정을 통해 JavaMailSender 객체가 생성된다.

MimeMessage

앞선 예제들에서는 성공적으로 메일을 전송할 수 있으나, 그 형식이 텍스트로 제한된다.
따라서, 메일 내용을 HTML로 확장하기 위해서는 MimeMessage 객체를 이용해야한다.

MimeMessagePreparator를 이용한 방법

private final JavaMailSender mailSender;

public void sendMail(String email) {

    String htmlMessage =  """
            <html>
                <body>
                    <h1 style="color:blue;">환영합니다!</h1>
                    <p>Spring 이메일 서비스 테스트 중입니다.</p>
                    <p>감사합니다.</p>
                </body>
            </html>
            """;

    MimeMessagePreparator preparator = (mimeMessage) -> {
        mimeMessage.setRecipients(Message.RecipientType.TO, email);
        mimeMessage.setSubject("테스트용 메일");
        mimeMessage.setContent(htmlMessage, "text/html; charset=UTF-8");
    };

    try{
        mailSender.send(preparator);
    }catch(MailException e){
        log.error(e.getMessage());
    }
}

우선, MimeMessagePreparator를 이용한 HTML 메시지 전송 예시다.

해당 인터페이스는 void prepare(MimeMessage)의 단일 추상메서드를 가진 콜백 인터페이스로, MimeMessagesetContent를 이용해 HTML 형식을 지정해주는 것으로 HTML 메시지를 전송할 수 있다.


실제로 도착한 메일을 살펴보면 HTML 형식이 정상적으로 출력되는 것을 확인할 수 있다.

MimeMessagePreparator는 사실 메일전송과 관련된 코드를 캡슐화하여 독립적으로 관리 및 재사용할 수 있다는 데에 의의가 있다. 당연히 후술되는 MimeMessageHelperprepare() 내에서 사용할 수 있으므로, 그냥 이런 방식도 가능하다는 것을 보여주기 위해 작성했다.

MimeMessageHelper를 이용한 방법

private final JavaMailSender mailSender;

public void sendMail(String email) {

    String htmlMessage =  """
            <html>
                <body>
                    <h1 style="color:blue;">환영합니다!</h1>
                    <p>Spring 이메일 서비스 테스트 중입니다.</p>
                    <p>감사합니다.</p>
                </body>
            </html>
            """;

    try{
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage);
        helper.setTo(email);
        helper.setSubject("테스트용 메일");
        helper.setText(htmlMessage, true);

        log.info("Send To : {}, message : {}", email, mimeMessage);
        mailSender.send(mimeMessage);
    }catch(MailException | MessagingException e){
        log.error(e.getMessage());
    }
}

MimeMessageHelper를 사용하는 경우에는 MessagingException 예외를 처리해줘야 한다.
setText 메서드에서 true를 줌으로써, HTML 형식의 메시지임을 명시해주는 방식이다.

파일이 첨부된 메시지 전송

String htmlMessage =  """
        <html>
            <body>
                <h1 style="color:blue;">환영합니다!</h1>
                <p>Spring 이메일 서비스 테스트 중입니다.</p>
                <p>감사합니다.</p>
            </body>
        </html>
        """;

try{
    MimeMessage mimeMessage = mailSender.createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
    helper.setTo(email);
    helper.setSubject("테스트용 메일");
    helper.setText(htmlMessage, true);

    FileSystemResource file = new FileSystemResource(new File("D:\\Desktop\\baby.jpg"));
    helper.addAttachment("baby.jpg", file);

    log.info("Send To : {}, message : {}", email, mimeMessage);
    mailSender.send(mimeMessage);
}catch(MailException | MessagingException e){
    log.error(e.getMessage());
}

MimeMessageHelper 객체를 생성하면서 true 옵션을 통해 multipart 전송을 활성화해주면 파일첨부도 가능하다.
이후, addAttachment를 이용하여 첨부할 파일을 추가해주면 된다.

HTML내 이미지 추가

String htmlMessage =  """
                <html>
                    <body>
                        <h1 style="color:blue;">환영합니다!</h1>
                        <p>Spring 이메일 서비스 테스트 중입니다.</p>
                        <p>감사합니다.</p>
                        
                        <img src="cid:identifier_baby">
                    </body>
                </html>
                """;
   
// ...

FileSystemResource file = new FileSystemResource(new File("D:\\Desktop\\baby.jpg"));
helper.addInline("identifier_baby", file); // 인라인 이미지 추가

mailSender.send(mimeMessage);

HTML 메시지에 img 태그를 작성하고, 식별자를 명시하는 것으로 인라인 이미지를 추가할 수 있다.
이때, helper의 addInline에서 작성한 식별자와 HTML 메시지에 작성한 식별자가 동일해야한다.

FreeMarker를 이용한 예제

앞선 예제를 통해 HTML 메시지를 전송하는 방법을 알아봤다. 하지만, Java코드내에 HTML 메시지를 작성하는 것은 불편하다. 따라서, 단순한 텍스트가 아닌이상 앞선 방법처럼 메시지를 전송하는 것은 잘 사용되지 않는다고 한다. 출처

대안으로 FreeMarker와 같은 템플릿 라이브러리를 사용하라고 소개하는데, 이를 간단하게 살펴보자

우선 Spring에서 제공하는 FreeMarker Starter를 받아온다.

SpringBoot에서는 이를 위한 자동설정을 FreeMarkerAutoConfiguration에 제공한다.

@ConfigurationProperties(prefix = "spring.freemarker")
public class FreeMarkerProperties extends AbstractTemplateViewResolverProperties {

	public static final String DEFAULT_TEMPLATE_LOADER_PATH = "classpath:/templates/";

	public static final String DEFAULT_PREFIX = "";

	public static final String DEFAULT_SUFFIX = ".ftlh";
    
    // ...

기본경로가 templates 로 설정되어 있으므로, 해당 경로에 .ftlh 확장자 파일을 작성하자.

<#ftl encoding="utf-8"/>

<html>
    <body>
        <h1 style="color:blue;">환영합니다!</h1>
        <p>Spring 이메일 서비스 테스트 중입니다.</p>
        <p>감사합니다.</p>

        <img src="cid:${img01}">
    </body>
</html>

앞서 사용했던 HTML 텍스트를 그대로 재사용할 예정이다. 다음으로 메일전송 코드를 수정하자.

private final JavaMailSender mailSender;
private final Configuration freeMarkerConfig;

public void sendMail(String email) {
    try{
        Template template = freeMarkerConfig.getTemplate("sample.ftlh");
        Map<String, String> model = Map.of("img01", "identifier_baby");
        String message = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);

        MimeMessage mimeMessage = mailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
        helper.setTo(email);
        helper.setSubject("테스트용 메일");
        helper.setText(message, true);

        FileSystemResource file = new FileSystemResource(new File("D:\\Desktop\\baby.jpg"));
        helper.addAttachment("baby.jpg", file); // 첨부파일 추가

        helper.addInline("identifier_baby", file); // 인라인 이미지 추가
        log.info("Send To : {}, message : {}", email, mimeMessage);
        mailSender.send(mimeMessage);
    }catch(Exception e){
        log.error(e.getMessage());
    }
}

간단하게 설명하자면 DI를 이용해 Configuration 객체를 주입받고, 미리 작성해둔 sample.ftlh파일을 받아온다. 이후, ftlh파일 내에 값을 주입하기 위해 Map을 이용해 세팅하고 FreeMarkerTemplateUtils를 이용해 문자열로 변환한다.

나머지 코드는 앞과 동일하다.

동일한 결과물이 메일로 도착한 것을 확인할 수 있다.

좀 더 자세한 내용은 Spring Framework 문서FreeMarker 공식문서를 참고하자

참고

0개의 댓글