1. build.gradle
// java mail
implementation 'org.springframework.boot:spring-boot-starter-mail'
implementation 'javax.mail:javax.mail-api:1.6.2'
implementation 'com.sun.mail:javax.mail:1.6.2'
  1. application.yml (메일 보내기 설정 on/off 설정 - optional)
mail:
  send: true
  1. MailProperties.java (메일 보내기 설정 on/off 설정 - optional)
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConfigurationProperties(prefix = "mail")
public class MailProperties {
    private boolean send;

    public boolean isSend() {
        return send;
    }

    public void setSend(boolean send) {
        this.send = send;
    }
}
  1. 메일 전송 service 클래스
private final MailProperties mailProperties;

if(mailProperties.isSend()) {
	mailService.sendReviewApplicationMail(evalUser.getEmail(), paper.getPaperNm(), paper.getAuthorList().stream().map(Author::getUserNm).collect(Collectors.joining(", ")));
}
  1. MailDto class
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.core.io.ClassPathResource;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.mail.javamail.MimeMessagePreparator;
import org.springframework.stereotype.Component;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils;

import javax.mail.internet.MimeMessage;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Map;

@Data
@NoArgsConstructor
@Component
public class MailDto implements MimeMessagePreparator {
    private String fromEmail;
    private String toEmail;
    private String subject;
    private String contents;
    private boolean html = true;
    private String tmpFilePath;

    @Builder
    public MailDto(String fromEmail, String toEmail, String subject, String contents, boolean html, String tmpFilePath) {
        this.fromEmail = fromEmail;
        this.toEmail = toEmail;
        this.subject = subject;
        this.contents = contents;
        this.html = html;
        this.tmpFilePath = tmpFilePath;
    }

    /**
     * 템플릿 파일 경로 변수인 templateFilePath 에 있는 파일을 읽어서 메일 본문을 셋팅한다
     * 메일 본문에서 변환해야 할 작업이 없을 경우엔 convertMap 변수에 null을 설정하면 파일 내용을 그대로 메일 본문에 셋팅하게 되며
     * 변환할 작업이 있을 경우 찾아야 할 문자열을 Key로, 바꿔야 할 문자열을 Value로 셋팅한 Map 객체를 파라미터로 전달하면 문자열을 치환해서 메일 본문을 셋팅한다
     * @param tmpFilePath
     * @param convertMap
     * @throws IOException
     */
    public void setTmpFilePath(String tmpFilePath, Map<String, String> convertMap) throws IOException {
        this.tmpFilePath = tmpFilePath;
        String readFile = readFile(tmpFilePath);
        if(convertMap == null) {
            this.contents = readFile;
        }else{
            setContents(readFile, convertMap);
        }
    }

    public void setContents(String contents, Map<String, String> convertMap) {
        String result = contents;
        for(Map.Entry<String, String> entry : convertMap.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            result = StringUtils.replace(result, key, value);
        }
        this.contents = result;
    }
    @Override
    public void prepare(MimeMessage mimeMessage) throws Exception {
        MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
        mimeMessageHelper.setFrom(fromEmail);
        mimeMessageHelper.setTo(toEmail);
        mimeMessageHelper.setSubject(subject);
        mimeMessageHelper.setText(contents, html);
    }
    private String readFile(String path) throws IOException {
        return readFile(path, "UTF-8");
    }

    /**
     * war로 빌드 후 서버 배포시 문제 발생 context.getRealPath을 사용하여 파일에 접근 시 파일 시스템 자원을 사용할 것으로 기대 되지만 jar 또는 war파일 내부에서 그렇게 사용할수가 없음.
     * 그래서 IDE 로컬 환경에서는 동작하지만 war, jar에서는 동작하지 않는 이유
     * 그래서 context.getRealPath대신 getInputStream을 사용하면 리소스의 위치에 상관없이 리소스의 콘텐츠를 읽을 수 있음
     * @param path
     * @param encoding
     * @throws IOException
     */
    private String readFile(String path, String encoding) throws IOException {
        Charset charset = Charset.forName(encoding);
        ClassPathResource resource = new ClassPathResource(path);
        byte[] encoded = FileCopyUtils.copyToByteArray(resource.getInputStream());
        return new String(encoded, charset);
    }

}
  1. MailService.class
import ###.api.common.dto.MailDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;

@Slf4j
@Service
public class MailService {

    private final JavaMailSender javaMailSender;

    public int authNumber;

    private final String USERNAME = "[email protected]";
    private final String PASSWORD = "실제 비밀번호가 아닌, smtp 연결용 비밀번호";
    private final Properties EMAIL_PROPERTIES;

    public MailService(JavaMailSender javaMailSender) {
        this.javaMailSender = javaMailSender;

        EMAIL_PROPERTIES = new Properties();
        EMAIL_PROPERTIES.put("mail.smtp.host", "smtp.gmail.com");
        EMAIL_PROPERTIES.put("mail.smtp.port", "587");
        EMAIL_PROPERTIES.put("mail.smtp.auth", "true");
        EMAIL_PROPERTIES.put("mail.smtp.starttls.enable", "true");
        EMAIL_PROPERTIES.put("mail.smtp.ssl.protocols", "TLSv1.2");
    }

    /**
     * 이메일 템플릿 적용
     */
    @Async
    public String sendAuthCodeMail(String email, HttpSession reqSession) {
        MailDto mailDto;
        String emailTemplatePath = "templates/valid_code.html";
        makeRandomNumber();
        String authNumberStr = String.valueOf(authNumber);
        Map<String, String> convertMap = new HashMap<>();
        for (int i = 0; i < authNumberStr.length(); i++) {
            convertMap.put("${authNumber" + (i + 1) + "}", String.valueOf(authNumberStr.charAt(i)));
        }
        String logoBase64 = encodeImageToBase64("src/main/resources/static/image/logo.svg");
        convertMap.put("logoImage", logoBase64);
        try {
            mailDto = MailDto.builder()
                    .fromEmail(USERNAME)
                    .toEmail(email)
                    .subject("Authentication code")
                    .html(true)
                    .build();
            mailDto.setTmpFilePath(emailTemplatePath, convertMap);
            reqSession.setAttribute("code", authNumber);
            javaMailSender.send(mailDto);
            return "sent";
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            return "error";
        }
    }

    /**
     * 심사 요청 메일
     * @param email
     * @return
     */
    @Async
    public String sendReviewApplicationMail(String email, String paperNm, String author) {
        MailDto mailDto;
        String emailTemplatePath = "templates/review_request.html";
        Map<String, String> convertMap = new HashMap<>();
        String logoBase64 = encodeImageToBase64("src/main/resources/static/image/logo.svg");
        convertMap.put("${paperNm}", paperNm);
        convertMap.put("${author}", author);
        convertMap.put("${logoImage}", logoBase64);
        try {
            mailDto = MailDto.builder()
                    .fromEmail(USERNAME)
                    .toEmail(email)
                    .subject("Request for Review")
                    .html(true)
                    .build();
            mailDto.setTmpFilePath(emailTemplatePath, convertMap);
            javaMailSender.send(mailDto);
            return "sent";
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            return "error";
        }
    }

    /**
     * 심사 결과 완료 메일
     * @param email
     * @return
     */
    @Async
    public String sendReviewCompletedMail(String email, String paperNm) {
        MailDto mailDto;
        String emailTemplatePath = "templates/review_completed.html";
        Map<String, String> convertMap = new HashMap<>();
        String logoBase64 = encodeImageToBase64("src/main/resources/static/image/logo.svg");
        convertMap.put("${paperNm}", paperNm);
        convertMap.put("${logoImage}", logoBase64);
        try {
            mailDto = MailDto.builder()
                    .fromEmail(USERNAME)
                    .toEmail(email)
                    .subject("Article Review Completed")
                    .html(true)
                    .build();
            mailDto.setTmpFilePath(emailTemplatePath, convertMap);
            javaMailSender.send(mailDto);
            return "sent";
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            return "error";
        }
    }

    private void makeRandomNumber() {
        // 난수의 범위 111111 ~ 999999 (6자리 난수)
        Random r = new Random();
        int checkNum = r.nextInt(888888) + 111111;
        log.info("인증번호 : "+checkNum);
        authNumber = checkNum;
    }

}