Skip to content

[FEAT] 단체 메일 발송 및 발송 이력 기록 #629

Open
2 of 2 issues completed
Open

Description

@023-dev

목표

운영자가 한 모집 공고에서 선택한 지원자들에게 하나의 메일 템플릿으로 단체 메일을 발송하고, 발송 작업과 수신자별 결과를 남길 수 있도록 합니다.

예약 발송이 아니라 요청 시점에 실행되는 단체 메일입니다. 템플릿 원본과 이번 발송에서 사용할 제목·입력값을 구분해야 합니다.

요청 및 실행 계약

  • 관리자용 API를 /admin/mails/** 아래에 추가합니다. 발송 요청에는 recruitId, scenarioId, applyIds, 선택적인 제목 덮어쓰기 값, 입력 변수 맵이 포함되어야 합니다.
  • 선택 대상은 모두 요청한 모집 공고에 속한 제출 완료 지원이어야 합니다. 다른 모집 공고의 ID, 중복 ID, 미제출·반려·삭제 지원이 섞이면 외부 발송 전에 전체 요청을 거부합니다.
  • 대상자는 최소 1명, 최대 500명입니다. 최대치를 초과하면 일부만 발송하지 말고 요청 전체를 거부합니다.
  • 비활성 템플릿은 발송할 수 없습니다.
  • 제목 덮어쓰기는 이번 발송에만 적용하며 MailScenario 원본을 수정하지 않습니다. 덮어쓰기가 없으면 템플릿 제목을 사용합니다.
  • 최종 제목은 공백을 제거한 길이가 2~40자여야 합니다.
  • 필수 입력 변수, 선언되지 않은 입력 변수, DATE_TIME 등 타입 오류, 시스템 변수 미해결 여부는 발송 전에 대상자 전원에 대해 검증합니다.
  • 검증 단계에서 대상자별 제목·본문을 모두 렌더링하고 하나라도 실패하면 외부 메일 서비스 호출을 시작하지 않습니다.
  • 발송 작업 생성과 외부 메일 호출의 트랜잭션 경계를 분리합니다. 작업 상태와 대상 상태를 저장하고, 같은 작업이 이미 실행 중이거나 완료된 경우 중복 실행하지 않습니다.
  • 외부 메일 서비스 호출 중 한 수신자가 실패해도 나머지 대상 처리를 계속합니다. 수신자별 성공·실패와 실패 사유를 기록하고 성공 건수·실패 건수를 응답 또는 작업 결과에서 확인할 수 있어야 합니다.

이력 데이터 계약

발송 작업 단위와 수신자 단위를 분리해 저장합니다.

발송 작업에는 다음을 기록합니다.

  • 작업 ID와 상태
  • 사용한 템플릿 ID(필요하면 시나리오 코드·이름 snapshot)
  • 모집 공고 ID
  • 대상 수
  • 요청한 관리자 ID(인증 주체에서 취득)
  • 작업 요청·시작·종료 시각
  • 성공·실패·처리 중 건수

수신자별 기록에는 다음을 기록합니다.

  • 작업 ID
  • applyId와 발송 시점의 이메일 주소 snapshot
  • 대상 처리 상태
  • 성공 시각 또는 실패 사유

외부 발송이 완료된 뒤 템플릿을 수정해도 과거 작업의 대상·주소·성공 여부를 확인할 수 있어야 합니다. 이미 전송된 메일을 DB 트랜잭션 rollback으로 되돌릴 수 있다고 가정하지 않습니다.

API 및 구현 경계

  • 동기·비동기 실행 방식은 현재 외부 메일 어댑터와 관리자 화면의 흐름에 맞추되, 요청이 반환된 뒤에도 작업 상태와 수신자별 결과를 조회할 수 있어야 합니다.
  • 기존 단건 메일 발송 서비스와 rate limit을 재사용합니다. 외부 서비스의 제한을 우회하기 위해 병렬 요청을 무제한으로 만들지 않습니다.
  • 메일 본문은 지원자별 시스템 변수(name, 모집 공고 기수, 예비 번호)와 공통 입력 변수를 병합해 렌더링합니다. 시스템 변수는 요청의 입력 변수로 덮어쓸 수 없습니다.
  • API 경로, DTO, 상태 enum은 기존 관리자 메일 코드와 호환되는 이름을 우선하고, 최종 계약을 Swagger와 PR 설명에 남깁니다.

완료 조건

  • 선택한 1~500명의 제출 완료 지원자에게 개인화된 제목·본문이 발송된다.
  • 선택 대상이 비어 있거나 모집 공고·지원 상태가 맞지 않으면 발송되지 않는다.
  • 발송 전 전체 렌더링과 변수 검증이 수행되고, 실패 시 외부 호출이 0건이다.
  • 제목 덮어쓰기가 템플릿 원본을 변경하지 않는다.
  • 2자 미만·40자 초과 제목과 필수 입력 누락이 거부된다.
  • 일부 수신자 실패가 나머지 발송을 중단하지 않는다.
  • 발송 작업과 수신자별 성공·실패·실패 사유·이메일 snapshot이 저장된다.
  • 요청 관리자와 발송 시각을 확인할 수 있다.
  • 같은 작업의 중복 실행이 방지된다.
  • rate limit을 준수한다.
  • 단위, 서비스, API, 저장소/통합 테스트로 전체 성공·전체 실패·부분 실패·사전 검증 실패를 검증한다.

이번 이슈에서 하지 않는 것

  • 미래 시각을 저장하는 예약 발송 및 스케줄러
  • 발송 이력 목록·상세·실패 대상 조회 API
  • 발송 취소 또는 이미 전송된 메일 회수
  • 자동 재시도 정책과 별도 워커/인프라 구축
  • 첨부파일, 템플릿 버전 관리, 레거시 고정형 메일 플로우 제거

선행 작업 및 참고

범위 확정

  • 이 이슈의 완료 범위는 요청 시점 즉시 발송, 발송 작업·수신자별 결과 저장, 부분 실패 집계입니다.
  • 발송 작업·수신자별 결과 조회 API는 후속 이슈 #669에서 다룹니다.
  • Outbox·워커·자동 재시도·재기동 복구·idempotency key payload 충돌 검증은 후속 이슈 #670에서 다룹니다.
  • 실패 사유는 외부 메일 공급자의 원문이 아닌 안전한 오류 코드로 저장합니다.

Activity

  1. added 3 commits that reference this issue on Sep 2, 2026
  2. added 4 commits that reference this issue on Sep 8, 2026
  3. 023-dev commented on Sep 9, 2026

    @023-dev
    MemberAuthor

    메일 발송 진행 상황

    #629는 #678과 후속 #669·#670 작업이 모두 정리될 때까지 열어둡니다.

  4. added a commit that references this issue on Sep 18, 2026
  5. added 3 commits that reference this issue on Oct 2, 2026
  6. added 4 commits that reference this issue on Oct 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions