[AWS] AWS SES API를 사용해서 메일 보내기 (feat. NodeJS)
이 글에서 사용하는 것
- AWS SDK - AWS SES API
- NestJS
시작하기 전에
AWS SES는 두 가지 방법으로 사용할 수 있습니다. SMTP 인터페이스를 사용하거나 SES API를 사용해야 합니다.
저는 NestJS 애플리케이션 상에서 메일을 보내고 싶었습니다. 이 글에서는 API 방식을 설명해보겠습니닷.
API를 사용한다면 크게 3가지 방식으로 메일을 보낼 수 있습니다.
1. Simple
SES 객체의 Simple Email 보내는 함수
sendEmail(args: SendEmailCommandInput, options?: __HttpHandlerOptions): Promise<SendEmailCommandOutput>;
sendEmail(args: SendEmailCommandInput, cb: (err: any, data?: SendEmailCommandOutput) => void): void;
sendEmail(args: SendEmailCommandInput, options: __HttpHandlerOptions, cb: (err: any, data?: SendEmailCommandOutput) => void): void;
2. Raw
SES 객체의 Raw Email 보내는 함수
sendRawEmail(args: SendRawEmailCommandInput, options?: __HttpHandlerOptions): Promise<SendRawEmailCommandOutput>;
sendRawEmail(args: SendRawEmailCommandInput, cb: (err: any, data?: SendRawEmailCommandOutput) => void): void;
sendRawEmail(args: SendRawEmailCommandInput, options: __HttpHandlerOptions, cb: (err: any, data?: SendRawEmailCommandOutput) => void): void;
3. Template
SES 객체의 Template Email 보내는 함수
sendTemplatedEmail(args: SendTemplatedEmailCommandInput, options?: __HttpHandlerOptions): Promise<SendTemplatedEmailCommandOutput>;
sendTemplatedEmail(args: SendTemplatedEmailCommandInput, cb: (err: any, data?: SendTemplatedEmailCommandOutput) => void): void;
sendTemplatedEmail(args: SendTemplatedEmailCommandInput, options: __HttpHandlerOptions, cb: (err: any, data?: SendTemplatedEmailCommandOutput) => void): void;
별도 첨부 파일이 있거나 이쁘게 커스터마이징해서 보내고 싶으면 Simple이 아닌 (MIME-formatted eail인) Raw 방식으로 보내야 합니다.
+ AWS SDK가 V1, V2, V3가 있으니 문서 볼 때 조심하세요.
AWS SES API 사용법
SES API를 위한 준비물
- AWS 계정의 Access Key ID, AWS Secret Access Key
- AWS SES 상에서 인증(Verified)된 이메일 주소 (혹은 도메인)
- AWS SES API를 사용하기 위한 SDK
- AWS SES를 Sandbox에서 Production으로 업그레이드
- Verfied된 Identity가 하나라도 있어야지 production으로 업그레이드해주니 주의하세용.
1. 메일용 IAM 사용자를 만들어서 AWS access key를 발급받자
SES API용으로 IAM 창에서 새로운 유저를 만들어보겠습니다.
적절히 이름을 정하고 Access Key를 체크합니다. 이걸 체크해야 이 계정의 AWS access key ID, AWS secret access key를 나중에 받을 수 있습니다.
Next. Permissions를 누릅니다.
권한 설정 창입니다.
미리 SES용으로 만든 정책 그룹 없는 경우 (AWS가 친절하게) 미리 만들어둔 정책 직접 추가(Attach existing policies directly)를 누르고 필요한 정책(policy)을 직접 추가해줍니다.
조금 더 세부적으로 정말 필요한 것만 부여하고 싶다면 Create Policy를 누르고 직접 처음부터 만들면됩니다. 설정이 완료되면 Next: Tags를 누릅니다.
태그 설정 창입니다.
필요시 원하는 태그를 넣어줍니다.
모두 완료되었다면 Next: Review를 눌러서 검토해줍니다.
이상한 점이 없으면 Create user 클릭!
다음에 뜨는 창에서 이 계정의 AWS access key ID와 AWS secret access key를 받을 수 있습니다.
Download .csv 버튼을 눌러서 access key ID와 secret access key를 파일로 다운받습니다.
이 키는 이때만 확인할 수 있고 다음에는 확인할 수 없습니다. 이 키만 있으면 이 계정의 모든 것을 사용할 수 있기 때문에 주의해서 관리해야 합니다. 이 키를 실수로 GitHub에 push하게 되면 AWS 계정이 해킹 당할 가능성이 높습니다. 그래서 계정을 만들 때 정말 필요한 권한만 넣는 것이 좋고 키 관리도 잘해야 합니다.
Tip.
적어도 정말 중요한 정보인데 Downloads 폴더에 방치해놓지만 맙시당...
다운 받으면 이름이 new_user_credential.csv 처럼 설정되는데 이게 계정명이 구분이 안 되기 때문에 여러 Credential이 쌓이게 되면 구분하기가 힘들어집니다. 다운로드 받을 때 비밀번호를 걸 수 있는 다른 문서(ex. number, pdf, word 등)로 옮기고 파일명을 미리 계정명에 맞추어 계정명_credential로 바꿔주면 유출 사고도 어느정도? 방지할 수 있고 관리하기 편합니다.
2. AWS SES 상에서 이메일 주소 혹은 도메인 인증(Verify)
내가 AWS 상에서 인증한 메일(혹은 도메인)만 내 메일 주소로 사용할 수 있습니다. AWS에 들어가서 사용할 Identity를 인증합니다.
이것에 대한 설명은 AWS 문서가 잘 되어 있으므로 생략하겠습니닷
Route53으로 구매한 도메인이 있다면 그 도메인도 편리하게 등록해서 메일 주소로 사용할 수 있습니닷.
(+) Verfied된 Identity가 있어야지 프로덕션으로 업그레이드를 해줍니다!
https://docs.aws.amazon.com/ses/latest/dg/creating-identities.html#verify-email-addresses-procedure
인증된 Identity는 아래처럼 상태가 Verified로 뜹니다.
저처럼 Domain이 아니더라도 개인 이메일 주소에 대한 이메일 인증을 해서 Identity를 추가할 수도 있습니다.
앞으로 우리가 메일을 보낼 때 여기에 보이는 인증된 메일만 보내는 이메일 주소로 사용할 수 있습니다.
3. 애플리케이션에 SES API를 사용하기 위해 AWS SDK 설치
저는 NestJS를 사용하므로 NodeJS 기준으로된 설명입니다.
다른 언어를 쓰시면 그 언어에 해당하는 방식으로 구글링해서 설정해주시면 됩니다.
구글링하시다 보면 AWS SDK가 V2인 글을 많이 보실텐데 V3가 나왔습니다.
Node에서 V2는 AWS SDK를 전부 (require로) 불러와야 해야 했지만 V3부터는 내가 필요한 명령어만 불러(import) 올 수 있습니다. 이 Client가 제공해주는 명령어는 node-modules/@aws-sdk/client-패키지명/.../명령어-폴더에서 찾을 수 있습니다.
사용하는 패키지 매니저로 @aws-sek/client-ses를 설치합니다.
저는 yarn을 씁니다.
yarn add @aws-sdk/client-ses
V3부터는 필요한 것만 import해서 사용할 수 있습니다. AWS SES를 쓴다면 다음과 같이 SESClient(혹은 SESClient는 자식인 SES)를 import해서 사용하면 됩니다.
// ES6 스타일
import { SESClient, CloneReceiptRuleSetCommand } from "@aws-sdk/client-ses";
const client = new SESClient({ region: "REGION" });
const params = {
// 생략
};
const command = new CloneReceiptRuleSetCommand(params);
// simple 메일 보내기
try {
const data = await client.send(command);
console.log({data})
} catch (err) {
console.error({err})
}
4. 필요시 Sandbox 탈출 신청서를 AWS에 제출한다
기본적으로 SES를 활성화시키면 Sandbox 환경에 배치되고 다음과 같은 제약이 생깁니다.
메일 전송을 아무곳에나 못 보내고 초당 1개 메시지, 하루 최대 200개 제한이 있습니다.
프로덕션용으로 업그레이드 신청을 AWS에 보내고 심사를 받아야 합니다.
SES를 고객들 회원 인증하는데 쓸 것이다~~ 길게 적어 보내고 하루 정도 기다리니깐 승인 됐습니다.
* 처리되길 기다리는 동안 Sandbox라도 테스트하는 데에는 문제 없습니다.
이제 코드를 작성해봅시다.
5. 개발 환경에 발급 받은 Access Key로 Credential 설정
1.번째 단계에서 발급 받은 AWS Access ID와 Access Secret을 앱에서 인식할 수 있게 설정해야 합니다.
방법은 여러가지가 있습니다. 원하는 방법을 선택해서 사용하세용.
첫 번째 방법 :: 환경 변수를 사용하자
PaaS를 쓴다면 Paas 설정창에서 환경변수를 넣어주고 로컬에서는 다음과 같이 설정해주면 됩니다.
# 사용하는 환경에 설정.
# bash를 쓰면 .bashrc, zsh를 쓰면 .zshrc에 넣어도 ㄱㅊ
export AWS_ACCESS_KEY_ID=SES계정의_ACCESS_KEY_ID
export AWS_SECRET_ACCESS_KEY=SES계정의_SECRET_ACCESS_KEY
SDK는 먼저 환경 변수를 체크한다고 합니다. 다른 환경에 배포할 때도 환경변수만 세팅해주면 돼서 편하고 파일로 공유하다가 누출되는 사고도 어느정도 방지할 수 있습니다.
두 번째 방법 :: AWS Credential 정보를 ~/.aws/credentials에 저장하자
로컬 환경에서 AWS CLI를 사용한한 적이 있다면 이미 파일이 존재할 수도 있습니다.
세팅하는 방법은 쉽습니다. 여기서 시키는 대로 하면 됩니다.
https://docs.aws.amazon.com/ses/latest/dg/create-shared-credentials-file.html
AWS를 하나의 계정만 쓰는 것이 아닐 수도 있어서 별로라는 생각이 들 수도 있데 (위의 문서에는 안 나와있지만) credentials 파일을 아래와 같이 구성하면 여러 프로필을 쓸 수 있습니다.
만약 AWS S3용으로 다른 계정 AWS SES용으로 따로 Credential이 필요한 경우 다음과 같이 파일을 설정하고 클라이언트를 만들 때 구체적인 profile을 주거나 AWS_PROFILE 환경 변수로 원하는 프로필을 설정해서 쓰면 됩니다. (기본이 default입니다.)
[default] ; AWS CLI 등 기본으로 쓸 계정의 프로필
aws_access_key_id = <기본으로_쓸_계정의_ACCESS_KEY_ID>
aws_secret_access_key = <기본으로_쓸_계정의_SECRET_ACCESS_KEY>
[ses] ; ; AWS SES용 계정 프로필
aws_access_key_id = <SES용으로_쓸_계정의_ACCESS_KEY_ID>
aws_secret_access_key = <SES계정의_SECRET_ACCESS_KEY>
[s3] ; AWS S3용 계정 프로필
aws_access_key_id = <S3용으로_쓸_계정의_ACCESS_KEY_ID>
aws_secret_access_key = <S3용으로_쓸_계정의_SECRET_ACCESS_KEY>
세 번째 방법 :: @aws-sdk/credential-provider-node를 사용하자
직접 팩터리를 호출해서 원하는 형태로 만들고 주입해서 쓸 수 있습니다.
ENV, SSO, OICD Token, Shared Credential, AWS의 Metadata 서비스, Credential의 파일을 불러오는 경로를 바꾸는 등 많은 옵션을 제공해줍니다.
AWS API V3부터는 @aws-sdk/credential-provider-node를 클라이언트 생성자에 직접 주입하도록 바뀌었습니다.
원한다면 깔아서 defaultProvider로 주입해주면 됩니다.
yarn add @aws-sdk/credential-provider-node
import { defaultProvider } from "@aws-sdk/credential-providers";
export class EmailService {
private sesClient: SESClient;
constructor() {
this.sesClient = new SESClient({
region: 'ap-northeast-2',
credentialDefaultProvider: defaultProvider
});
}
//...
}
6. 메일 보내는 코드 작성
NestJS에서 서비스를 만든 후 간단하게 Text로 보내는 코드를 작성해보았습니다.
SendEmailCommand의 input 객체를 적절하게 구성해줍니다.
- Destination.ToAddresses: ["메일을 받을 사람의 이메일 주소 1", "메일을 받을 사람의 이메일 주소 2", ...]
- 목적지 이메일 주소
- 만약 Sandbox 환경이라면 Verified된 identity나 SES 메일박스 시뮬레이터로만 보낼 수 있습니다.
- Source
- 보내는 사람의 주소
- AWS SES 상에서 Verfied된 Indentity만 사용할 수 있습니다
- Message.Body.Text
- 이메일 본문 내용
- Message.Subject
- 이메일 제목
import { Injectable } from '@nestjs/common';
import { SendEmailCommand, SESClient } from '@aws-sdk/client-ses';
@Injectable()
export class EmailService {
private sesClient: SESClient;
constructor() {
this.sesClient = new SESClient({
region: 'ap-northeast-2', // AWS Region 서울로 설정
});
}
private async sendSimpleMail() {
const command = new SendEmailCommand({
Destination: {
//목적지
CcAddresses: [],
ToAddresses: ['테스트로_보낼_이메일주소@gmail.com'], // 받을 사람의 이메일
},
Message: {
Body: { // 이메일 본문 내용
Text: {
Charset: 'UTF-8',
Data: '메일이 보내지는지 테스트중입니다.',
},
},
Subject: { // 이메일 제목
Charset: 'UTF-8',
Data: '이메일 테스트',
},
},
Source: 'no-reply@mail.modoing.net', // 보내는 사람의 이메일 - 무조건 Verfied된 identity여야 함
ReplyToAddresses: [],
});
try {
const response = await this.sesClient.send(command);
console.log("메일 전송 완료\n", response.$metadata);
} catch (error) {
console.log(error);
}
}
}
호출해봅시다!
NestJS의 신기능인 REPL로 직접 sendSimpleEmail 함수를 호출해보았습니다. ㅎㅎ
Postman같은 걸로 호출할 필요 없이 바로 터미널로 호출할 수 있어서 편합니다.
메일이 왔습니당.
HTML로 본문을 보내는 코드를 작성하고 호출해봅시다.
import { Injectable } from '@nestjs/common';
import { SendEmailCommand, SESClient } from '@aws-sdk/client-ses';
@Injectable()
export class EmailService {
private sesClient: SESClient;
constructor() {
this.sesClient = new SESClient({
region: 'ap-northeast-2', // AWS Region 서울로 설정
});
}
private async sendSimpleMail() {
const command = new SendEmailCommand({
Destination: {
//목적지
CcAddresses: [],
ToAddresses: ['테스트로_보낼_이메일주소@gmail.com'], // 받을 사람의 이메일
},
Message: {
Body: {
Html: { // 본문을 HTML로도 보낼 수 있다.
Charset: 'UTF-8',
Data:
'<h1>구글로 이동 가능?</h1>' +
'<a href="google.com">구글 Link</a>',
},
},
Subject: {
Charset: 'UTF-8',
Data: 'HTML 되는지 테스트',
},
},
Source: 'no-reply@mail.modoing.net', // 보내는 사람의 이메일 - 무조건 Verfied된 identity여야 함
ReplyToAddresses: [],
});
try {
const response = await this.sesClient.send(command);
console.log("메일 전송 완료\n", response.$metadata);
} catch (error) {
console.log(error);
}
}
}
HTML로 보낸 본문도 잘 보내졌습니다. 이처럼 HTML 태그가 인식되어서 링크로 보이는 것을 확인할 수 있습니다. 실제로 눌렀을 때 구글로 잘 이동했습니다.
스팸으로 분류되지 않도록 주의합시다!
스팸으로 분류되지 않도록 DKIM 인증을 받았는지 확인합니다.
잘 포맷팅된 HTML이랑 텍스트를 같이 제공해줍니다.
본문에 전송하는데 쓰는 도메인이 아닌 도메인 링크가 포함되면 스팸으로 분류되기도 합니다ㅠㅠ. 주의하세요.
끝!
댓글
이 글 공유하기
다른 글
-
[AWS] Route53 Domain 다른 AWS 계정으로 이전하기
[AWS] Route53 Domain 다른 AWS 계정으로 이전하기
2022.11.20 -
[AWS] Elastic Beanstalk graceful shutdown (feat. AutoScaling Lifecycle Hook - TERMINATING)
[AWS] Elastic Beanstalk graceful shutdown (feat. AutoScaling Lifecycle Hook - TERMINATING)
2022.11.10 -
[AWS] VPC의 NAT 비용을 줄여보자 :: Ubuntu로 NAT 인스턴스 만들기
[AWS] VPC의 NAT 비용을 줄여보자 :: Ubuntu로 NAT 인스턴스 만들기
2022.09.25 -
[AWS] S3를 통해 정적인 Asset 호스팅하기
[AWS] S3를 통해 정적인 Asset 호스팅하기
2022.08.22