Programming/AWS

[AWS] AWS SES로 메일 발송하기

유하얀 2024. 6. 12. 23:42

목차

  1. 서론
  2. AWS SES
  3. NestJS API 작성
    1. 사전에 필요한 내용
      1. AWS SES 생성
      2. AWS SES 샌드박스에서 프로덕션으로 업그레이드하기
      3. Credential 저장해두기
    2. SDK를 활용해 API 작성하기
  4. 문제점
    1. AWS SES 모니터링 화면을 통해 문제점 찾기
    2. 해결 방안 모색
  5. RECOMMEND FOR YOU & REFERENCE

서론

기존에 사용하던 nodemailer 대신 AWS SES를 사용하여 메일 발송을 구현하였다.

AWS SES를 사용하기로 결정한 이유는 다음과 같다.

  1. nodemailer가 보낸 메일은 자꾸 스팸 처리가 되었다.
  2. 인증된 곳에서 보내고 발송 실패할 경우에도 기본적인 처리 방법을 제시해준다. (실제로 서비스하는 시스템을 개발 중에 있다보니 이 점이 꽤나 중요했다.)
  3. GUI로 대시보드를 보여주고, 문서화가 잘 되어 있어 개발자가 사용하기에도 편리하다.
  4. 다양한 언어에서 SDK를 지원하여 추후 다른 언어로 마이그레이션을 진행할 때에도 사용하기 좋다.

사실 무엇보다도, 같이 개발을 공부하는 친구들에 비해 AWS 사용 경험이 적다는 점이 단점으로 작용할까봐 사용하고 싶었던 것도 있다.

AWS의 여러 서비스들은 복합적으로 얽혀있기 때문에 SES를 사용하기 위해 공부를 하면서 AWS가 제공하는 다른 서비스들도 공부할 수 있었다. 특히 IAM을 많이 배우게 되었다.


AWS SES

AWS SES는 메일 발송을 도와주는 서비스로, Simple Email Service의 줄임말이다.
최근 여기어때 등의 기술블로그에서도 종종 보이고 있다.

SESAWS CLI에서 사용하거나, SDK로 프로그래밍을 통해 사용하거나, 다른 웹 GUI에서 사용하는 등 다양한 발송 방법이 있다.

이 중에서 필자는 SDK로 프로그래밍해 발송 API를 구현하는 방법을 선택했다.

AWS SES SDK에 대해 더 알고 싶다면 아래의 공식 문서를 확인해보자.

https://docs.aws.amazon.com/ses/


NestJS API 작성


사전에 필요한 내용

AWS SES 생성

우선, 당연하겠지만 AWS 계정이 필요하다.
SES 서비스에 전체 접근 권한이 있는 IAM 계정 또는 루트 계정이 준비되어야 한다.

이후 콘솔창으로 들어가서 검색창에 SES를 검색하면 Amazon Simple Email Service 서비스에 접속할 수 있다.
처음 접속하면 아래와 같은 창이 뜰 것이다.

"시작하기" 버튼을 눌러 설정으로 들어간다.

아래의 설정에서는 예시를 들기 위한 가상의 이메일 주소인 abcde@fghi.com을 사용해보도록 하겠다.

 

이메일을 발송할 주소인 abcde@fghi.com를 입력한다.

 

이메일 주소가 전송되는 도메인인 fghi.com을 입력해준다.

대부분의 경우 이메일이 발송되는 도메인은 웹사이트의 도메인과 같은 것을 사용할 것이다.

 

다음 단계로 넘어가게 되면 MAIL FROM 도메인과 MX 실패 시 동작을 설정하는 창이 등장한다.

하위 도메인은 입력하지 않아도 상관 없으며, 입력하게 될 경우에는 From 주소를 맞출 수 있다.

MX 실패 동작은 전송 시 필요한 레코드를 찾을 수 없을 때, 기본 설정을 사용해서 발송할 것인지 발송을 취소할 것인지를 선택하는 것이다.

필자는 우선 기본 설정을 사용해서 발송하기를 선택하였다.

 

검토 페이지를 보여주고 있다.

모든 사항을 한 번씩 확인해주고 Get Started를 클릭해 넘어가자.

 

AWS SES 샌드박스에서 프로덕션으로 업그레이드하기

SES의 콘솔 창은 다음과 같은 모양을 갖고 있다.

상태를 보면 현재 "샌드박스 - 전송 한도 및 제한 적용"이라고 적혀있다.

샌드박스 상태에서는 전송 할당량이 하루 200건으로 제한되며, 수신 이메일의 도메인이 제한되는 등 다양한 제약 사항이 걸려있다.

프로덕션 상태는 물론이고 개발중인 상태에서도 다른 도메인에 이메일을 발송하고 싶을 경우에는 프로덕션으로 업그레이드를 시켜주어야 한다.

프로덕션으로 업그레이드를 요청하기 위해서는 사전에 몇 가지 준비가 필요하다.

  • 이메일 주소 확인 : 처음에 작성했던 이메일 주소가 확인되어야 한다. 해당 이메일의 받은 편지함으로 들어가 발신자가 "no-reply-aws@amazon.com"인 메일에서 인증을 진행해주자.
  • 전송 도메인 확인 : 두 번째 단계에서 작성한 도메인 주소에 이메일 발송 관련 레코드 설정을 올려야 한다. AWS에서는 다음과 같이 안내하고 있다.
    • 전송 도메인 확인 카드:
      DNS 레코드 가져오기 버튼을 선택하고 세 개의 CNAME 레코드 각각에서 이름 및 값을(를) 복사한 다음 DNS 공급자의 지시에 따라 도메인의 DNS 설정에 추가합니다.
      DNS 공급자에 따라 모든 항목이 적절한 필드에 올바르게 입력되면 몇 분 후에 상태가 확인됨(으)로 변경됩니다.
      상태가 오류 - 이유 알아보기(으)로 변경되면 링크를 선택하여 설명과 문제 해결 방법을 확인합니다.

위 단계를 모두 마치면 프로덕션으로 업그레이드 요청을 할 수 있다.
요청할 때는 최대한 자세하게, 구구절절 말해야 잘 들어준다.

 

첫 요청 이후 돌아온 답변. 얼마나 자세하게 써야 하는지를 모르겠어서 구구절절 한 페이지 넘게 썼더니 넘겨줬다.

 

저런 답변을 받고 난 이후 5천자 정도 썼더니 한도를 올려주셨다.

필자는 좀 과하게 쓴 경향이 있을지 모르겠으나, 이 글을 보고 SES 프로덕션을 시도하는 여러분도 자세하게 쓰길 바란다.

프로덕션이 되면 아래와 같이 일일 전송 할당량이 대폭 늘어나며, 최대 전송 속도 역시 꽤 늘어난 것을 볼 수 있다.

일일 전송량은 200개에서 50,000개로 늘었고, 1초당 전송되는 이메일의 수도 1개에서 14개로 늘어났다.

 

Credential 저장해두기

잠깐 말해두자면, 프로그래밍 방식으로 SDK를 이용할 때에는 AWS Credential이 필요하다.
이를 위해 AWS (SES 사용 권한이 있는) IAM 또는 루트 계정에서 "보안 자격 증명"에 들어가 액세스 키를 생성해주어야 한다.
하나의 IAM에 최대 2개까지만 만들 수 있으므로 한 번 생성한 액세스 키를 잃어버리지 말고 잘 저장해두는 것을 추천한다.

잃어버리면 삭제하고 새로 발급받아야 한다. 꼭 잘 저장해두도록 하자.


SDK를 활용해 API 작성하기

NestJSNodeJS 기반의 프레임워크이므로 npm에서 라이브러리를 가져온다.
AWS SES를 사용하기 위해 @aws-sdk/client-ses를 설치한다.

import { SendEmailCommand, SESClient } from "@aws-sdk/client-ses";

 

SDK v3부터 이렇게 원하는 함수만 가져와 사용할 수 있게 되었다.
자세한 사항은 AWS SDK SES v3 공식 문서를 읽도록 하자.

 

  constructor(@Inject(SESClient) private ses: SESClient) {
    this.ses = new SESClient({
      region: "ap-northeast-2",
      credentials: {
        accessKeyId: process.env.AWS_ACCESS_KEY_ID,
        secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
      },
    });
  }

 

생성자를 활용해 SES Client 객체를 생성해준다.
이 때 regioncredential은 반드시 입력해주어야 하며, credential에 필요한 내용은 위에서 저장했던 Credential 파일에서 가져온다.
이 외에도 옵션을 자유롭게 선택할 수 있다.

 

  command = (request: SendEmailRequestDto) =>
    new SendEmailCommand({
      Destination: {
        ToAddresses: [request.email],
      },
      Message: {
        Body: {
          Html: {
            Charset: "UTF-8",
            Data: request.content,
          },
        },
        Subject: {
          Charset: "UTF-8",
          Data: request.title,
        },
      },
      Source: process.env.LOG_EMAIL,
      ReplyToAddresses: [],
    });

 

SendEmailCommand 객체를 생성하여 발송할 이메일에 대한 설정을 해준다.
일반 텍스트만 보내고 싶을 경우 Html 대신 Text 속성을 사용할 수도 있다.

Message에서 BodySubject 속성은 필수이고, Source 속성은 발신자의 이메일 주소, Destination은 수신자의 이메일 주소를 입력해주면 된다.

 

  send = async (
    request: SendEmailRequestDto,
  ): Promise<SendEmailResponseDto> => {
    const response = await this.ses.send(
      this.command({
        email: request.email,
        title: request.title,
        content: request.content,
      }),
    );
    const { requestId, attempts, totalRetryDelay } = response.$metadata;

    return {
      requestId,
      attempts,
      totalRetryDelay,
    };
  };

 

마지막으로 SESClient로 생성한 객체인 sessend 함수에 인자로 command 객체를 넣어주면 발송이 완료된다.
또한 발송 이후 반환값이 들어간 response 객체의 $metadata에는 http 코드 등이 들어가 있으니, 적당히 골라서 반환값으로 처리해주면 된다.


회고

새로운 기술을 사용할 수 있어 상당히 재미있는 경험을 했다고 생각한다.

 

그리고, 새로운 기술이다 보니 경험이 없기도 하고, DNS 레코드를 직접 설정할 수 없는 등등의 문제로 이슈가 하나 발생했는데...
해당 이슈와 회고글은 다음 글에 작성하도록 하겠다.


RECOMMEND FOR YOU

[AWS] AWS SES 하드 바운스 이슈 해결하기

REFERENCE