매일공부

캠페인 상태 머신: 테스트 없이 실제 메일이 나가지 않도록 만들기 본문

IT/작업기록

캠페인 상태 머신: 테스트 없이 실제 메일이 나가지 않도록 만들기

julian 2026. 8. 31. 14:10

이메일 캠페인 도구 개발기 시리즈 (5/9). 전체 개요

문제

초기 버전은 캠페인을 만들면 바로 pending 상태가 되고, 스케줄러 (scripts/send_scheduled_campaigns.py)가 scheduled_at이 되면 별도 확인 없이 전체 대상자에게 즉시 발송했다. 실수로 오타가 있는 본문, 잘못된 대상 그룹을 그대로 전체 발송해버릴 위험이 있는 구조였다.

해결: 상태 머신에 "테스트 승인" 단계 추가

pending_review (작성/테스트 대기)
      │  테스트 발송(TEST 그룹) 성공
      ▼
   pending (승인, 예약 발송 대기)
      │  scheduled_at 도달 + 스케줄러 실행
      ▼
   sending  →  sent / failed
  • campaigns.status ENUM에 pending_review 추가, 기본값도 pending_review로 변경
  • 테스트 발송 시각 기록용 test_sent_at DATETIME NULL 컬럼 추가
  • 새 캠페인은 INSERT 시점에 pending_review로 생성 → 스케줄러는 status='pending'만 조회하므로, 승인 전까지는 절대 자동 발송 대상이 되지 않는다

테스트 발송

  • 캠페인 상태가 pending_review일 때만 동작
  • subscribers WHERE company_type_id=999(운영 중인 TEST 그룹, 구독 여부 무관 전체)를 수신자로 삼아 실제 발송 함수(models.send_messages)로 즉시 발송
  • 성공 시 campaigns.test_sent_at=NOW() 기록. 실패하면 test_sent_at을 건드리지 않고 오류 메시지를 캠페인 상세 페이지에 표시

승인 — 서버 쪽에서 이중으로 막기

# POST /campaign/<id>/approve
# 상태가 pending_review이고 test_sent_at이 채워져 있을 때만 status='pending'으로 전환

두 조건 중 하나라도 안 맞으면 아무것도 하지 않는다. 승인 버튼 자체도 test_sent_at이 없으면 disabled 처리하지만, URL을 직접 조작해서 테스트를 건너뛰고 승인을 호출해도 서버 쪽 조건 검사에서 막힌다 — 클라이언트 쪽 UI 제약은 서버 검증의 보조 수단일 뿐, 실제 방어는 항상 서버에서 해야 한다는 원칙을 그대로 따른 부분.

수정하면 다시 검토 상태로

승인된(pending) 캠페인이라도 내용을 수정하면 저장 시 무조건 status='pending_review', test_sent_at=NULL로 되돌아가게 강제했다. 그렇지 않으면 "승인 후 내용을 몰래 고쳐도 테스트 없이 그대로 자동 발송되는" 구멍이 생긴다. 반면 실패 후 재시도(campaign_retry)는 내용 변경이 없으므로 재테스트 없이 바로 pending으로 복귀시켰다 — 같은 "다시 pending으로" 동작이라도 내용이 바뀌었는지 여부에 따라 재검토가 필요한지가 갈린다는 게 포인트.

배운 점

  • 자동화된 배치가 있는 시스템에서는 "사람이 확인했다"는 사실 자체를 DB 컬럼 (test_sent_at)으로 남기고, 그 컬럼을 게이트 조건으로 쓰는 게 가장 단순하고 확실하다.
  • UI에서 버튼을 숨기거나 비활성화하는 것은 사용성일 뿐 보안/안전장치가 아니다. 실제 게이트는 항상 서버 라우트 안의 조건문이어야 한다.
  • "수정 = 재검토 필요"라는 규칙을 상태 전이 로직에 명시적으로 넣어두면, 나중에 이 프로젝트를 안 보다가 다시 봐도 왜 이렇게 짰는지 코드만 봐도 알 수 있다.
Comments