해당 글은 제가 인턴 업무 중 이메일 서비스를 구현할 때 만났던 문제와 구현했던 방식에 대해서 기술하려고합니다.
많은 문제들을 해결해 나가며 결국 이메일이 전송되었을 때에 그 쾌감은 잊지 못합니다..ㅠ
많은 블로그에서 이메일 서비스 구현에 대한 블로깅을 해놓았지만, 제가 원하는 기능은 찾아보기가 어려워 애를 많이 먹었습니다.
이 글에서는 정말 Class 하나하나 곱씹어 가며 정리한 글이기 때문에 만약에 코드만 필요하시다면 맨 밑 으로 가시면 제가 구현한 Code 만 올려드리겠습니다. (보시면 더 좋고...😅)
Gmail 설정
우선 Gmail 에 설정을 해놓아야 이메일 전송이 가능합니다.
여기서는 이메일 전송에 대한 부분을 집중적으로 작성하려고 하기 때문에 설정 관련 부분은 제가 참고한 블로그를 올려드리겠습니다.
이메일 전송
이메일을 전송할때에는 크게 잡아 두가지의 방법이 존재합니다.
- 메일을 작성해 SMTP 서버에 전달하는 방법
- 직접 메일서버(SMTP 서버)를 구축 or 서비스를 이용하는 방법
저의 경우에는 큰 서비스가 아니다 보니 Google Mail Server 에 전달하는 방법을 채택했습니다.
첨부파일 없이 전송
첨부파일 없이 전송하는 방법은 많은 블로그에서 설명 하고 있어서 잘 알고 계시겠지만 정리 차원에서 여기에서도 설명 드리겠습니다.
public void sendEmail(String to, String subject, String msg) throws MessagingException, UnsupportedEncodingException {
MimeMessage message = javaMailSender.createMimeMessage();
message.addRecipients(MimeMessage.RecipientType.TO, to);
message.setSubject(subject);
message.setText(msg);
message.setText(msg, "utf-8", "html");
message.setFrom(new InternetAddress(username, "prac_Admin"));
javaMailSender.send(message);
}
다른 로직들은 이미 구현됐다고 가정하고 설명드리겠습니다.
1. MimeMessage message = javaMailSender.createMimeMessage();
MimeMessage 객체를 생성해서 javaMailSender 의 createMimeMessage를 통해 message 를 생성합니다.
MimeMessage 는 Message 를 상속받고 있고 MimePart를 구현하고 있습니다.
파파고의 힘을 잠시 빌려(?) 설명드리자면,
Clients wanting to create new MIME style messages will instantiate an empty MimeMessage object and then fill it with appropriate attributes and content.
(새 MIME 유형 메시지를 작성하려는 클라이언트는 빈 MIME 메시지 오브젝트를 인스턴스화한 후 적절한 속성과 내용으로 채웁니다.)비어있는 MIME 메시지 오브젝트를 인스턴스화 해서 적절한 속성과 내용으로 채운다는 의미입니다.
여기서 비어있는 MIME 메시지에 적절한 속성과 내용을 추가해주는 메서드가 javaMailSender.createMimeMessage() 입니다.
JavaMailSender 는 MailSender 의 구현체로써 Java 로 mail 서비스를 구현할때 Mimemessage를 편리하게 작성하게 해주는 도우미 클래스입니다.
그리고 createMimeMessage 메서드는 이렇게 쓰여있습니다. (다시한번 파파고의 힘을...)
Create a new JavaMail MimeMessage for the underlying JavaMail Session of this sender. Needs to be called to create MimeMessage instances that can be prepared by the client and passed to send(MimeMessage).
(이 발신인의 기본 JavaMail 세션에 대한 새 JavaMail MimeMessage를 만듭니다. 클라이언트가 준비하고 전송할 수 있는 MimeMessage 인스턴스를 만들려면 호출해야 합니다(MimeMessage).)정리해보자면 JavaMail 세션 대한 새로운 메시지를 생성해주는 메서드입니다.
2. message.addRecipients(message.addRecipients(MimeMessage.RecipientType.TO, to);
- 위에서 만든 MIMEmessage 의 내부 메서드입니다.
- 이 부분만 따로 가져온 이유는 해당 메서드가 가장 중요하기 때문입니다. 바로 누구에게 보낼 것인지를 설정하는 부분입니다.
- 해당 메서드의 요구사항을 확인해보면 첫 번째 파라미터에 RecipientType 객체를 넣고 두 번째 파라미터에 보낼 대상의 문자열을 입력해서 수신자를 설정하는 방식입니다.
- 여기서 RecipientType 에서 TO의 열거형을 사용한 이유는 아래 그림처럼 수신인 열거형 이기 때문입니다.
The "To" (primary) recipients
(받는 사람(기본) 수신인)
- 아래에 있는 부분은 간단하게 설명드리겠습니다.
- setSubject(subject); -> 제목
- setText(msg) -> 내용 설정
- setText(msg, "utf-8", "html"); -> html 한글 설정
- setFrom(new InternetAddress(usermame, "~"); -> 보내는 사람 설정
마지막으로 javaMailSender.send(message) 를 입력하면 메일이 전송됩니다.
첨부파일이 들어간 메일 전송
사실 이부분이 제가 제일 쓰고 싶었던 부분입니다.
많은 블로그를 돌아다녀 봤지만 찾지 못해서 ChatGPT와 내부 클래스 탐방을 통해 구현에 성공했습니다. 우선 코드부터 보여드리겠습니다.
public void sendEmail(String to, String subject, String msg, List<MessageTemplateImage> messageTemplateImageList) throws MessagingException, IOException {
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper messageHelper = new MimeMessageHelper(message, true, StandardCharsets.UTF_8.name());
messageHelper.setFrom(new InternetAddress(username, "prac_Admin"));
messageHelper.setTo(to);
messageHelper.setSubject(subject);
messageHelper.setText(msg, true);
MimeMultipart multipart = new MimeMultipart();
BodyPart bodyPart = new MimeBodyPart();
bodyPart.setContent(msg, "text/html; charset=UTF-8");
multipart.addBodyPart(bodyPart);
for (MessageTemplateImage messageTemplateImage : messageTemplateImageList) {
byte[] attachmentByteList = downloadFromUrl(messageTemplateImage.getImageUrl());
DataSource dataSource = new ByteArrayDataSource(attachmentByteList, "application/octet-stream");
MimeBodyPart mimeBodyPart = new MimeBodyPart();
mimeBodyPart.setDataHandler(new DataHandler(dataSource));
mimeBodyPart.setFileName(messageTemplateImage.getOriginalFileName());
multipart.addBodyPart(mimeBodyPart);
}
message.setContent(multipart);
javaMailSender.send(message);
}
여기에서는 중요한 부분들만 설명드리겠습니다.
MimeMessageHelper, MimeMultipart, BodyPart
- MimeMessageHelpler 의 설명부터 확인해보겠습니다.
처음에는 MimeMessage 상속받고 있거나 연관이 있을거라고 생각했는데, 단독 객체였습니다. 중요한 부분만 발췌하자면
Helper class for populating a MimeMessage.
Mirrors the simple setters of org.springframework.mail.SimpleMailMessage, directly applying the values to the underlying MimeMessage. Allows for defining a character encoding for the entire message, automatically applied by all methods of this helper class.
(MimeMessage를 채우는 도우미 클래스입니다.
org.springframework.mail의 단순 세터를 미러링합니다.기본 MimeMessage에 값을 직접 적용하는 SimpleMailMessage. 이 도우미 클래스의 모든 메서드에 의해 자동으로 적용되는 전체 메시지에 대한 문자 인코딩을 정의할 수 있습니다.)
쉽게 이야기해서 MimeMessage 가 못하는 부분을 도와주는 도우미 클래스입니다.
1. MimeMessageHelper messageHelper = new MimeMessageHelper(message, true, StandardCharsets.UTF_8.name());
우선 MimeMessage 객체를 넣고 multipart 의 속성을 true 로 넣어서 현재 첨부파일이 들어갈 것이라는 것을 설정하고, 메시지의 타입을 미리 UTF_8 로 설정합니다.
2. MimeMultipart multipart = new MimeMultipart();
MimeMultipart 객체를 생성해서 사용해야합니다.
MimeMultipart 객체는 이렇게 설명하고있습니다
The MimeMultipart class is an implementation of the abstract Multipart class that uses MIME conventions for the multipart data.
(MIME Multipart 클래스는 Multipart 데이터에 MIME 규칙을 사용하는 추상 Multipart 클래스의 구현입니다.)
설명처럼 추상클래스 Multipart의 클래스의 구현체입니다. 추가적으로 MIME 규칙을 사용하는 클래스입니다.
또한 속성과 내용을 포함하는 BodyPart 객체를 사용해야합니다.
BodyPart bodyPart = new MimeBodyPart();
bodyPart.setContent(msg, "text/html; charset=UTF-8");
multipart.addBodyPart(bodyPart);
위의 코드가 제일 중요한 로직입니다.
방금 위에서 설명드린 BodyPart 객체를 생성해서 setContent 메서드를 통해 message 를 설정하고, multipart의 메서드중 addBodyPart를 이용해 객체에 추가합니다.
아래의 사진을 보시면 list 형식으로 추가된다고 설명하고있습니다.
자 이제 제일 중요한 첨부파일 부분입니다.
첨부파일 설정
아래의 MessageTemplateImage 는 제가 S3 에 저장해놓은 URL 을 저장해놓은 Entity 입니다.
제가 정말 설명드리고 싶었던 부분이 이 부분입니다. 왜냐하면 대부분의 블로그들은 URL 을 전송하는 게 아니라 파일 그 자체로 보내는 방법을 사용하고 있어서 저에겐 해당하지 않는 부분이었습니다.
다시 코드로 돌아오면
- byte[] attachmentByteList = downloadFromUrl(messageTemplateImage.getImageUrl());
우선 downloadFromUrl 이라는 메서드를 생성해야합니다.
이 downloadFromUrl 이 필요한 이유는 URL 형식의 이미지를 byte 형태로 변환해야 하기 때문입니다.
순서대로 말씀드리면
- URL 객체를 생성할때 서버에 저장되어있던 또는 imageURL 을 입력합니다.
URLConnection 객체를 생성해서 URL 리소스를 읽습니다.
그리고 위의 로직을 이용하여 byte 형식으로 변환합니다.
마지막으로 message.setContent(multipart) 를 이용해서 message 의 content를 설정하고 javaMailSender.send(message) 설정해 주시고 나면 전송됩니다.
- 성공화면
처음으로 이런 이메일 서비스를 구현해보니 많은 개발 실력이 늘어난 것을 확인했습니다.
중간중간 많은 문제들을 만났지만 대부분 삽질이어서 생략했습니다.😂
이 서비스 구현을 통해 개발을 진행할 때 내가 사용하는 객체의 의미와 객체의 사용용도를 파악하여 사용하는 것이 얼마나 중요한지 깨달았습니다
(이메일 서비스 다시 구현하라고하면 금방 구현가능!🤓)
처음에 약속했던 구현코드
public void sendEmail(String to, String subject, String msg) throws MessagingException, UnsupportedEncodingException {
MimeMessage message = javaMailSender.createMimeMessage();
message.addRecipients(MimeMessage.RecipientType.TO, to);
message.setSubject(subject);
message.setText(msg);
message.setText(msg, "utf-8", "html");
message.setFrom(new InternetAddress(username, "prac_Admin"));
javaMailSender.send(message);
}
public void sendEmail(String to, String subject, String msg, List<MessageTemplateImage> messageTemplateImageList) throws MessagingException, IOException {
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper messageHelper = new MimeMessageHelper(message, true, StandardCharsets.UTF_8.name());
messageHelper.setFrom(new InternetAddress(username, "prac_Admin"));
messageHelper.setTo(to);
messageHelper.setSubject(subject);
messageHelper.setText(msg, true);
MimeMultipart multipart = new MimeMultipart();
BodyPart bodyPart = new MimeBodyPart();
bodyPart.setContent(msg, "text/html; charset=UTF-8");
multipart.addBodyPart(bodyPart);
for (MessageTemplateImage messageTemplateImage : messageTemplateImageList) {
byte[] attachmentByteList = downloadFromUrl(messageTemplateImage.getImageUrl());
DataSource dataSource = new ByteArrayDataSource(attachmentByteList, "application/octet-stream");
MimeBodyPart mimeBodyPart = new MimeBodyPart();
mimeBodyPart.setDataHandler(new DataHandler(dataSource));
mimeBodyPart.setFileName(messageTemplateImage.getOriginalFileName());
multipart.addBodyPart(mimeBodyPart);
}
message.setContent(multipart);
javaMailSender.send(message);
}
private byte[] downloadFromUrl(String imageUrl) throws IOException {
URL url = new URL(imageUrl);
URLConnection connection = url.openConnection();
connection.connect();
try (InputStream inputStream = connection.getInputStream();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int byteListRead;
while ((byteListRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, byteListRead);
}
return outputStream.toByteArray();
}
}
'Back-end > Java' 카테고리의 다른 글
[Java] Java Generic 이란? (0) | 2022.12.19 |
---|---|
[Java]Filter와 interceptor 차이점과 용도 (0) | 2022.12.10 |
[Java] Servlet이란? (0) | 2022.12.09 |
[Java] JVM 메모리구조란? (0) | 2022.12.08 |
[Java] String, StringBuffer, StringBuilder 차이점과 장단점 (0) | 2022.12.06 |