Skip to content

SES 이메일 전송 결과 실시간 반영 (SNS/SQS 이벤트 처리) #97

@jhan0121

Description

@jhan0121

✨ 구현 할 기능

현재 SES API 호출 성공 시점을 SENT로 처리하고 있어, 실제 전달 실패(soft bounce, hard bounce, complaint 등)를
시스템이 인지하지 못하는 문제가 있음.

SES → SNS → SQS 경로를 통해 실제 이메일 전달 결과 이벤트를 수신하고, notification_history 상태를 정확하게 반영하는 구조로 개선.

📢 구현 방식

SES → SNS → SQS → Spring Boot 폴링 방식 고려

SES ──(Delivery/Bounce/Complaint 이벤트)──> SNS Topic
                                               └──> SQS Queue <── Spring Boot (@Scheduled 폴링)
                                                        └──> DLQ (처리 실패 시)

방법 선택 근거: 기존 @Scheduled 패턴과 일관성 유지, Spring 서비스 레이어 재사용,
앱 다운 시 SQS 메시지 보관으로 이벤트 유실 방지.

상태 모델 확장 (PENDING/SENT/FAILED → 5가지)

상태 의미
PENDING SES 미접수 (초기 생성)
ACCEPTED SES API 호출 성공, Delivery 이벤트 대기 중
SENT SES Delivery 이벤트 수신 확인
FAILED Soft bounce / ACCEPTED 타임아웃 (재시도 대상)
BLOCKED Hard bounce / Complaint (재시도 차단)

📑 기능 명세

인프라

  • SES Configuration Set 생성 및 이벤트 대상(SNS) 연결
  • SNS 토픽 생성
  • SQS 큐(메인) + DLQ 생성, SNS 구독 설정
  • EC2 IAM Role에 SQS 수신 권한 추가

DB

  • notification_history.status ENUM에 ACCEPTED, BLOCKED 추가
  • notification_history.ses_message_id 컬럼 추가 (이벤트 매핑용)
  • ses_message_id 인덱스 추가

애플리케이션

  • NotificationStatus enum에 ACCEPTED, BLOCKED 추가
  • NotificationHistory 엔티티에 sesMessageId 필드 추가
  • EmailSender.send() 반환 타입 변경 (voidString, messageId 반환)
  • EmailSenderSendEmailRequest에 Configuration Set 이름 추가
  • SingleReviewEmailSender — 발송 성공 시 SENTACCEPTED + messageId 저장으로 변경
  • NotificationHistoryRepositoryupdateStatusAndSesMessageId(), findAllBySesMessageId(), updateStatusWhereDeadlineExpired() 쿼리 추가
  • AwsSqsConfigSqsClient 빈 등록
  • SesEventPoller@Scheduled SQS 폴링, 처리 성공 시 deleteMessage()
  • SesEventProcessingService — Delivery/Bounce/Complaint 이벤트 분기 처리
  • AcceptedTimeoutService — deadline 초과 ACCEPTEDFAILED 전환 스케줄러
  • build.gradlesoftware.amazon.awssdk:sqs 의존성 추가

테스트

  • SesEventProcessingServiceTest — 이벤트별 상태 변경 검증 (Delivery/Bounce hard&soft/Complaint)
  • SingleReviewEmailSenderTest — 발송 성공 시 ACCEPTED 상태 검증으로 수정

📕 래퍼런스

Metadata

Metadata

Assignees

Labels

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions