홈시리즈멘토링

© 2026 정기창. All rights reserved.

본 블로그의 콘텐츠는 CC BY-NC-SA 4.0 라이선스를 따릅니다.

☕후원하기소개JSON Formatter러닝 대기질개인정보처리방침이용약관

© 2026 정기창. All rights reserved.

콘텐츠: CC BY-NC-SA 4.0

☕후원하기
소개|JSON Formatter|러닝 대기질|개인정보처리방침|이용약관

Amazon SES 는 모든 리전에 있지 않습니다 — 샌드박스도 DKIM 도 발송 리전을 따라갑니다

정기창·2026년 9월 13일

저는 Amazon SES 를 어느 리전에서나 쓸 수 있는 서비스로 여기고 있었습니다. 그런데 SES 엔드포인트 표에는 없는 리전이 있습니다. 앱을 둔 리전이 거기 없으면 다른 리전의 SES 를 불러야 하고, 그때부터 샌드박스·프로덕션 액세스·DKIM·반송 처리가 전부 발송 리전을 기준으로 따로 움직입니다.

트랜잭션 메일을 붙이기 전에 표를 확인하다가 알게 된 일입니다. 아래의 동작 서술은 2026-09-11 에 연 AWS 공식 문서를 근거로 했고, 확인하지 못한 부분은 그렇다고 적었습니다. 발송 리전의 예는 서울(ap-northeast-2)로 적습니다.

SES 엔드포인트 표에 없는 리전이 있습니다

SES 는 리전마다 email.<리전 코드>.amazonaws.com 형태의 API 엔드포인트를 둡니다. 2026-09-11 기준 엔드포인트 표에서 아시아 태평양의 SES 리전은 하이데라바드·자카르타·말레이시아·뭄바이·오사카·서울·싱가포르·시드니·도쿄입니다. 목록은 늘어나기도 합니다. 2025년 11월에는 말레이시아와 캘거리가 추가됐습니다.

앱 리전이 이 목록에 없다면 SDK 나 CLI 에 그 리전을 넣은 순간부터 호출이 성립하지 않습니다. 확인은 명령 하나로 됩니다.

# 확인하려는 리전 코드를 넣습니다
REGION=ap-northeast-2
aws sesv2 get-account --region "$REGION" \
  --query '{production:ProductionAccessEnabled, sending:SendingEnabled, quota:SendQuota}'

엔드포인트가 없는 리전을 넣으면 명령은 연결 단계에서 Could not connect to the endpoint URL 로 끝납니다. botocore 의 이 연결 오류(EndpointConnectionError) 메시지에 담기는 것은 접속하려던 URL 하나뿐입니다. 오류는 «연결하지 못했다»고 말할 뿐 «이 리전에는 SES 가 없다»고 말하지 않으니, 판정은 엔드포인트 표로 하는 편이 확실합니다. 호출이 성공하면 ProductionAccessEnabled 가 그 리전의 샌드박스 여부(false 면 샌드박스)를, SendQuota 가 그 리전의 한도를 알려 줍니다.

다른 리전에서 보내기로 하면 설정이 그 리전으로 모입니다

네트워크 쪽은 단순합니다. SES API 는 리전별 공개 HTTPS 엔드포인트라, 앱 서버가 인터넷으로 나갈 수 있으면 VPC 피어링 같은 리전 간 연결이 필요 없습니다. 인터넷 출구 없이 VPC 엔드포인트로만 나가는 서브넷이라면 다릅니다. 다른 리전 서비스에 인터페이스 엔드포인트로 닿는 교차 리전 PrivateLink 의 지원 목록에 2026-09-11 기준 SES 는 없었습니다.

코드에서는 SES 를 부르는 객체에만 리전을 명시합니다. SDK 는 객체에 리전을 주지 않으면 환경의 기본 리전(AWS_REGION 등)을 쓰고, 객체에 직접 준 리전이 우선합니다. 까다로운 것은 이 한 줄이 아니라, 발송에 필요한 상태 대부분이 리전 단위로 나뉜다는 점입니다.

항목 발송 리전 기준으로 해야 하는 일
샌드박스·발송 한도 리전마다 상태와 한도가 다르고, 프로덕션 액세스도 발송 리전에서 받습니다
도메인·주소 검증 다른 리전에서 검증했어도 발송 리전에서 다시 검증해야 씁니다
Easy DKIM 발송 리전에서 생성한 CNAME 을 DNS 에 넣습니다
커스텀 MAIL FROM MX 레코드 값이 그 리전의 피드백 엔드포인트입니다
계정 수준 억제 목록 현재 리전에만 적용됩니다
피드백 알림용 SNS 토픽 SES 를 쓰는 리전과 같은 리전에 있어야 합니다
IAM 정책의 아이덴티티 ARN arn:aws:ses:ap-northeast-2:123456789012:identity/example.com 처럼 리전 칸이 있습니다

표를 채우고 보니 SES 에서 리전은 호출 주소 이상이었습니다. 샌드박스 여부, 검증된 아이덴티티, 억제 목록 같은 계정 상태가 리전 단위로 쌓입니다.

샌드박스는 리전마다 따로입니다

새 계정은 SES 샌드박스에서 시작하고, 샌드박스 상태는 리전마다 따로입니다. 한 리전에서 프로덕션 액세스를 받았어도 다른 리전은 여전히 샌드박스일 수 있습니다. 샌드박스에서는 24시간에 200통, 초당 1통까지 보낼 수 있고, 수신자는 검증된 이메일 주소나 도메인, 또는 SES 메일박스 시뮬레이터여야 합니다.

트랜잭션 메일에서 이 제약이 무거운 이유는 수신자에 있습니다. 회원가입 인증 메일은 아직 가입하지 않은 사람에게 가고, 가입하려는 사람마다 주소를 먼저 SES 에 검증받게 할 수는 없습니다. 샌드박스에서 가입 인증은 구조적으로 성립하지 않는데, 검증해 둔 제 주소와 메일박스 시뮬레이터로만 시험하는 동안에는 이 제약이 드러날 계기가 없습니다. 프로덕션 액세스를 받은 뒤에도 From·Source·Sender·Return-Path 에 쓰는 아이덴티티는 여전히 발송 리전에서 검증돼 있어야 합니다.

SES 프로덕션 액세스는 코드보다 먼저 거는 일입니다

그래서 샌드박스를 해제하는 프로덕션 액세스는 코드를 다 짠 뒤가 아니라 발송 리전을 정한 직후에 요청하는 편이 맞습니다. AWS Support 팀의 첫 응답은 24시간 안이라고 적혀 있지만, 추가 정보가 필요하면 더 걸릴 수 있다고 함께 적혀 있습니다. 코드로는 줄일 수 없는 리드타임입니다.

SES 콘솔의 요청 양식이 묻는 것은 메일 유형(마케팅 또는 트랜잭션), 웹사이트 URL, 추가 연락 주소(최대 4개), 연락 언어(영어 또는 일본어), 그리고 동의 체크박스 하나입니다. 체크박스는 명시적으로 요청한 사람에게만 보내고 반송·불만 알림을 처리하는 절차가 있다는 확인입니다. API 의 사용 사례 설명(UseCaseDescription) 파라미터에는 폐기(deprecated) 표시가 붙어 있습니다. 아래 CLI 는 실행하면 실제로 요청이 제출됩니다.

aws sesv2 put-account-details \
  --region ap-northeast-2 \
  --production-access-enabled \
  --mail-type TRANSACTIONAL \
  --website-url https://example.com \
  --additional-contact-email-addresses info@example.com \
  --contact-language EN

제출한 뒤에는 검토가 끝날 때까지 내용을 고칠 수 없고, 진행 중에 PutAccountDetails 를 다시 부르면 ConflictException(409)이 돌아옵니다. 영구히 고칠 수 없다는 문구는 없지만, 거절된 뒤의 재요청 경로도 공식 문서에서는 찾지 못했습니다. 거절 후 CLI 로 다시 제출했더니 같은 예외가 났다는 사용자 보고가 있어, 케이스에 회신하는 쪽이 실제 경로일 가능성이 있다고만 적어 둡니다. 웹사이트 URL 과 연락처는 넣기 전에 확정해 두는 편이 낫습니다.

DKIM 만 맞추면 DMARC 는 통과합니다 — 대량 발송자가 되기 전까지는

SES 의 Easy DKIM 은 DNS 에 넣을 CNAME 레코드 3개로 도메인을 검증합니다. 키는 기본 RSA 2048비트(콘솔에서 1024비트 선택 가능)이고, 키 길이 전환은 24시간에 한 번으로 제한됩니다. 서명 호스팅 영역이 리전마다 다를 수 있어서, 레코드는 발송 리전 API 가 돌려준 SigningHostedZone 으로 만들라고 문서는 적습니다.

다음 질문은 DKIM 만으로 충분한가입니다. SES 는 따로 설정하지 않으면 amazonses.com 하위 도메인을 MAIL FROM 으로 쓰고, 이때 SPF 는 통과하지만 From 주소의 도메인과 정렬되지 않습니다. 그래서 DMARC 를 통과시키는 것은 From 도메인으로 서명한 DKIM 쪽입니다. DKIM 정렬은 relaxed 가 기본이라 From 이 하위 도메인이어도 상위 도메인으로 서명하면 정렬되고, SPF 까지 정렬하려면 From 도메인에 맞춘 커스텀 MAIL FROM 이 필요합니다.

Gmail 의 발송자 요건을 옮겨 보면 어디서 갈리는지가 보입니다.

구분 Gmail 요건
모든 발송자 SPF 또는 DKIM, 유효한 정·역방향 DNS, TLS, 스팸률 0.3% 미만, RFC 5322 형식. DMARC 레코드는 필요 없음
대량 발송자 (개인 Gmail 계정에 24시간 약 5,000통 이상, 한 번 분류되면 해제되지 않음) SPF 와 DKIM 둘 다, DMARC 레코드(p=none 허용), From 도메인이 SPF 또는 DKIM 도메인과 정렬, 마케팅·구독 메일의 원클릭 수신거부, 스팸률 0.3% 미만

정리하면 DMARC 레코드가 없어도 소량 발송은 요건을 채우지만 대량 발송은 채우지 못하고, 대량 발송자 분류는 한 번 정해지면 풀리지 않습니다. 하나는 확인하지 못했습니다. Gmail 안내문은 대량 발송자 요건을 «발송 도메인에 대한» SPF 와 DKIM 으로 적는데, 기본 MAIL FROM 의 SPF 는 amazonses.com 하위 도메인에 대한 것이라 여기에 해당하는지는 이번 근거로 단정하지 못했습니다.

발송 리전에서 도메인 상태는 이렇게 한 번에 봅니다. 뒤의 두 필드는 다음 절에서 씁니다.

aws sesv2 get-email-identity --region ap-northeast-2 \
  --email-identity example.com \
  --query '{verified:VerifiedForSendingStatus, dkim:DkimAttributes.Status, mailFrom:MailFromAttributes.MailFromDomainStatus, forwarding:FeedbackForwardingStatus, configSet:ConfigurationSetName}'

반송은 조용하지 않지만 앱은 모릅니다

반송 처리에서는 제가 두 번 틀릴 뻔했습니다. 처음에는 «억제 목록이 있으니 반송은 알아서 처리된다»고 생각했습니다. 2019년 11월 25일 이후 SES 를 쓰기 시작한 계정은 계정 수준 억제 목록이 반송과 불만 모두에 대해 기본으로 켜져 있고, 목록에 오른 주소로 보내면 SES 는 메시지를 받아들이지만 보내지 않습니다. 앱이 받는 것은 수락 응답뿐이고, 억제 목록은 발송을 막을 뿐 그 사실을 이벤트로 전달하지 않습니다.

목록이 모든 경우를 덮는 것도 아닙니다. 억제 목록에 들어가는 반송은 하드 바운스뿐이고, Gmail 은 SES 에 불만 데이터를 주지 않아서 Gmail 웹에서 스팸 신고를 눌러도 그 주소는 목록에 오르지 않습니다.

반대로 «구성 세트를 만들지 않으면 반송을 전혀 모른다»도 틀렸습니다. 이메일 피드백 포워딩이 기본으로 켜져 있어서, 아무것도 설정하지 않아도 반송·불만 알림이 원본 메일의 Return-Path 주소(없으면 Source 주소)로 이메일로 갑니다. 다른 알림 수단이 없으면 포워딩을 꺼 두어도 갑니다.

반송은 조용하지 않습니다. 다만 그 메일함을 누가 읽는지 정해 두지 않았다면 알림은 아무도 읽지 않는 곳으로 갑니다.

앱이 개별 반송·불만을 기계적으로 받으려면 아이덴티티별 SNS 피드백 알림이나 구성 세트의 이벤트 게시(CloudWatch·Firehose·Pinpoint·SNS·EventBridge)를 설정해야 합니다. 이벤트 게시만 쓴다면 구성 세트를 모든 발송에 적용해야 하고, 적용하지 않은 메일의 알림은 이메일 포워딩으로 돌아갑니다. 앞의 확인 명령에 아이덴티티의 기본 구성 세트(ConfigurationSetName)를 넣은 이유입니다. 프로덕션 액세스 체크박스가 묻던 «절차»가 결국 이 자리입니다.

남는 것 — 이유를 말하지 않는 실패들

정리하고 나니 이 글의 함정들은 모양이 닮았습니다. 엔드포인트가 없는 리전은 원인을 담지 않은 연결 오류로 끝나고, 억제된 주소로 보낸 메일은 수락된 채 보내지지 않으며, 반송 알림은 아무도 정하지 않은 메일함으로 갑니다. 셋 다 오류가 나지 않거나, 나더라도 이유를 말하지 않습니다. 그래서 확인은 오류를 기다리는 대신 목록으로 하는 편이 맞다는 생각이 들었습니다.

  • 앱 리전이 설계하는 날의 SES 엔드포인트 표에 있는가
  • SES 를 부르는 코드에 발송 리전을 명시했는가
  • 발송 리전에서 도메인 검증·Easy DKIM·DMARC 레코드를 마쳤는가 (SPF 정렬까지는 커스텀 MAIL FROM)
  • 프로덕션 액세스를 발송 리전에서, 코드보다 먼저 요청했는가
  • IAM 아이덴티티 ARN 과 피드백 알림용 SNS 토픽이 발송 리전을 가리키는가
  • 반송·불만 알림을 누가, 어떤 경로로 받는가

발송 리전을 나중에 옮기면 이 목록은 새 리전에서 처음부터 다시 시작됩니다. 돌이켜 보면 제가 고른 것은 리전 하나가 아니라, 샌드박스 여부와 검증과 억제 목록을 어디에 쌓아 둘지였습니다.

참고 문서

  • Amazon Simple Email Service endpoints and quotas
  • Regions and Amazon SES
  • Request production access (Moving out of the Amazon SES sandbox)
  • SES API v2 — PutAccountDetails
  • SES API v2 — GetAccount
  • SES API v2 — GetEmailIdentity
  • SES API v2 — SendEmail
  • Creating and verifying identities in Amazon SES
  • Using a custom MAIL FROM domain
  • Using the Amazon SES account-level suppression list
  • Setting up event notifications for Amazon SES
  • Identity and access management in Amazon SES
  • Cross-region enabled AWS services (AWS PrivateLink)
  • AWS SDKs and Tools Reference Guide — AWS Region
  • botocore exceptions.py (EndpointConnectionError)
  • Gmail Email sender guidelines
Amazon SES이메일 발송SES 샌드박스DKIMDMARC트랜잭션 메일반송 처리

관련 글

EC2를 띄우기 전에 AWS CLI로 먼저 잰 것들 — 계획서가 틀려 있던 8가지

Coolify를 EC2에 올리기 전, 읽기 전용 describe만으로 계정을 먼저 쟀습니다. 계획서의 1순위 단계는 불필요했고, 정작 필수였던 것들은 계획서에 아예 없었습니다. 비용 0인 준비만 먼저 끝낸 과정을 정리했습니다.

관련도 91%

와일드카드 인증서는 HTTP-01으로 못 받습니다 — DNS-01이 강제하는 IAM 결정

멀티테넌트 서브도메인을 위해 와일드카드 인증서를 발급하려다 알게 된 것은, 문제가 TLS가 아니라 DNS 쓰기 권한이라는 점이었습니다. 프록시에 Route53 권한을 주는 순간 생기는 폭발 반경과 그것을 좁히는 방법을 정리했습니다.

관련도 91%

SSM 포트포워딩으로 배스천 없이 프라이빗 RDS 에 접속하기 — 세션 권한은 네트워크 권한이었습니다

SSM 포트포워딩은 대상 인스턴스에 인바운드 규칙도, 배스천도 없이 프라이빗 RDS 에 로컬 포트로 붙게 해 줍니다. 다만 IAM 은 목적지 host 를 조건으로 묻지 못해, 세션을 열 권한이 곧 그 인스턴스가 네트워크에서 닿는 모든 곳에 대한 접근이 됩니다.

관련도 90%