AWS SES로 안정적인 메일 발송 시스템 구축하기

매일메일·2024년 11월 6일
17
post-thumbnail

매일메일은 어떤 서비스인가요?

매일 오전 7시에 무작위의 기술 질문과 답변을 보내 드리는 서비스입니다.
수익 창출 목적 전혀 없이, 세상에 쓰이는 서비스를 만들고자 매일메일을 만들게 됐습니다.

이메일 등록만 해놓으시고 하루에 5분만 투자해보세요!

📮 매일메일 바로가기

서론

안녕하세요, 매일메일 백엔드 개발팀입니다. 구독자님들의 많은 관심 속에서 백엔드 시스템도 지속해서 진화하고 있는데요. 메일 발송 관련 문제들과 해결하기 위한 고민을 공유하기 위해서 시리즈를 연재해 보기로 결정했습니다. 서비스 초기에는 Gmail SMTP를 이용해 메일 전송을 사용하고 있었는데요. 오늘은 이를 사용하면서 만난 문제들과 AWS SES로 이전하는 과정들을 다뤄보려고 합니다.

구독자가 없었을때..!

매일메일 서비스의 초기 요구사항은 "오전 7시에 분야별 면접 질문을 메일로 전송한다." 밖에 없었습니다. 그리고, 사용자도 저희 팀원들밖에 없는 상황이었기 때문에 화려한 기술을 사용할 만한 근거가 없었어요. 따라서, 목적을 달성하기 위한 가장 쉽고 저렴한 방법을 사용했는데요. 바로 spring-boot-starter-mailGmail SMTP를 사용하는 것이었습니다. 그리고 @Scheduled를 살짝 곁들였어요.

SMTPSimple Mail transfer Protocol의 약자입니다. 다른 사람에게 메일 메시지를 전송하고 받을 때 사용하는 전송 규약입니다. 메일 서버는 SMTP를 사용하여 메일을 주고받을 수 있습니다. Gmail에서는 SMTP를 사용하는 메일 서버를 사용할 수 있도록 해주는데요. 하루에 500건의 메일을 전송할 수 있고 구축도 간편하여 채택했습니다.

스프링 프레임워크에서는 이메일 전송을 위해 JavaMailSender를 제공합니다. JavaMailSender는 MIME 메시지 지원과 다양한 메일 관련 기능들을 제공해요. 그리고 spring-boot-starter-mail 모듈은 이를 쉽게 사용할 수 있도록 도와주는 starter 프로젝트 중 하나에요. 나중에 기회가 되면 스프링과 부트의 관계를 다뤄보도록 할게요. 😀

@Scheduled 어노테이션은 스프링 환경에서 스케줄러를 등록하기 위해서 사용했습니다. @EnableScheduling을 등록하고, 스케줄 메서드에 @Scheduled 어노테이션을 등록하면 해당 어노테이션에 등록된 크론 표현식을 기반으로 주기적으로 메서드를 실행할 수 있어요. 아래는 사용 예시입니다.


public class SendQuestionScheduler {

    // 매일 오전 7시에 메일을 전송한다.
    @Scheduled(cron = "0 0 7 1/1 * ?", zone = "Asia/Seoul")
    public void sendQuestion() {
        메일을_전송한다();
    }
}
@EnableScheduling
@SpringBootApplication
public class MaeilMailApplication {

    public static void main(String[] args) {
        SpringApplication.run(MaeilMailApplication.class, args);
    }
}

구독자가 100명인 상황과 불안정한 메일 전송

본격적으로 서비스를 홍보하고 운영해야겠다고 결정했어요. 그리고, 한 슬랙 커뮤니티에 홍보를 진행했었어요. 첫 홍보 이후에 50명 이상의 구독자를 확보했었습니다. 이 시점에 깃허브 알고리즘에 매일메일 서비스가 노출됐고 자연 유입 구독자가 발생하여 구독자가 100명에 도달했어요. 그리고.. 2가지 문제가 발생했습니다. 🥲

  • 메일이 스팸으로 처리된다는 피드백을 받았어요.
  • 메일 발송이 차단되는 문제가 생겼어요.

Gmail 메일 발송 차단 문제

분명히 구독자가 거의 없었을 때는 제대로 동작했던 Gmail이 갑자기 전송 메일 수가 늘어나니 전송이 제대로 안 됐어요. 서버에는 에러 로그가 생성되지 않았고, 아래와 같이 메일 전송이 차단되어 있었습니다.

처음에는 Gmail SMTP 서버의 처리량을 넘었거나 스팸 처리로 인해서 차단 당했나 싶었어요. 그래서, 신규 계정을 생성하고, 비동기 전송 방식에서 동기 전송 방식으로 변경한 뒤에 다음날 전송해 봤어요. 하지만, 또 다시 차단 당하는 상황이 발생했어요. 이 문제를 해결하기 위해서 열심히 문서를 찾는 중 다음과 같은 글을 찾았어요.

Why are my bulk emails being blocked starting August 2, 2019?

글에는 개인 Gamil은 대량 이메일 서비스로 의도되지 않았고, 대량 이메일 발송 시 Google에서 차단될 수 있다는 내용이 있었어요. 이때 팀 내부에서 대량 이메일 발송 서비스를 직접 구축하거나 안정적인 타사 서비스를 사용해야겠다는 공감대가 형성됐습니다.

구글 컴퓨터의 컨디션이 안 좋았나? 싶기도 했었어요. 😅

발송 메일 스팸 처리 문제

발송 메일이 스팸 처리된 원인은 매일메일 서비스에서 발송한 메일이 인증되지 않았기 때문이었어요. 즉, "내가 보낸 메일은 안전해!"를 증명할 방법이 없었습니다. 메일이 안전하다는 것을 증명하기 위해서는 DKIM, SPF, DMARC를 사용해 볼 수 있는데요. 간단하게 설명을 해보겠습니다.

DKIM(DomainKeys Identified Mail)

DKIM에는 공개 키와 개인 키를 생성하는데요. 발신자는 DNS에 공개 키를 등록하고, 개인 키로 전자 서명된 메일을 전송합니다. 그리고 수신자는 DNS에서 공개 키를 찾아서 메일을 복호화합니다.

SPF(Sender Policy Framework)

SPF는 메일 서버의 정보를 DNS에 등록하고, 발신자 정보를 DNS 레코드와 대조하여 메일을 검증하는 기술입니다. 메일 수신자는 메일 발신 도메인이 정상적인 도메인에서 발송되었는지 질의를 하여 처리합니다.

DMARC(Domain-based Message Authentication, Reporting, and Conformance)

DMARC는 DKIM 및 SPF 인증이 실패했을 경우 메일을 어떻게 처리해야 할지 알려줍니다. 스팸으로 처리하거나 메일을 삭제하는 등 메일 서버에서 수행해야 할 작업을 명시할 수 있어요. 마찬가지로 DMARC 정책도 DNS 레코드로 저장됩니다.

AWS SES로 이전하기

매일메일 서비스는 위 문제들을 해결하기 위해서 AWS SES(Simple Email Service) 를 사용하기로 결정했어요. 앞서 언급 드린 DKIM, SPF, DMARC 설정이 간편하고, 메일 관련 지표(차단, 반송, 조회 등)를 편리하게 추적할 수 있고 다른 서비스와 통합하기 쉽기 때문입니다. 또한 프리티어 플랜을 활용할 수 있기 때문에 매력적이었습니다.

AWS SES를 프로덕션 환경에서 사용하려면 3가지 사전작업이 필요했는데요. 아래와 같습니다.

  • 샌드박스 해제 요청
  • SMTP 보안 인증 생성
  • 도메인 인증 및 DKIM, SPF, DMARC 설정

도메인 인증 및 DKIM, SPF, DMARC 설정은 여기어때컴퍼니 SRE팀 - AWS SES 구축기에 자세히 설명되어 있습니다.

샌드박스 해제 요청

처음에는 위와 같이 샌드박스 계정으로 설정되어 있었습니다. 샌드박스 계정은 전송 할당량과 전송 속도가 상당히 낮으며, SES에 자격 증명된 메일로만 메시지를 전송할 수 있었습니다. 이를 해결하기 위해서 AWS 측에 샌드박스 해제를 요청했습니다. 샌드박스 해제 요청을 위해서 다음과 같은 정보를 AWS 측에 추가로 전송했습니다.

  • 서비스 소개
  • 메일 전송 빈도
  • 서비스 초기 목표 메일 전송량
  • 수신자 목록 유지 관리 방법
  • 수신자의 개인 정보 수집 방식
  • 반송, 구독 취소 요청 관리 방법
  • 이메일의 예시

샌드박스 해제 요청 처리는 2 ~ 3일 정도 소요되었습니다. AWS에서 주말에는 요청을 처리하지 않았어요. (추가 정보가 충분하지 않으면 해제 요청이 더욱 오래 소요될 수 있으니 참고해 주세요.)

SMTP 보안 인증 생성

AWS SES를 스프링 애플리케이션에서 사용하기 위해서는 보안 인증을 생성해야 했는데요. SES 서비스 좌측에 SMTP 설정으로 이동한 다음 [SMTP 보안 인증 생성] 버튼을 클릭하여 SMTP를 사용할 수 있는 인증 정보를 생성했습니다.

인증 정보를 성공적으로 생성하고 application.yml 내부에 spring.mail.usernamespring.mail.password에 인증 정보를 입력하고 spring.mail.host에는 smtp 엔드 포인트를 입력하여 AWS SES와 스프링 애플리케이션을 연동했습니다.

위와 같은 작업을 거치고 Gmail SMTP에서 AWS SES로 성공적으로 이전했습니다. 그리고 스팸과 차단 문제를 해결했습니다.

분산 서버 환경에서 중복 메일 발송 문제(다음편 예고)

사용자가 점점 많아지면서 단일 서버로는 한계가 있을 것이라 판단했습니다. 따라서, 위 그림과 같이 가용성을 위해 서버 이중화를 하기로 결정했습니다.

그런데 또 다른 문제가 발생했습니다. @Scheduled가 각 서버에서 동작하기 때문에 메일을 중복 발송할 수 있게 된 것이 문제였습니다. 이를 해결할 수 있는 다양한 옵션들이 있었는데요. 각 옵션의 트레이드 오프를 고려해가며 문제를 해결한 경험을 다음 글에서 공유할까 합니다. 많은 관심 부탁 드립니다. 🙇🏻‍♂️

혹시, 해당 글이 도움이 되었다면 하트를 눌러주세요. ❤️ 작은 관심이 서비스 운영에 큰 도움이 됩니다! 감사합니다. :)

📮 매일메일 바로가기

profile
매일메일 - 기술 면접 질문 구독 서비스

4개의 댓글

comment-user-thumbnail
2024년 11월 7일

훌륭해요. 나는 당신의 글에 깊은 인상을 받았습니다. 이런 주제를 보니 반갑습니다. 제 블로그에 오셔서 읽어보시기 바랍니다. https://www.mcdvoice.site

1개의 답글
comment-user-thumbnail
4일 전

It would be interesting to try it in practice, the topic of emails is most relevant to me

1개의 답글