마스킹 이메일 복구 — 구체 실행 계획
beta · 11,958 마스킹 · 위험표면 매우 작음(active 임박 7건) · 2026-06-04
한눈에
좋은 소식: 누수는 #8138로 차단됨. 임박 위험은 active 시퀀스 1개의 overdue 7건뿐 — 나머지 임박분(505건)은 이미 paused 시퀀스라 발사 안 됨(자동 보호). 따라서 광범위한 캠페인 정지는 불필요하고, 이메일 복구가 핵심 작업입니다.
11,958
마스킹 (백업 완료)
7
즉시 위험(active 캠페인 overdue)
9,459
예약메일(복구 시 자동)
1,628
재활성화 대상(stopped)
6,124
과거 skip(재발송 정책결정)
핵심 메커니즘 (코드 확정)
| 동작 | 결과 | 안전? |
|---|---|---|
| sequence = paused | sequence.gate → removeJob, step status pending 유지 → 재개 시 재enqueue | 무손실·가역 |
| enrollment = paused | enrollment.gate → 특례 없음 → skipped(손실) | 금지 |
| loader 적재 조건 | s.status='active' AND e.status='active' 인 pending만 | — |
| 옛 skip BullMQ job | removeOnComplete(1h)로 제거됨 → 재적재 dedup 차단 없음 | OK |
정지는 반드시 sequence 단위. enrollment 단위 정지는 이미 delayed에 적재된 잡이 발사될 때 skip(손실)을 유발.
G0 선행 게이트
✅ 백업:
lead_contacts_mask_backup_20260604 (11,958)
▢ 누수 멈춤 확정: 아래 = 0 이어야 진행
SELECT count(*) FROM lead_contacts WHERE contact_type='email' AND contact_value LIKE '%***%' AND created_at > '2026-06-04 05:03:00+00'; -- #8138 배포시각
+ 변경 전 enrollment/execution 상태 스냅샷(seq_recovery_backup_20260604) 권장.
1 즉시 위험 차단 (선택·경량)
active 시퀀스 중 overdue 마스킹 보유 = 1개. 복구를 바로 시작하면 생략 가능. 복구가 지연될 우려면 이것만 정지:
-- "AI 캠페인 · 6월 4일 14:11" (019e910b…) — overdue 7건 UPDATE sequences SET status='paused' WHERE id='019e910b-45b0-7863-8f70-3a67162e7c29' AND status='active';
나머지 임박 마스킹(505건)은 이미 paused 시퀀스 소속 → 발사 안 됨. 단, 그 paused 시퀀스들을 복구 전에 재개하지 말 것(재개 시 마스킹 그대로 발송→skip).
2 이메일 원본 복구 핵심
모두 트랜잭션 + dry-run COUNT 먼저. 상세 SQL = 복구계획 페이지.
| 계층 | 건수 | 방법 |
|---|---|---|
| T1 원본 공존 | 6,903 | 마스킹 행 삭제 + 원본 primary 승격 |
| T2 discovery 유일 | 1,935 | 제자리 UPDATE 교체 (is_verified=false) |
| T2b 모호 | 366 | 회사명 추가매칭 |
| T3 원본 없음 | 2,739 | 재enrich 또는 contact 비활성 |
검증:
contact_value LIKE '%***%' = 0(또는 T3 비활성 잔량). lead별 email primary 유일성 확인.3 예약메일 자동 정상화 재예약 불필요
복구 완료 → execution.status pending 유지 → loader 30초 tick 감지
→ BullMQ 적재(jobId idempotent) → 원본으로 정상 발송
9,459건 자동. BullMQ delayed(216k)는 손대지 않음 — 각 잡이 scheduled_at에 fire하며 복구된 원본으로 검증 통과. 대부분 >24h 뒤라 복구가 앞섬.
4 paused 시퀀스 재개 (복구 후에만)
마스킹 pending을 가진 paused 시퀀스는 2단계 복구 완료 후 재개. 재개 즉시 보존된 pending이 재enqueue되어 원본으로 발송.
-- 복구 끝난 뒤, 1단계에서 정지한 것 + 운영상 재개할 것 UPDATE sequences SET status='active' WHERE id IN (/* 복구 완료 확인된 시퀀스 */) AND status='paused';
재개 후
paused_leaked(#5873) lifecycle 누수 모니터 — 시퀀스 헬스체크.5 재활성화 — stopped/unreachable
복구된 원본 보유 1,628건만. (전체 2,139 중 511은 원본 없음 → 재enrich 후)
UPDATE sequence_enrollments SET status='active', stop_reason=NULL, stopped_at=NULL WHERE status='stopped' AND stop_reason='unreachable' AND lead_id IN (SELECT lead_id FROM lead_contacts WHERE contact_type='email' AND contact_value NOT LIKE '%***%'); -- 중단 때 skipped된 미래 step은 6단계 패턴으로 pending 복구 / 또는 reEnroll
제외: bounced 442 / account_removed 280 / completed 52(마스킹 무관).
6 과거 skip 재발송 정책 결정 · 기본 안 함
6,124건(step1 4,133)을 일괄
pending 복구하면 과거 콜드메일이 즉시 버스트 → 평판·스테일 리스크. 기본 권장: 재발송하지 않음.보내야 한다면 (stagger 안전 패턴)
UPDATE sequence_step_executions SET status='pending', error_message=NULL, scheduled_at = now() + (row_number() OVER(ORDER BY id) * interval '20 seconds') WHERE status='skipped' AND error_message LIKE '%***%' AND enrollment_id IN (/* 최근 1~2일 + 복구완료 선별 */); -- currentStepOrder 손대지 말 것(워커 자동진행). failed(마스킹) 954는 reEnrollFailedEnrollments() 사용
7 검증 & 영속 방어
- DB: 마스킹 잔량 0 · overdue pending 정상 발송 · 중복 sent 0
- BullMQ: wait/active 적체 없음 · delayed 자연 소진 · 재적재 잡 fire 확인
- Redis: sent marker(2h) 자연만료 · 재발송 막히면
clearSentMarker(execId) - 영속:
ALTER TABLE lead_contacts ADD CONSTRAINT lead_contacts_no_mask CHECK (contact_value NOT LIKE '%***%') NOT VALID→ 복구 후 VALIDATE
실행 순서 (의존)
G0 게이트(누수=0, 백업)
→ 1 즉시위험 1캠페인 정지(선택)
→ 2 이메일 복구(T1·T2) ──┬─→ 3 예약메일 9,459 자동 (재예약 불필요)
├─→ 4 paused 시퀀스 재개 (복구 후에만)
├─→ 5 stopped 1,628 재활성화
└─→ 6 과거 skip = 정책결정(기본 skip)
→ 7 DB·BullMQ·Redis 검증 + CHECK 제약
요지: "캠페인 일시정지→재개"는 전면적으로 필요 없음 — 임박 위험이 active 1캠페인 7건뿐이라 그것만(또는 복구를 바로) 처리하면 됨. 이미 paused인 시퀀스는 복구 끝낸 뒤 재개하는 순서만 지키면 무손실. 핵심 작업은 이메일 복구이고, 그러면 예약 9,459는 자동 정상화됩니다.