개발자의 길

[springboot] java mail 보내기 본문

4. JAVA

[springboot] java mail 보내기

자르르 2023. 5. 24. 16:11


MailSender 인터페이스를 상속받은 JavaMailSender를 이용하는 이메일 전송 시스템

 

build.gradle 디펜던시 추가

	implementation 'org.springframework.boot:spring-boot-starter-mail'
    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'
    }

 

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

 

dto 생성

import java.util.List;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MailDto {

    private String fromAddress;
    private List<String> toAddressList;
    private List<String> ccAddressList;
    private List<String> bccAddressList;
    private String subject; // 메일 제목
    private String message; // 메일 내용
    private String messagePath; // HTML 메일 형식의 파일 경로(isUseHtml이 ture 인 경우)
    private Boolean isUseHtml; // 메일 형식이 HTML인지 여부(true, false)
    private List<AttachFileDto> attachFileList;

    private String title; // 메일 템플릿 타이틀
    private String contents; // 메일 템플릿 내용
    private String addInfo;
}
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AttachFileDto {
    // 첨부파일로 등록할 파일 경로
    private String attachPath;
    // 첨부파일명
    private String attachName;
}

실제 메일 발송 class 생성

import java.io.File;
import java.util.List;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.FileSystemResource;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeUtility;

/**
 * 메일 전송 서비스 클래스이다.
 *
 * @see JavaMailSender
 */
@Slf4j
@RequiredArgsConstructor
@Component
public class MailHandler {

    private final JavaMailSender javaMailSender;
    private static final String CHARSET = "UTF-8";

    /**
     * 비동기 방식으로 메일 전송하는 메소드이다. {@code @Async} 어노테이션으로 별도의 스레드에서 실행된다.
     *
     * @param mailDto 메일 정보를 담은 객체
     * @return 메일 전송 성공 여부 (true: 성공, false: 실패)
     */
    @Async
    public Boolean sendMail(MailDto mailDto) {

        // 파일명이 길 경우 줄바꿈을 하지 말라는 옵션 ** 중요
        System.setProperty("mail.mime.splitlongparameters", "false");
        // MimeMessage 객체 생성
        MimeMessage mimeMessage = javaMailSender.createMimeMessage();
        try {
            // MimeMessageHelper를 통해 메일 전송 서비스 작성(use multipart true로 하여 첨부파일도 전송 가능)
            MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, CHARSET);


            helper.setSubject(MimeUtility.encodeText(mailDto.getSubject(), CHARSET, "B")); // Base64 encoding
            helper.setText(mailDto.getMessage(), (mailDto.getIsUseHtml() == null) ? true : mailDto.getIsUseHtml()); // isUseHtml true일 경우 HTML 형식으로 메일 전송
            helper.setFrom(new InternetAddress(mailDto.getFromAddress(), mailDto.getFromAddress(), CHARSET));

            if (!CollectionUtils.isEmpty(mailDto.getToAddressList())) {
                helper.setTo(listToArray(mailDto.getToAddressList()));
            }

            if (!CollectionUtils.isEmpty(mailDto.getCcAddressList())) {
                helper.setCc(listToArray(mailDto.getCcAddressList()));
            }
            if (!CollectionUtils.isEmpty(mailDto.getBccAddressList())) {
                helper.setBcc(listToArray(mailDto.getBccAddressList()));
            }
            if (!CollectionUtils.isEmpty(mailDto.getAttachFileList())) {
                for (AttachFileDto attachFileDto : mailDto.getAttachFileList()) {
                    FileSystemResource file = new FileSystemResource(new File(attachFileDto.getAttachPath()));
                    // FileDataSource file = new FileDataSource(attachFileDto.getAttachPath());

                    helper.addAttachment(MimeUtility.encodeText(attachFileDto.getAttachName(), CHARSET, "B"), file);
                }
            }
            javaMailSender.send(mimeMessage);
        } catch (Exception e) {
            return false;
        }
        return true;
    }

    /**
     * 문자열 리스트를 InternetAddress 배열로 변환하는 메소드이다.
     *
     * @param addressList 메일 주소를 담은 문자열 리스트
     * @return {@link InternetAddress} 객체를 담은 배열
     * @throws AddressException 잘못된 형식의 주소가 있을 경우 발생하는 예외
     */
    private static InternetAddress[] listToArray(List<String> addressList) throws AddressException {
        InternetAddress[] internetAddresses = new InternetAddress[addressList.size()];
        for (int i = 0; i < addressList.size(); i++) {
            internetAddresses[i] = new InternetAddress(addressList.get(i));
        }
        return internetAddresses;
    }
}

 

메일 발송 class 예제

//메일 발송
MailDto mailDto = new MailDto();

//메일 발송템플릿html 안에 있는 변수 설정
Map<String, String> mailValueMap = new HashMap<>();
mailValueMap.put("SEND_DATE", Today);
mailValueMap.put("SOLUTION_NAME", solutionName);
mailValueMap.put("SystemMailDomain", domainName);
mailValueMap.put("Subject", subject);
mailValueMap.put("LoginID", loginId);

//업데이트

// 템플릿에 전달할 데이터 설정
Context context = new Context();
mailValueMap.forEach(context::setVariable);
String mailTemplatePath = "mail/template"; //메일 발송 템플릿html

// 메일 내용 설정 템플릿 프로세스
mailDto.setSubject(subject);
//상단에 class templateEngine을 선언해줬다.
// private final SpringTemplateEngine templateEngine;
mailDto.setMessage(templateEngine.process(mailTemplatePath, context));
mailDto.setIsUseHtml(true);
mailDto.setFromAddress(solutionEmail);
mailDto.setToAddressList(List.of(loginId));

mailHandler.sendMail(mailDto);

 

 

개고생 부분을 설명하겠다.(사실 이거때문에 글을 쓸 생각했음)

MailHandler.java 안에 sendMail을 보면,

 

첨부파일 영역이 있다.

그런데 첨부파일 명의 자꾸 제대로 안나오는 부분이 발생했다.

처음엔 한글 문제인가 싶어서 encoding까지 했지만(물론 이 문제도 맞았다.), 알고 보니, 첨부파일명의 길이가 문제 였다.

 

소스 내부를 살펴보다 보니, RFC 2047라는 형식에 맞춰서, 파일명 길이가 75byte가 넘으면, 공백(띄어쓰기)로 인코딩 문자를 넘겨서 실제 메일 발송 라이브러리 쪽에서 공백이 있으면 배열 형식으로 첨부파일명을 정의하는 방식이었다. 

이게 naver 메일에서는 아~무 문제없이, 잘 되었는데 gmail과 회사 내부 smtp 시스템에서는 이걸 해석을 못했다..이상했음.

그래서 해결책을 찾아보다가,

 

내가 만든 sendMail 메소드 첫번째 줄에 보면

 

System.setProperty("mail.mime.splitlongparameters", "false");

라고 정의한 부분이 있다.

이 옵션을 주면, 파일명 길이가 길어져서 공백(띄어쓰기)가 있든 없든, 배열로 자르는게 아니라 한줄로 쭉~ 보내라는 설정이다.

 

이 한줄을 찾는데 하루를 버렸다..

 

수고링..

 



이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
공유하기 링크
Comments