이번 글에서는 MailSender 인터페이스를 상속받은 JavaMailSender를 사용하여 이메일 전송 시스템을 구현해보도록 하겠습니다.

전체 코드는 깃헙을 참고하시길 바랍니다.


개발환경

  • IntelliJ 2019.02
  • Java 11
  • SpringBoot 2.2.5
  • Gradle 6.0.1






1. 준비 작업


build.gradle

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-mail'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}

이메일 발송을 위해 spring-boot-starter-mail 의존성을 추가합니다.



프로젝트 구조





2. Gmail SMTP Server 설정

이 글에서는 구글 계정만 있으면 무료로 발송할 수 있는 Gmail SMTP Server를 이용할 것입니다.

Gmail SMTP Server를 사용하려면 요구사항에 맞는 설정이 필요하며, 그 내용은 다음과 같습니다. ( 참고 )




이제 이를 바탕으로 설정 내용을 작성해보겠습니다.

application.yml

spring:
mail:
host: smtp.gmail.com
port: 587
username: {YOUR_GMAIL_ADDRESS}
password: {YOUR_GMAIL_PASSWORD}
properties:
mail:
smtp:
auth: true
starttls:
enable: true

spring.mail.* 네임스페이스를 통해 메일 서버 정보를 설정해줍니다.

( 다른 SMTP 서버를 사용하려면, 해당 서버(서비스)의 맞는 설정 정보를 확인할 필요가 있으며 그에 따라 application.yml 내용이 다를 수 있습니다. )



중요!

application.yml 파일은 google 계정 정보가 들어있으므로 github 등 공개된 곳에 올릴 것이라면, 반드시 applicatio.yml 파일은 .gitingore를 해줘야합니다!

.ignore

### my ignore ###
src/main/resources/application.yml




3. 공통 영역 구현하기

먼저 메일 발송과 직접적인 관련이 없는 영역들을 구현하겠습니다.

간단하므로 설명은 생략하겠습니다.


1) 퍼블리싱

resources/templates/mail.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>메일 발송</title>
</head>
<body>
<h1>메일 발송</h1>

<form th:action="@{/mail}" method="post">
<input name="address" placeholder="이메일 주소"> <br>
<input name="title" placeholder="제목"> <br>
<textarea name="message" placeholder="메일 내용을 입력해주세요." cols="60" rows="20"></textarea>
<button>발송</button>
</form>
</body>
</html>


2) Controller

com.victolee.mailExam.controller.MailController.java

import com.victolee.mailExam.dto.MailDto;
import com.victolee.mailExam.service.MailService;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
@AllArgsConstructor
public class MailController {
private final MailService mailService;

@GetMapping("/mail")
public String dispMail() {
return "mail";
}

@PostMapping("/mail")
public void execMail(MailDto mailDto) {
mailService.mailSend(mailDto);
}
}


3) Dto

com.victolee.mailExam.dto.MailDto.java

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
public class MailDto {
private String address;
private String title;
private String message;
}





4. 메일 발송 - SimpleMailMessage

먼저 SimpleMailMessage를 사용하여 간단한 텍스트 메일을 발송해보도록 하겠습니다.


com.victolee.mailExam.service.MailService.java

import com.victolee.mailExam.dto.MailDto;
import lombok.AllArgsConstructor;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;

@Service
@AllArgsConstructor
public class MailService {
private JavaMailSender mailSender;
private static final String FROM_ADDRESS = "YOUR_EMAIL_ADDRESS";

public void mailSend(MailDto mailDto) {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(mailDto.getAddress());
message.setFrom(MailService.FROM_ADDRESS);
message.setSubject(mailDto.getTitle());
message.setText(mailDto.getMessage());

mailSender.send(message);
}
}

  • JavaMailSender
    • MailSender 인터페이스를 상속받았으며, MIME를 지원합니다. (여기서는 단순 텍스트를 발송하지만요.)
  • FROM_ADDRESS
    • 보내는 사람의 이메일 주소를 상수로 정의합니다.
    • 해당 값은 application.yml에서 @Value로 가져와도 되고, 비즈니스에 따라 값을 유동적으로 바꿔도 됩니다.
  • SimpleMailMessage
    • 이메일 메시지 정보를 작성합니다.
      • setTo()
        • 받는 사람 주소
      • setFrom()
        • 보내는 사람 주소
        • 해당 메서드를 호출하지 않으면, application.yml에 작성한 username으로 셋팅됩니다.
      • setSubject()
        • 제목
      • setText()
        • 메시지 내용
      • 더 많은 기능은 문서를 참고해주세요.
  • mailSender.send
    • 실제 메일 발송 부분



테스트

http://localhost:8080/mail 페이지에 접근하여 메일 내용을 작성하고 실제 메일을 확인해봅니다. 



*** error ***

org.springframework.mail.MailAuthenticationException: Authentication failed; nested exception is javax.mail.AuthenticationFailedException: 535-5.7.8 Username and Password not accepted. Learn more at

535 5.7.8  https://support.google.com/mail/?p=BadCredentials r12sm16296716pgu.93 - gsmtp


applicatioy.yml에 아이디 비밀번호를 제대로 입력했는데도 해당 에러가 발생하면서 메일이 발송되지 않으면, 여기에서 "보안 수준이 낮은 앱 허용: 사용"으로 바꿔주시면 됩니다.







5. 메일 발송 - MimeMessageHelper

이번에는 스프링에서 제공하는 헬퍼를 이용하여 MIME( 이미지, 첨부파일 등 )를 포함한 메일을 발송해보겠습니다.


com.victolee.mailExam.service.MailService.java

import com.victolee.mailExam.dto.MailDto;
import com.victolee.mailExam.util.MailHandler;
import lombok.AllArgsConstructor;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;

@Service
@AllArgsConstructor
public class MailService {
private JavaMailSender mailSender;
private static final String FROM_ADDRESS = "YOUR_EMAIL_ADDRESS";

public void mailSend(MailDto mailDto) {
try {
MailHandler mailHandler = new MailHandler(mailSender);

// 받는 사람
mailHandler.setTo(mailDto.getAddress());
// 보내는 사람
mailHandler.setFrom(MailService.FROM_ADDRESS);
// 제목
mailHandler.setSubject(mailDto.getTitle());
// HTML Layout
String htmlContent = "<p>" + mailDto.getMessage() +"<p> <img src='cid:sample-img'>";
mailHandler.setText(htmlContent, true);
// 첨부 파일
mailHandler.setAttach("newTest.txt", "static/originTest.txt");
// 이미지 삽입
mailHandler.setInline("sample-img", "static/sample1.jpg");

mailHandler.send();
}
catch(Exception e){
e.printStackTrace();
}
}
}

    • SimpleMailMessage 버전에서 수정된 것은 이메일 메시지 작성 부분과 발송을 모두 MailHandler에서 처리하고 있다는 점입니다.
    • 또한 첨부파일과 이미지 파일은 static 경로 아래에 존재하고 있어야 합니다.


com.victolee.mailExam.util.MailHandler.java

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;

import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;

import java.io.File;
import java.io.IOException;

public class MailHandler {
private JavaMailSender sender;
private MimeMessage message;
private MimeMessageHelper messageHelper;

// 생성자
public MailHandler(JavaMailSender jSender) throws
MessagingException {
this.sender = jSender;
message = jSender.createMimeMessage();
messageHelper = new MimeMessageHelper(message, true, "UTF-8");
}

// 보내는 사람 이메일
public void setFrom(String fromAddress) throws MessagingException {
messageHelper.setFrom(fromAddress);
}

// 받는 사람 이메일
public void setTo(String email) throws MessagingException {
messageHelper.setTo(email);
}

// 제목
public void setSubject(String subject) throws MessagingException {
messageHelper.setSubject(subject);
}

// 메일 내용
public void setText(String text, boolean useHtml) throws MessagingException {
messageHelper.setText(text, useHtml);
}

// 첨부 파일
public void setAttach(String displayFileName, String pathToAttachment) throws MessagingException, IOException {
File file = new ClassPathResource(pathToAttachment).getFile();
FileSystemResource fsr = new FileSystemResource(file);

messageHelper.addAttachment(displayFileName, fsr);
}

// 이미지 삽입
public void setInline(String contentId, String pathToInline) throws MessagingException, IOException {
File file = new ClassPathResource(pathToInline).getFile();
FileSystemResource fsr = new FileSystemResource(file);

messageHelper.addInline(contentId, fsr);
}

// 발송
public void send() {
try {
sender.send(message);
}catch(Exception e) {
e.printStackTrace();
}
}
}

  • MimeMessageHelper
    • 스프링에서 제공하는 헬퍼 객체이며, HTML 레이아웃, 이미지 삽입, 첨부파일 등 MIME 메시지를 생성할 수 있습니다.
    • 생성자
      • MimeMessageHelper(MimeMessage mimeMessage, boolean multipart, @Nullable String encoding)
    • 또한 앞서 살펴본 SimpleMailMessage의 메서드를 모두 지원하고 있으며, 추가된 메서드는 다음과 같습니다.
      • messageHelper.setText - 메일 내용
        • 두 번째 파라미터에 HTML 사용여부를 전달합니다.
      • messageHelper.addAttachment - 첨부 파일
        • 첫 번째 파라미터에 메일에 노출된 파일 이름을 작성합니다.
        • 두 번째 파라미터에 파일의 경로를 작성합니다.
      • messageHelper.addInline(contentId, fsr) - 이미지 삽입
        • 첫 번째 파라미터에 삽입될 이미지의 id 애트리뷰트명을 작성합니다.
        • 두 번째 파라미터에 파일의 경로를 작성합니다.



테스트

http://localhost:8080/mail 페이지에 접근하여 메일 내용을 작성하고 실제 메일을 확인해봅니다. 



메일 내용에 이미지가 삽입되고, 첨부파일이 추가된 것을 확인할 수 있습니다.





6. 참고

예제에서 HTML 템플릿을 사용했지만, <p>, <img> 엘리먼트 2개가 전부인 간단한 DOM입니다.


그런데 엘리먼트가 많은 layout을 작성해야 한다면, 별도로 템플릿을 생성하는 MailTemplateService 서비스를 두는 것이 좋다고 생각됩니다.

즉, 컨트롤러에서 MailTemplateService 서비스를 호출하여 메시지 내용을 보여주는 DOM를 만들어내고,

해당 DOM을 MailService에 전달하면 DOM 생성과 메일발송의 역할이 각 서비스로 분할되어 관리가 깔끔해질 것 같습니다.





이상으로 JavaMailSender, MimeMessageHelper를 사용하여 Gmail 이메일을 전송하는 방법에 대해 알아보았습니다.


[참고 자료]

https://www.baeldung.com/spring-email

https://medium.com/@js230023/spring-boot-%EB%A9%94%EC%9D%BC-%EB%B3%B4%EB%82%B4%EA%B8%B0-f01751da4c02

https://gangzzang.tistory.com/entry/%EC%8A%A4%ED%94%84%EB%A7%81Spring-MainlSender-JavaMailSender-%EB%A9%94%EC%9D%BC-%EB%B0%9C%EC%86%A1


댓글 펼치기 👇
  1. 택길이지 2020.07.23 17:02

    자세하고 퀄리티 높은 글로 스프링부트에 대해 많이 배워가고 있습니다. 감사합니다.

    이 앞 강좌(?)까지는 따라하는데로 잘 실행이 됐는데 메일 발송 부분에서는 오류가 나네요.

    다른 분들의 별다른 코멘트가 없는거 봐서는 저만 발생하는 오류 인가 싶기도 하고....

    org.springframework.mail.MailSendException: Mail server connection failed; nested exception is javax.mail.MessagingException: Exception reading response;
    nested exception is:
    java.net.SocketTimeoutException: Read timed out. Failed messages: javax.mail.MessagingException: Exception reading response;
    nested exception is:
    java.net.SocketTimeoutException: Read timed out; message exception details (1) are:
    Failed message 1:
    javax.mail.MessagingException: Exception reading response;
    nested exception is:
    java.net.SocketTimeoutException: Read timed out

    오류 발생 부분은 sender.send(message); 이 부분에서 발생하고 있어요

    위와같은 오류가 계속 나서 구글 찾아서 접속 포트도 바꿔보고 SMTP 설정도 바꿔보도 했는데도 오류가 없어지질 않네요

    혹시 관련해서 어떤 부분을 찾아보면 좋을지 힌트 좀 부탁 드려도 될까요?

    • Favicon of https://victorydntmd.tistory.com victolee 우르르응 2020.08.01 14:32 신고

      구글 메일서버와 커넥션이 실패했나보네요.
      application.yml 설정파일에서
      mail.smtp.ssl.enable: true로 설정해주면 해결이 되는듯 합니다!
      ( 참고 : https://okky.kr/article/329317#note-text-1071747 )

  2. woos0368 2020.09.15 12:15

    이거 너무 느린데 해결책 있나요??

  3. ironypj 2020.09.16 00:04

    main.html이 아니라, mail.html로 해야지 되는 거 같습니다!