해당 글에서는 Spring Boot 환경에서 aws SES를 이용한 메일 전송 기능 구현에 대해 다룹니다.
사이드 프로젝트 `모카콩`의 Wiki에 작성한 글에 해당된다.
해당 프로젝트 github: https://github.com/mocacong/Mocacong-Backend
들어가며
모카콩의 초기 프로세스 로직에 이메일 인증 기능이 있었다는 사실. 알고 계셨나요?
원래 모카콩에 회원가입하려면 이메일을 인증해야 됐었어요. (지금은 번거롭다고 판단해 비밀번호 찾기 시에만 이메일 인증 기능이 존재하지만요.) 그렇기 때문에 이메일을 보낼 서버가 필요했습니다.
모카콩은 클라우드 서버로 AWS를 사용하고 있었기 때문에 AWS SES를 가장 높은 순위의 후보군으로 고민했습니다. 이후 가격의 합리성, dependency의 업데이트 빈도 등을 파악하여 최종적으로 AWS SES를 사용하기로 결정했습니다.
AWS SES는 Amazon SES SMTP 인터페이스 또는 Amazon SES API를 사용하여 이메일을 보낼 수 있는 기능입니다. 또, Route 53에 간편하게 MX 타입의 DNS 레코드를 등록하여 사용중인 도메인 주소에 해당되는 메일서버를 손쉽게 생성할 수 있습니다.
모카콩은 Route 53을 원래부터 이용하고 있었기 때문에 자격 증명 과정만 거치면 자동으로 메일 관련 타입에 해당되는 MX 타입의 DNS 레코드가 등록될 수 있었습니다. 자격 증명 과정은 어렵지 않으므로 생략하겠습니다.
바로 연동하는 코드를 살펴보러 가보겠습니다.
build.gradle
1
2
3
|
implementation 'org.springframework.boot:spring-boot-starter-mail'
implementation 'com.amazonaws:aws-java-sdk-ses:1.12.429'
implementation 'commons-io:commons-io:2.11.0'
|
cs |
Spring Boot에서는 메일을 편리하게 보낼 수 있는 의존성인 spring-boot-starter-mail 의존성이 존재합니다. 바로 추가해주도록 합시다.
그리고 우리는 aws ses를 이용할 것이기 때문에 mvnRepository에서 ses를 검색하여 가장 상위에 나오는 라이브러리인 aws-java-sdk-ses 라이브러리를 선택했습니다.
https://mvnrepository.com/artifact/com.amazonaws/aws-java-sdk-ses
버전은 비교적 최신이자 사람들이 많이 쓰는 버전으로 선택했습니다.
마지막으로 프론트 측에서 작성해준 메일 html 파일을 편리하게 String으로 변환해줄 apache에서 만든 commons-io 라이브러리를 설정해주었습니다. 이 라이브러리는 메일 연동과는 상관없으므로 필수적인 라이브러리는 아닙니다.
application.yml
cloud:
aws:
ses:
access-key: ${SES_ACCESS_KEY}
secret-key: ${SES_SECRET_KEY}
AWS의 IAM 사용자에서 설정해준 access-key와 secret-key를 추가해줍니다. S3 스토리지 연동이나 다른 AWS 서비스 이용을 목적으로 설정한 IAM에 권한 추가하는 방법도 있지만, 보안을 위해 별도의 access-key, secret-key를 가지는 IAM을 생성해주었습니다.
AwsSESSender.class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
@Slf4j
@Component
public class AwsSESSender {
private final AmazonSimpleEmailService amazonSimpleEmailService;
public AwsSESSender(@Value("${aws.ses.access-key}") String accessKey,
@Value("${aws.ses.secret-key}") String secretKey) {
BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey);
AWSStaticCredentialsProvider credentialsProvider = new AWSStaticCredentialsProvider(awsCredentials);
this.amazonSimpleEmailService = AmazonSimpleEmailServiceClientBuilder.standard()
.withCredentials(credentialsProvider)
.withRegion("ap-northeast-2")
.build();
}
}
|
cs |
스프링부트에서는 yml 설정에서 cloud.aws.ses prefix 설정을 따르면 기본적으로 AmazonSimpleEmailService을 자동으로 생성해줍니다. 모카콩에서는 prefix 설정이 살짝 달라서 위처럼 직접 생성해주었습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
@Slf4j
@Component
public class AwsSESSender {
// ...
@Async
public void sendToVerifyEmail(String to, String code) {
try {
// 이메일 HTML 파일 위치를 통해 HTML 파일 가져오기
File file = new File(VERIFY_EMAIL_FILE_PATH);
// 라이브러리를 이용하여 HTML 파일 내용을 String으로 변환
String contentHtml = FileUtils.readFileToString(file, StandardCharsets.UTF_8);
// 실제 인증코드 삽입
String updatedContent = contentHtml.replace(INITIAL_CODE, code);
// 이메일 전송
SendEmailRequest request = generateSendEmailRequest(to, updatedContent);
amazonSimpleEmailService.sendEmail(request);
} catch (IOException e) {
log.error("이메일 생성 중 문제가 발생했습니다. error message = {}", e.getMessage());
throw new IllegalArgumentException(e.getMessage());
}
}
}
|
cs |
이제 본격적으로 메일 전송 로직을 살펴봅시다.
우선 메일을 AWS SES 서버와 연동하여 실제로 전송하는 기능은 매우 느립니다. 그렇기 때문에 웬만해서는 비동기로 작업하는 것을 추천하며, @Async 어노테이션을 달아줍시다. 만약 @EnableAsync 설정 및 ThreadPool 별도 설정을 하지 않은 상태라면 추가해주도록 합시다.
이메일 html 파일을 String으로 변환하여 실제 인증코드를 넣는 11~15번째 라인에 대한 코드 설명은 생략하겠습니다. aws SES와 무관한 모카콩 로직이기 때문입니다.
18번째 줄부터가 핵심인데, 여기를 함 볼까요? aws SES는 SendEmailRequest을 요청으로 이메일을 전송하는 기능을 제공합니다.
SendEmailRequest는 위와 같이 이루어져 있습니다. 따라서 우리는 Source, Destination, Message를 설정해야 합니다.
Destination
1
2
|
Destination destination = new Destination()
.withToAddresses(to);
|
cs |
Destination은 위와 같이 생성해줍시다.
메일을 보낼 목적지입니다. 여기서 to는 프론트 측으로부터 넘겨받은 수신자 메일 주소가 됩니다.
Message
1
2
3
4
5
6
7
8
9
10
11
12
|
Message message = new Message()
.withSubject(createContent(TITLE))
.withBody(new Body()
.withHtml(createContent(content))
);
// createContent 메서드
private Content createContent(String text) {
return new Content()
.withCharset("UTF-8")
.withData(text);
}
|
cs |
Message는 이메일 제목, 내용이 포함됩니다.
모카콩에서는 HTML 형식으로 content를 보내므로 위와 같이 withHtml()을 통해 설정해주었습니다.
SendEmailRequest 생성
1
2
3
4
|
return new SendEmailRequest()
.withSource(FROM)
.withDestination(destination)
.withMessage(message);
|
cs |
여기서 FROM은 보내는 측의 이메일 주소입니다.
이렇게 하여 SendEmailRequest를 만들 수 있습니다.
MemberService.class
1
2
3
4
5
6
7
8
|
public EmailVerifyCodeResponse sendEmailVerifyCode(String to) {
Random random = new Random();
int randomNumber = random.nextInt(EMAIL_VERIFY_CODE_MAXIMUM_NUMBER + 1);
String code = String.format("%04d", randomNumber);
// 이메일을 보내는 메서드
awsSESSender.sendToVerifyEmail(to, code);
return new EmailVerifyCodeResponse(code);
}
|
cs |
이렇게 해서 이메일을 보내는 로직을 완성했습니다! 참고로 ApplicationEventPublisher을 이용한 이벤트 발행 방식으로도 많이들 구현하는 것으로 알고 있습니다. 원하시는대로 구현하시면 될 듯합니다.
참고로 aws SES 연동 시 aws 측에게 별도의 요청을 하지 않으면(즉, 처음 연동할 때에는) 샌드박스에 있다는 문구가 뜹니다.
샌드박스에 있을 경우 아래 제한에 걸리게 됩니다.
- 확인 자격 증명을 하지 않은 계정에게 메일 발송 불가
- 일일 메일 발송제한 200건
그 외에도 여러 제한이 있지만, 체감상 느낀 제한은 위 2개 정도였습니다. 발송 제한 건수는 일 200건 정도면 문제될 정도는 아니지만, 수신자 이메일을 매번 자격 증명을 해야된다는 점이 굉장히 큰 번거로움으로 느껴졌습니다.
이를 해결하기 위해 aws 직원분과 연락을 해봤습니다만, 한도 제한 해제 요청 reject를 당해버렸습니다.
아직 정식 배포하지 않은 앱이고, HTTPS 적용 전이며 구체적인 메일 처리 계획이 없었던 것이 가장 큰 이유이지 않을까 싶습니다.
정식 배포하기 직전에 다시 한 번 도전해봐야겠습니다.
+) 23.05.23. 추가
Sandbox 해제 승인 과정이 궁금하다면 아래 글 클릭!
https://kth990303.tistory.com/449
그래도 이메일 인증 api를 성공적으로 구현하고 나니 뿌듯합니다 :)
'Infra > Aws' 카테고리의 다른 글
[Infra] 와이어샤크로 tcpdump 파일 분석하여 AWS ALB idle timeout 설정하기 (4) | 2023.05.15 |
---|---|
[230504] AWS Summit Seoul 2023 후기 (17) | 2023.05.04 |
[AWS] CloudWatch + Lambda로 slow query 발생 시 Slack 알림 보내기 (0) | 2023.04.21 |
[AWS] S3 연동 후 Spring Boot 파일 업로드 구현 일지 (0) | 2023.04.09 |
[AWS] 프리티어 사용중에 Route 53 요금 발생? (feat. aws 프리티어 아키텍처) (4) | 2023.02.06 |