Architecture · AWS Serverless

주간 뉴스레터 파이프라인

Claude Code로 기사를 만들어 S3에 올리면, 매주 정해진 시각에 SES가 구독자 전원에게 보낸다. 서버는 한 대도 켜두지 않는다.

전체 아키텍처

내 컴퓨터 · Claude AWS (서울 리전) 만들기 모으기 보내기 지키기 Claude 앱 (remote) 생각날 때 화제·요약 던지기 Claude Code 한 주 모아 기사 HTML 빌드 S3 · issues/ 2026-09-21.html s3 cp 방문자 이메일 입력 후 구독 CloudFront + S3 정적 구독 페이지 (mydomain.com) API Gateway + Lambda 구독 · 확인 · 수신거부 API POST /subscribe EventBridge Scheduler cron: 매주 월 09:00 Lambda · 발송 이번 주 HTML + 구독자 → 치환 → 발송 SES [email protected] DynamoDB subscribers 테이블 읽기 활성 구독자 목록 구독자 수신함 수신거부 링크 → API로 SNS 바운스 · 스팸신고 알림 Lambda · 정리 해당 주소 비활성 처리 Route 53 + ACM 도메인 · DNS · SSL · DKIM CloudWatch 결제 알람 · 발송 실패 알람
내가 직접 하는 부분 AWS에서 자동으로 도는 부분 점선 상자는 전체를 받쳐주는 공통 서비스

세 가지 흐름

만들기 (주중 수시 + 주말 1회)

  1. 평일에 Claude 앱으로 화제를 던지고 요약을 받아 notes/2026-W38/에 쌓는다.
  2. 주말에 Claude Code에게 "이번 주 노트로 기사 만들어" → 템플릿에 맞춘 이메일용 HTML 생성.
  3. -test 파일로 내 주소에 먼저 발송해 Gmail·네이버·아이폰에서 확인.
  4. 확정본을 s3://…/issues/2026-09-21.html로 업로드. 여기까지가 내 일.

모으기 (24시간 자동)

  1. 방문자가 mydomain.com에서 이메일 입력.
  2. Lambda가 DynamoDB에 pending 상태로 저장하고 SES로 확인 메일 발송.
  3. 확인 링크 클릭 → active로 전환 (더블 옵트인).
  4. 수신거부 링크 클릭 → unsubscribed로 전환. 같은 Lambda가 처리.

보내기 (매주 월 09:00 자동)

  1. EventBridge가 발송 Lambda를 깨운다.
  2. issues/에서 오늘 날짜 파일을 찾는다. 없으면 나에게 알림 후 종료.
  3. DynamoDB에서 active 구독자를 읽고, 구독자별 수신거부 URL을 치환.
  4. SES로 발송 후 sent#2026-09-21 마커를 기록해 중복 발송을 막는다.

구성 요소와 비용

역할서비스왜 이걸로월 비용 (구독자 1만 명 기준)
구독 페이지S3 + CloudFrontGitHub Pages와 같은 정적 호스팅. 1TB 전송·1천만 요청 상시 무료$0
구독·수신거부 APIAPI Gateway (HTTP) + Lambda요청 있을 때만 실행. 월 100만 요청 무료$0
구독자 저장DynamoDB (on-demand)25GB 상시 무료. 서버 없음, 관리 없음. RDS는 유휴 비용이 나와 부적합$0
기사 보관S3 (issues/)HTML 파일 수백 개여도 GB 미만$0
주간 트리거EventBridge Schedulercron 한 줄$0
발송SES1,000통당 $0.10. 43,333통/월$4.33
바운스·신고 처리SNS + LambdaSES 계정 정지를 막는 필수 장치$0
도메인·DNS·SSLRoute 53 + ACM호스팅 존 $0.50/월. 인증서 무료. 도메인 등록비 연 $13~15 별도$0.50
알람CloudWatch10개 알람 무료$0
합계구독자 1,000명이면 $1 미만, 5만 명이면 약 $25≈ $5 + 도메인 연 $15

신규 계정은 6개월간 최대 $200 크레딧을 받으므로 첫 반년은 실질 0원에 가깝습니다. 2023년 이전의 "SES 월 6.2만 통 무료"는 폐지되었으니 옛 글은 참고만 하세요.

비용 계산기

구독자 수와 트래픽을 바꿔 보면서 통당·방문당 비용이 어떻게 비례하는지 확인할 수 있습니다. SES는 HTML 본문 크기와 무관하게 통 수로만 과금되고, 본문 안 이미지는 구독자가 메일을 열 때 CloudFront 트래픽으로 잡힙니다.

월 총비용—
월 발송 통수—
1통당 실질 비용—
구독자 1명당 월—
항목산정 근거월 비용

월 트래픽: 이메일 이미지 — + 홈페이지 — = — · 요청 —

같은 조건에서 구독자 규모별 비교

구독자월 발송 통수월 총비용 ($)월 총비용 (원)1통당구독자 1명당 월

단가는 2026년 9월 서울 리전 공개 요금 기준: SES $0.10/천 통 · CloudFront $0.12/GB, $0.012/만 요청 · Lambda $0.20/백만 요청 + $0.0000167/GB-초 · API Gateway $1.20/백만 요청 · DynamoDB 쓰기 $1.43/백만, 읽기 $0.29/백만 · CloudWatch Logs $0.76/GB. 발송 Lambda는 통당 60ms·512MB로 가정. 12개월 한정 무료는 제외했고, DynamoDB 온디맨드 무료 요청 구간은 계정 조건에 따라 다를 수 있습니다.

도메인은 하나면 된다

홈페이지와 이메일 주소에 도메인을 각각 사야 한다고 생각하셨는데, 도메인 하나로 둘 다 처리됩니다. 같은 Route 53 호스팅 존에 레코드만 나눠 등록하면 됩니다.

mydomain.com → CloudFront (홈페이지)
api.mydomain.com → API Gateway (구독 API)
mail.mydomain.com → SES 발송 도메인 ([email protected])

발송은 서브도메인으로 분리해 두는 걸 권합니다. 만약 스팸 신고가 쌓여 발송 도메인 평판이 나빠져도 홈페이지 도메인은 영향을 받지 않습니다. 회신 받을 곳은 Reply-To에 기존 Gmail을 넣으면 비용 0입니다.

데이터 모델

이메일 주소만 저장한다고 하셨지만, 상태와 토큰 두 가지는 함께 있어야 수신거부·더블 옵트인·바운스 처리가 됩니다. 테이블은 하나로 충분합니다.

테이블: newsletter   (파티션 키 pk, 정렬 키 sk)

pk                       sk         속성
sub#[email protected]      meta       status=active | pending | unsubscribed | bounced
                                    token=8f3a…(수신거부·확인용 랜덤 값)
                                    created_at, confirmed_at
sent#2026-09-21          meta       count=10432, finished_at   ← 중복 발송 방지 마커

GSI: status-index (status) → 발송 Lambda가 active만 빠르게 조회

말씀하신 것 외에 꼭 필요한 것들

SES 샌드박스 해제

신규 계정은 인증된 주소로만 발송됩니다. 콘솔에서 프로덕션 전환을 요청하면 1~3 영업일 소요. 용도와 수신거부 처리 방식을 적어 제출하세요.

SPF · DKIM · DMARC

SES가 알려주는 DNS 레코드를 Route 53에 등록. 이게 없으면 Gmail·네이버가 스팸함으로 보냅니다.

더블 옵트인

확인 메일을 거쳐야 active가 되게 합니다. 오타 주소·봇 가입으로 바운스율이 오르는 걸 막습니다.

수신거부 링크와 발신자 표기

모든 메일 하단에 원클릭 수신거부 링크, 발신자 이름·주소·연락처를 넣습니다. 정보통신망법 요건이자 SES 이용 조건입니다. List-Unsubscribe 헤더도 함께.

바운스·신고 자동 처리

SES → SNS → Lambda로 해당 주소를 즉시 비활성화. 바운스율 5%, 신고율 0.1%를 넘으면 SES가 발송을 정지시킵니다.

개인정보처리방침 페이지

이메일을 수집하는 순간 필요합니다. 수집 항목(이메일), 목적(뉴스레터 발송), 보관 기간, 위탁(AWS) 정도를 적은 정적 페이지 하나.

테스트 발송 경로

실제 구독자에게 나가기 전에 내 주소로만 보내는 경로. 발송 Lambda에 dry_run 인자 하나로 해결됩니다.

결제 알람

CloudWatch Billing Alarm을 $5에 하나 걸어두세요. 실수로 무한 루프가 돌아도 메일로 먼저 알게 됩니다.

이메일 HTML 규칙

폭 600px, 테이블 레이아웃, CSS 인라인, 이미지는 첨부 대신 S3/CloudFront URL. MJML로 쓰고 컴파일하면 이 규칙이 자동으로 지켜집니다.

발송 속도와 분할

SES 기본 한도는 초당 14통 안팎. 구독자 5만 명을 넘으면 Lambda 15분 제한에 걸리므로 그때 SQS 큐를 앞에 두고 나눠 보냅니다. 처음엔 필요 없습니다.

Claude Code 쪽 작업 구조

로컬 저장소 하나에 노트·템플릿·발행물을 함께 두고, Claude Code가 빌드와 업로드까지 맡게 하면 매주 명령 한 줄로 끝납니다.

newsletter/
├── notes/
│   └── 2026-W38/            ← 주중에 Claude 앱으로 받은 요약을 여기에 저장
│       ├── mon-ai-chips.md
│       └── wed-fed-rate.md
├── templates/
│   └── issue.mjml           ← 헤더·푸터·{{unsubscribe_url}} 자리 고정
├── dist/
│   └── 2026-09-21.html      ← 빌드 결과물
├── scripts/
│   ├── build.sh             ← mjml 컴파일 + 인라인 CSS 검증
│   └── publish.sh           ← aws s3 cp dist/2026-09-21.html s3://…/issues/
└── CLAUDE.md                ← "notes/현재주차를 읽어 issue.mjml로 기사 작성, 톤·길이 규칙…"

매주 하는 일:  claude → "이번 주 기사 만들어서 테스트 발송해줘" → 확인 → "발행해"

Claude 앱(remote)에서 던진 요약은 Claude Code가 접근할 수 있는 곳에 남아야 합니다. 가장 단순한 방법은 노트를 GitHub 저장소에 커밋하는 것이고, 그러면 git push에 GitHub Actions를 붙여 S3 업로드까지 자동화할 수도 있습니다.

구축 순서

  1. 도메인 등록 + Route 53 호스팅 존이후 모든 단계가 DNS에 의존하므로 가장 먼저. 전파에 시간이 걸립니다.
  2. SES 도메인 인증, 샌드박스 해제 요청승인 대기 1~3일 동안 나머지를 만듭니다. DKIM·SPF·DMARC 레코드 등록.
  3. DynamoDB 테이블 + 구독·확인·수신거부 LambdaAPI Gateway로 노출하고 api.mydomain.com 연결. curl로 먼저 검증.
  4. 정적 구독 페이지 + 개인정보처리방침S3에 올리고 CloudFront에 mydomain.com 연결. 폼이 API를 호출하는지 확인.
  5. 발송 Lambda + EventBridge 스케줄내 주소만 있는 상태에서 dry_run으로 한 번, 실제 스케줄로 한 번.
  6. SNS 바운스 처리 + CloudWatch 알람실제 구독자를 받기 전에 반드시. 빠뜨리면 첫 바운스부터 평판이 깎입니다.
  7. Claude Code 저장소와 CLAUDE.md 정비템플릿·빌드·업로드 스크립트를 넣고 첫 호를 실제로 만들어 본다.
  8. 첫 발행지인 10~20명으로 시작해 도달함·렌더링을 확인한 뒤 공개 모집.