본문 바로가기
Study/SpringBoot

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

by 오늘만 사는 여자 2025. 3. 25.
728x90
반응형

 프로젝트 환경구성

 

이전 프로젝트에서 사용한 라이브러리, 설정 외에 더 추가할 것이 생겼다.

 

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'
 
 
 
implementation group: 'commons-io', name: 'commons-io', version: '2.11.0'
 
 
 
compileOnly 'org.projectlombok:lombok'
 
annotationProcessor 'org.projectlombok:lombok'

commons-io는 첨부 파일 구현에서 편리하게 쓸 메소드를 가져오는 데 쓰인다.
(이 포스팅 에서 파일 업로드, 다운로드 관련으로 유용하게 쓰였음)

 

application.properties

 
## 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

 

잠깐!
기존에는 메일 송신을 위해 보안 수준이 낮은 앱 및 Google계정 을 허용하여 날먹(?)으로 메일 송신이 가능했지만, 이제 더는 해당 방식을 지원하지 않는다.

따라서 2단계 인증을 설정한 후, 앱 비밀번호를 생성하여 해당 비밀번호를 기입해야만이 메일 송신이 가능하다.

그 방법은 이 포스팅 아래 부분에서 자세히 다루고 있으니 참고하자 ㅎㅎ

 

 

 프로젝트 화면 구성 - view

 

필자는 타임리프 뷰 템플릿으로 간단하게 화면을 구성했다.

js부분을 추가해서 여러 명의 수신인과 참조인을 가능하도록 했으니 편하게 가져다 쓰면 된다!

 

textMail.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>
 
<div class="container">
 
<div style="float: left; width: 50%;">
 
<h1>텍스트 메일 보내기</h1>
 
 
 
<form th:action="@{/mail/send}" method="post" enctype="multipart/form-data">
 
<table>
 
<tr class="form-group">
 
<td>보내는 사람</td>
 
<td>
 
<input type="text" class="form-control" name="from" placeholder="이메일 주소를 입력하세요">
 
</td>
 
</tr>
 
<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>
 
<textarea class="form-control" name="content" placeholder="보낼 내용을 입력하세요"> </textarea>
 
</td>
 
</tr>
 
<tr class="form-group">
 
<td>첨부 파일 </td>
 
<td>
 
<input type="file" name="file" class="file-input" />
 
</td>
 
</tr>
 
</table>
 
<button class="btn btn-default">발송</button>
 
</form>
 
 
 
</div>
 
</div>
 
</body>
 
<script>
 
const add_textbox = (obj) => {
 
const box = obj.parentElement.parentElement;
 
const newP = document.createElement("tr");
 
 
 
newP.innerHTML = "<tr class='form-group'><td>메일 주소</td><td><input type='text' class='form-control' name='address' ></td><td><input type='button' class='form-control' value='삭제' onclick='opt_remove(this)'></td></tr>";
 
box.parentNode.insertBefore(newP, box.nextSibling);
 
}
 
 
 
const add_textbox2 = (obj) => {
 
const box = obj.parentElement.parentElement;
 
const newP = document.createElement("tr");
 
 
 
newP.innerHTML = "<tr class='form-group'><td>참조 메일 주소</td><td><input type='text' class='form-control' name='ccAddress' ></td><td><input type='button' class='form-control' value='삭제' onclick='opt_remove2(this)'></td></tr>";
 
box.parentNode.insertBefore(newP, box.nextSibling);
 
}
 
 
 
const opt_remove = (obj) => {
 
obj.parentElement.parentElement.parentElement.removeChild(obj.parentElement.parentElement);
 
}
 
const opt_remove2 = (obj) => {
 
obj.parentElement.parentElement.parentElement.removeChild(obj.parentElement.parentElement);
 
}
 
</script>
 
</html>

form 타입은 꼭 multipart로 지정해야 첨부파일이 잘 넘어간다. 아니면 null처리되니 주의!

 

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>

 

 DTO 구현

 

 

view에서 받아올 데이터를 맵핑할 DTO를 구현하자.

 

 
package com.example.demo.dto;
 
 
 
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;
 
}

 

 Controller 구현

 

 
package com.example.demo.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 org.springframework.web.bind.annotation.RequestParam;
 
import org.springframework.web.multipart.MultipartFile;
 
 
 
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("/textMail")
 
public String mailSend() {
 
return "textMail";
 
}
 
 
 
@PostMapping("/mail/send")
 
public String sendMail(MailDto mailDto, MultipartFile file) throws MessagingException, IOException {
 
mailService.sendMultipleMessage(mailDto, file);
 
System.out.println("메일 전송 완료");
 
return "result";
 
}
 
 
 
}

첨부파일의 경우 MultipartFile 형식으로 받아온다.

 

 

 Service 구현

 

 

 
package com.example.demo.service;
 
 
 
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.springframework.web.multipart.MultipartFile;
 
 
 
import com.example.demo.dto.MailDto;
 
 
 
import lombok.RequiredArgsConstructor;
 
import lombok.extern.slf4j.Slf4j;
 
 
 
@Slf4j
 
@Service
 
@RequiredArgsConstructor
 
public class MailService {
 
 
 
private final JavaMailSender emailSender;
 
 
 
public void sendMultipleMessage(MailDto mailDto, MultipartFile file) throws MessagingException, IOException {
 
MimeMessage message = emailSender.createMimeMessage();
 
MimeMessageHelper helper = new MimeMessageHelper(message, true);
 
//메일 제목 설정
 
helper.setSubject(mailDto.getTitle());
 
 
 
//참조자 설정
 
helper.setCc(mailDto.getCcAddress());
 
 
 
helper.setText(mailDto.getContent(), false);
 
 
 
helper.setFrom(mailDto.getFrom());
 
 
 
//첨부 파일 설정
 
String fileName = StringUtils.cleanPath(file.getOriginalFilename());
 
 
 
helper.addAttachment(MimeUtility.encodeText(fileName, "UTF-8", "B"), new ByteArrayResource(IOUtils.toByteArray(file.getInputStream())));
 
 
 
//수신자 개별 전송
 
// for(String s : mailDto.getAddress()) {
 
// helper.setTo(s);
 
// emailSender.send(message);
 
// }
 
//수신자 한번에 전송
 
helper.setTo(mailDto.getAddress());
 
emailSender.send(message);
 
log.info("mail multiple send complete.");
 
 
 
 
 
}
 
 
 
}

기존에 text기반 메일을 보냈을 때와 달리 첨부파일을 담기 위해 MimeMessage 객체를 사용하고 있다. 

 

  • MimeMessageHelperclass
    MIME 메시지를 만들기 위한 클래스
    HTML 레이아웃에서 이미지, 일반적인 메일 첨부 파일 및 텍스트 내용을 지원

 

MIME이란?
Multipurpose Internet Mail Extensions의 약자이다.
기존 UUEncoding 방식은 ASCII(텍스트) 파일만 지원하여 음악, 워드 파일 등의 바이너리 파일을 전송할 수 없음
이러한 방식을 보안하여 나온 인코딩 방식이다.

 

참조, 수신인을 설정할때도 간단하다. setTo와 setCc 메소드를 활용하면 된다.

이 때, 수신인이나 참조인지 다중(여러 명)일 경우와 단일일 경우의 메소드가 둘 다 구현되어 있다.

 

따라서

A방식 메일 : 수신인[a, b, c, d....] 제목: "제목" 내용 : "내용"

B방식 메일 : 수신인[a]  제목: "제목" 내용: "내용"  ,    수신인[b]  제목: "제목" 내용: "내용"....

 

한 번에 여러 수신인에게 메일을 보내는 A 방식의 경우 setTo에 수신인 리스트를 넣어두고,

 

각 수신인에게 하나의 메일을 여러개 보내는 B 방식의 경우 setTo에 개별 수신인을 넣어 전송하면 된다.

 

(코드에 주석처리되어있으니 편한대로 사용하시라!)

 

 

 실행 결과

 

이제 프로젝트를 구동하여 메일이 정상적으로 보내지는지 확인해보자.

 

 

 

완료다! 직접 메일을 확인해보자.

 

 

 

 

 

 

REF: https://born2bedeveloper.tistory.com/68#google_vignette

 

728x90
반응형

댓글