[Spring Boot] 이메일 보내기 (3) - html 템플릿 적용 (feat. Thymeleaf)

2022. 8. 11. 14:18Spring Boot

반응형

 

++) 해당 포스팅은 이전 작성한 포스팅들과 연계되어 있으므로 먼저 보고 오는 것을 추천합니다!

 

https://born2bedeveloper.tistory.com/68?category=1038709 

 

[Spring Boot] 이메일 보내기 (2) - 참조(cc), 첨부 파일

꽤 오래전에 구글 SMTP 서버를 이용한 Email 전송에 관한 내용을 다룬 적이 있었는데, 생각보다 조회수가 매우 높았다. 아마 이메일 전송이 부가적인 기능으로 다양하게 사용되기 떄문이겠지? 새로

born2bedeveloper.tistory.com

https://born2bedeveloper.tistory.com/14?category=1038709 

 

[Spring Boot] EMail 보내기

기존 프로젝트를 베타 오픈하기 전에, 피드백을 받는 기능을 추가하면 좋겠다는 생각이 들었다. 일종의 고객센터 같은 느낌? 간단한 입력 form에 작성한 후 보내면, 관리자 계정으로 메일이 오도

born2bedeveloper.tistory.com

 

이번엔 html 템플릿을 적용한 이메일 전송에 대해 알아보도록 하자.

기존에 메일함을 보다 보면, 아래와 같이 텍스트 작성 외에 html문서형으로 작성된 메일을 받아본 적이 있을 것이다.

템플릿 기반 메일 전송에는 여러 방식이 있지만, 필자는 타임리프 템플릿 엔진을 이용하여 HTML 기반 이메일을 전송해볼것이다.

 

이메일 전송에 대한 기본적인 개념은 이전에 다뤘으니 생략하고 바로 구현으로 넘어가겠다.

 

 환경 설정

 

구현에 필요한 라이브러리들이다.

 

build.gradle

	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'

 

이전에 다룬 방식에서 크게 추가한 것은 없다. 

 

application.properties

spring.thymeleaf.check-template-location=true
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.cache=false
spring.thymeleaf.order=0

## mail default settings
spring.mail.default-encoding=UTF-8
## mail custom settings
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=[이메일 계정]
spring.mail.password=[앱 비밀번호]
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.debug=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.mime.charset=UTF-8
spring.mail.properties.mail.transport.protocol=smtp

필자는 구글 SMTP를 연동하였다. 이와 관련된 내용은 이곳에 자세히 나와있다.

 

또한 더 이상 보안 수준이 낮은 앱 허용을 지원하지 않으므로 앱 비밀번호를 생성해야 한다.

(그에 관한 내용도 윗 링크에 나와있다.)

 

 

 화면 구현 

 

 

POSTMAN을 통해 기능을 검증할 수 있지만, 이왕 타임리프를 쓰는 김에 view 부분도 간단하게 구현하였다.

 

templateMail.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="ko">
<head>
    <meta charset="UTF-8">
    <title>Send Mail</title>
    <script type="text/javascript" src="/scripts/jquery-ui/jquery.min.js"></script>
	<script type="text/javascript" src="/scripts/common/common-ui.js"></script>
	<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
	<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
	<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
<h1>템플릿 메일 보내기</h1>
 <div class="container">
    <form th:action="@{/mail/template/send}" method="post">
        <table>
            <tr id="box" class="form-group">
                <td>메일 주소</td>
                <td>
                    <input type="text" class="form-control" name="address" placeholder="이메일 주소를 입력하세요">
                </td>
                <td>
					<input type="button" class="form-control" value="추가" onclick="add_textbox(this)">
				</td>
            </tr>
            <tr id="box2" class="form-group">
                <td>참조 메일 주소</td>
                <td>
                    <input type="text" class="form-control" name="ccAddress" placeholder="참조 수신인을 입력하세요">
                </td>
                <td>
					<input type="button" class="form-control" value="추가" onclick="add_textbox2(this)">
				</td>
            </tr>
            <tr class="form-group">
                <td>제목</td>
                <td>
                    <input type="text" class="form-control" name="title" placeholder="제목을 입력하세요">
                </td>
            </tr>
            <tr class="form-group">
                <td>템플릿 이름</td>
                <td>
                    <input type="text" class="form-control" name="template" placeholder="템플릿 이름을 입력하세요">
                </td>
            </tr>
        </table>
        <button class="btn btn-default">발송</button>
    </form>
 </div>
</body>
<script>
	const add_textbox = () => {
	    const box = document.getElementById("box");
	    const newP = document.createElement("tr");
	
	    newP.innerHTML = "<th>메일 주소</th><td><input type='text' name='address' ><input type='button' value='삭제' onclick='opt_remove(this)'></td>";
		box.parentNode.insertBefore(newP, box.nextSibling);
	}
	
	const add_textbox2 = () => {
	    const box = document.getElementById("box2");
	    const newP = document.createElement("tr");

	    newP.innerHTML = "<th>참조 메일 주소</th><td><input type='text' name='ccAddress' ><input type='button' value='삭제' onclick='opt_remove2(this)'></td>";
		box.parentNode.insertBefore(newP, box.nextSibling);
		}
	
	const opt_remove = (obj) => {
	     document.getElementById('box').parentElement.removeChild(obj.parentElement.parentElement);
	}
	const opt_remove2 = (obj) => {
	     document.getElementById('box2').parentElement.removeChild(obj.parentElement.parentElement);
	}
</script>
</html>

 

result.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="ko">
<head>
  <title>Bootstrap Example</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>

<div class="container">
  <h2>Email 전송 완료.</h2>    
</div>

</body>
</html>

 

 

 

(정말 최소한의 디자인만 넣었다..)

 

 

 

 무료 템플릿 선정하기

 

물론, 직접 작성한 html 파일을 사용해서 메일을 보내도 되지만, 무료로 템플릿 테마를 가져올 수 있으니 괜찮은 놈으로 하나 골라서 사용해보자.

 

사이트는 많지만 필자는 아래 사이트를 참고하여 가져왔다.

 

https://unlayer.com/templates

 

Free HTML Email Templates and Editor

Browse hundreds of HTML email templates and choose the best for your business. Wide range of templates available for every industry and usage. Start for free.

unlayer.com

간단한 로그인으로 무료 템플릿을 본인 입맛에 맞게 수정하여 가져올 수 있다.

나는 이것도 귀찮다.. 싶으신 분들은 필자가 대충 가져온 템플릿을 아래에 둘 테니 파일로 저장해서 사용하시라!

(같이 첨부된 이미지는 src/main/resources/static/images 경로로 폴더를 만들어 넣어두면 된다)

템플릿예시.zip
0.10MB

 

받아온 템플릿의 경우, 위의 환경설정에서 classpath를 templates로 지정했으므로 src/main/resources/templates 경로 아래에 index.html을 넣어두면 된다.

 

잠깐! 만약 템플릿 지정 경로를 내가 원하는대로 지정하고 싶다면?

 

해당 내용은 아래의 포스팅에서 다뤘으니 참고하여 적용하면 된다.

https://born2bedeveloper.tistory.com/69?category=1038709 

 

[Spring Boot] Thymeleaf 경로 변경 & 다중 경로 설정

이 포스팅은 타임리프 템플릿 엔진 사용 시, html등의 파일을 프로젝트 '내부'가 아닌 '외부' (ex. C드라이브)에 두고 관리할 수 있는 방법을 다룬다.  Thymeleaf 경로 변경 별도의 설정이 없다면, 기본

born2bedeveloper.tistory.com

 

 

 

 Service 구현

 

이제 템플릿 적용을 위한 준비가 끝났으니 본격적으로 로직을 구현해보자!

 

먼저 view의 form에서 받아온 값들을 저장할 DTO부터 생성하자.

 

MailDto

import java.util.ArrayList;
import java.util.List;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
 
@Getter
@Setter
@NoArgsConstructor
public class MailDto {
	private String from;
    private String[] address;
    private String[] ccAddress;
    private String title;
    private String content;
    private String template;
}

마지막 인자로 받아오는 template의 경우 확장성을 고려하여 받아왔는데,

여러개의 template이 있다고 가정하고, 원하는 템플릿 이름(ex.index)을 받아오면 해당 디자인으로 메일을 전송할 수 있도록 고려하였다.

 

 

MailService.java

import java.io.IOException;
import java.util.HashMap;
import java.util.List;

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

import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thymeleaf.context.Context;
import org.thymeleaf.spring5.SpringTemplateEngine;

import com.example.demo.dto.MailDto;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
@RequiredArgsConstructor 
public class MailService {
	private final JavaMailSender emailSender;
    private final SpringTemplateEngine templateEngine;
    
        public void sendTemplateMessage(MailDto mailDto) throws MessagingException {	
    	MimeMessage message = emailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
        //메일 제목 설정
        helper.setSubject(mailDto.getTitle());

        //수신자 설정
        helper.setTo(mailDto.getAddress());
        
        //참조자 설정
        helper.setCc(mailDto.getCcAddress());

        //템플릿에 전달할 데이터 설정
        HashMap<String, String> emailValues = new HashMap<>();
    	emailValues.put("name", "jimin");
    	
        Context context = new Context();
        emailValues.forEach((key, value)->{
            context.setVariable(key, value);
        });
    	              
        //메일 내용 설정 : 템플릿 프로세스
        String html = templateEngine.process(mailDto.getTemplate(), context);
        helper.setText(html, true);
        
        //템플릿에 들어가는 이미지 cid로 삽입
        helper.addInline("image1", new ClassPathResource("static/images/image-1.jpeg"));

        //메일 보내기
        emailSender.send(message);
    }

여기서 주의깊게 봐야 할 것은 MimeMessageHelper 클래스의 addInline 메소드다.

만약, 해당 메소드 없이 html 템플릿 메일을 보내게 되면 아래와 같이 이미지가 적용되지 않는다.

(당연한 일임.. 이미지 경로가 프로젝트 내부에 있으니 메일로 보내버리면 받는 서버에서는 이미지 경로를 찾을수가 없다)

 

html 템플릿 안의 이미지를 전송하는 방법은 여러 가지가 있는데, 필자는 cid를 이용하여 이미지를 html안에 임베딩하여 보내는 방식을 사용할 것이다.

 

따라서 전송할 html 양식 안의 img 경로를 다음과 같이 설정해둔 후

 

<img src='cid:image'>

 

이메일 전송 전 addInline 메소드로 해당 cid를 적용해야한다!

 

helper.addInline("image", new ClassPathResource("static/images/image-1.jpeg"));

cid의 이름과 addInline의 첫 번째 인자를 서로 매칭해주고, 두 번째 인자에는 실제 저장되어 있는 이미지 경로를 넣어주면 된다.

(필자가 올려둔 index.html에서는 첫 번째 이미지만 임베딩 해둔 상태이니, 엑박이 불편한 분들은 직접 addInline을 통해 나머지 이미지를 임베딩 해보셔도 좋습니다 :D)

 

 

 Controller 구현

 

 

import java.io.IOException;
import java.util.List;

import javax.mail.MessagingException;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import com.example.demo.domain.Email;
import com.example.demo.dto.MailDto;
import com.example.demo.service.MailService;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
public class MailController {
	
	private final MailService mailService;
	
	 
    public MailController(MailService mailService) {
        this.mailService = mailService;
    }
	
	@GetMapping("/templateMail")
    public String templateMailSend() {
		return "templateMail";
	}
	
    @PostMapping("/mail/template/send")
    public String sendTemplateMail(MailDto mailDto, Model model) throws MessagingException {
    	mailService.sendTemplateMessage(mailDto);
        System.out.println("메일 전송 완료");
        return "result";
    }
    

}

 

 

이제 모든 준비가 끝났다! 설레는 테스트의 시간이다.

 

 

 이메일 전송 구현

 

 

 

떨리는 마음으로 발송을 눌러보면..

 

완료 시 리다이렉트한 페이지로 이동한다! 직접 보낸 계정으로 들어가 메일을 확인해보자.

 

 

직접 적용한 템플릿에 무사히 이미지까지 잘 임베딩 된 것을 확인할 수 있다!

 

 

해당 포스팅을 마지막으로 이메일을 이만 종결하도록 하겠다.

이메일 정복! 끝!