핵심 갭 3가지
① 영업시간·국가 공휴일 캘린더 미구현 (timezone offset만 존재) · ② wrong_contact/forward_request intent 부재 · ③ 알림 intent 필터·preferences 부재. 6개 기능의 절반이 이 3개 갭을 메우는 작업이다.
▶ 한 장면으로 보는 전체 그림
"수출기업 영업 담당 김 과장이 미국 바이어 200명에게 7일 간격 8차 메일 시퀀스를 돌린다" — 이 한 가지 상황에서 6개 기능이 어떻게 김 과장 대신 일해주는지.
지금(자동화 전)은 이렇게 흘러갑니다
엑셀로 받은 바이어 명단을 올리니 회사명이 (주)ABC Trading Ltd 처럼 지저분합니다. 시퀀스를 켜면 메일이 한국 시간 기준 10초에 한 통씩 나가는데, 미국은 한밤중이고 토요일에도 발송됩니다. 어떤 바이어는 "휴가 중" 자동응답만 돌려보내고, 어떤 바이어는 "저는 담당이 아니에요, 구매팀에 보내세요"라고 답합니다. 8번째 메일까지 다 보냈지만 답 없는 바이어가 절반입니다. 그 사이 진짜 "미팅하고 싶어요" 답장 몇 개는 수십 개의 답장 더미에 묻혀 놓칩니다. — 이 모든 걸 김 과장이 손으로 챙겨야 합니다.
6개 기능을 켜면, 같은 상황이 이렇게 바뀝니다
명단 업로드 직후 — 버튼 한 번으로 (주)·Ltd 같은 군더더기가 전부 정리됩니다. (기능 5)
발송 중 — 메일은 미국 바이어의 평일 업무시간(현지 오전 9시~오후 6시)에만 나갑니다. 추수감사절·주말은 알아서 건너뛰고, 200통이 하루를 넘기면 자동으로 다음 영업일로 나눠 보냅니다. (기능 4)
"휴가 중" 자동응답이 오면 → 그 바이어가 복귀하는 날 아침에 같은 메일을 한 번 더 보냅니다. (기능 2)
"담당 아니에요, 구매팀 메일로" 답장이 오면 → 그 주소로 같은 내용을 자동 전달(또는 확인 후 전송)합니다. (기능 3)
"미팅 잡고 싶어요" 긍정 답장이 오면 → 답장 더미에 묻히기 전에 김 과장 메일로 즉시 알림이 옵니다. (기능 6)
8차까지 다 보냈는데 답 없는 바이어에게는 → 며칠 뒤 "확인되셨을까요?" 후속 메일을 알아서 한 번 더. 아직 시퀀스 진행 중인 사람은 건드리지 않습니다. (기능 1)
한마디로: 명단 정리 → 적절한 시간 발송 → 상황별 답장 자동 대응 → 중요한 답장만 알림 → 놓친 사람 자동 리마인드까지, 손이 가던 일을 시스템이 대신합니다.
1 미응답 자동 후속(Nudge) 메일 신규
보낸 메일에 답장이 없으면 최근 보냈던 내용을 바탕으로 "확인되셨을까요?" 톤의 후속 메일을 자동 발송.
이런 게 편해져요
8번째 메일까지 다 보냈는데도 답이 없는 바이어가 많습니다. "한 번 더 찔러볼까?" 싶지만, 누가 답을 안 했는지 일일이 찾아 한 명씩 다시 쓰는 건 일입니다.
지금답 없는 바이어를 직접 추려 한 명씩 리마인드 메일을 손으로 작성·발송.
자동화 후시퀀스를 끝까지 돌렸는데도 답 없는 사람만 골라, 며칠 뒤 "확인되셨을까요?" 후속 메일을 같은 대화 스레드로 알아서 한 번 더 발송.
똑똑한 점: 아직 시퀀스가 진행 중인 사람에게는 보내지 않습니다 — 그건 이미 다음 메일이 예약돼 있어서 이중 발송·스팸이 되기 때문입니다. "다 보냈는데도 조용한" 사람에게만 딱 한 번(최대 2회) 갑니다.
핵심 설계 질문 — 7일 간격 8차 시퀀스는 어떻게?
요청에서 직접 지목한 쟁점이다. 결론부터:
시퀀스 진행 중에는 Nudge를 보내지 않는다. Nudge는 "시퀀스가 끝났는데도 답이 없는" enrollment에만 적용한다.
8차 시퀀스의 각 스텝은 그 자체가 이미 후속(follow-up)이다. 진행 중에 별도 Nudge를 끼우면 동일 스레드에 이중 발송 → 스팸·도달율 악화. 따라서 트리거는 enrollment.status='completed' AND repliedAt IS NULL 단 한 경우다.
트리거 조건 (전부 AND)
sequenceEnrollments.status = 'completed' — 마지막 스텝까지 발송 완료
repliedAt IS NULL — 답장 없음 (열람 여부 무관, 답장 기준)
completedAt < now() - interval 'N days' — 마지막 스텝 후 cool-down (기본 5 영업일)
리드가 bounced/unsubscribed/suppressed 아님
nudge_count < max_nudges — 시퀀스당 상한 (기본 1회, 최대 2회)
발송 내용 생성
해당 enrollment의 마지막(또는 열람률 가장 높았던) 스텝 콘텐츠를 sequenceStepContents에서 가져온다.
LLM(gemini-3-flash)으로 "짧은 리마인드 톤"으로 재작성 — 원문 요지 1~2줄 인용 + "확인되셨을까요?" CTA. 새 제목 대신 Re: 유지.
같은 스레드로 발송 — 원본 emails.threadId · messageId를 In-Reply-To/References 헤더에 넣어 받은 편지함에서 기존 대화에 묶이게 한다 (도달율·맥락 ↑).
난이도 S~M · OOO 감지·발송 인프라 재사용. 신규는 복귀일 파서 + resend 스케줄 분기. 기능 4 의존(영업일 계산 공유).
3 담당자 아님 → 자동 전달 신규
"저는 담당자가 아니에요, XXX@회사.com 으로 보내세요" 답장 → 그 주소로 동일 내용 자동 전달.
이런 게 편해져요
"저는 담당이 아니고, 구매팀 김 부장(kim@company.com)에게 연락하세요"라는 답장이 옵니다. 좋은 단서지만, 결국 내가 원래 메일을 복사해 새 주소로 다시 써야 합니다.
지금답장에서 새 이메일 주소를 눈으로 찾아, 원래 메일 내용을 복붙해 직접 다시 발송.
자동화 후답장에서 새 담당자 주소를 자동으로 찾아내, 같은 내용을 그 주소로 전달. 새 연락처로 등록까지.
실수 방지: 기본은 "전달 메일을 미리 만들어 두고 내가 확인 후 전송" 모드입니다. 자신 있으면 완전 자동 모드로 전환할 수 있습니다 — 엉뚱한 주소로 잘못 나가는 걸 막기 위함입니다.
현황과 갭
현재 AI 분류에 "담당자 아님" intent가 없다. 이런 답장은 objection이나 question으로 잘못 분류된다. 먼저 intent를 추가해야 한다.
구현 단계
확장 AI 분류에 intent wrong_contact 추가 — 프롬프트에 정의: "recipient says they are not the right person and points to someone else". 응답에 referred_email·referred_name 필드 추출 추가.
대체 주소 추출 — 답장 본문 정규식(이메일 패턴) + LLM 교차검증. 둘이 일치할 때만 신뢰.
전달 실행:
추출 주소를 새 leadContact로 추가
원본 시퀀스 콘텐츠(현재 스텝)를 그 주소로 발송 — 새 enrollment 생성 또는 단발 전달
원본 lead enrollment는 stopped (stopReason wrong_contact)
안전장치 — Auto vs Review
잘못 추출한 주소로 자동 발송하면 콜드메일 평판 리스크. 두 모드를 설정으로 제공:
Review (기본) — 전달 draft를 생성해 유저 inbox에 "확인 후 전송" 카드로 노출 (enqueueFollowUpDraftJob 패턴 재사용)
Auto — confidence ≥ 0.8 + 주소 정규식·LLM 일치 시에만 즉시 전달
DB 변경
-- email_replies 에 추출 결과 저장 (intent='wrong_contact' 일 때)
referred_email varchar(255)
referred_name varchar(255)
forward_status varchar(16) -- 'pending' | 'forwarded' | 'review' | 'skipped'
-- enrollment stopReason enum 에 'wrong_contact' 추가
난이도 M · 분류 프롬프트 확장 + 주소 추출 + 전달 발송 + Review 카드 UI. 기능 6과 알림 경로 공유 가능.
4 국가별 영업일 발송 신규
시퀀스 메일을 수신자 국가의 영업일·영업시간 안에서만 발송. 벗어나면 다음 영업 슬롯으로 자동 이월.
이런 게 편해져요
한국 오후 3시에 미국 바이어에게 메일을 보내면, 그쪽은 새벽 1시입니다. 자다 깨서 보는 콜드메일은 열어보지도 않고, 토요일·추수감사절에 도착한 영업 메일은 신뢰를 떨어뜨립니다.
지금한국 시간 기준 10초에 한 통씩 발송 → 상대국 새벽·주말·공휴일에도 그대로 도착. 메일이 수만 건이면 영업시간을 넘겨 밤새 나감.
자동화 후각 바이어 나라의 평일 업무시간(예: 현지 09–18시)에만 발송. 주말·공휴일은 자동으로 건너뜀. 하루치 발송 용량을 넘으면 다음 영업일로 알아서 나눠 보냄.
왜 중요한가: 콜드메일은 "현지 업무시간 도착"이 열람률·응답률에 가장 큰 영향을 줍니다. 잘못된 시간대 발송은 스팸 신고·도달율 하락으로 이어집니다.
현황과 갭
현재 timezoneMode(sender/buyer) + scheduledHour/Minute로 시각만 맞춘다. 영업 요일·공휴일 개념이 없다. 토요일 09:00, 크리스마스 09:00에도 그대로 발송된다.
설계 — Business Calendar 레이어
신규 워크스페이스(또는 시퀀스)별 영업시간 설정: 영업 요일(기본 월~금), 시작·종료 시각(기본 09:00–18:00), 기준 timezone.
신규 국가별 공휴일 — date-holidays 라이브러리 또는 정적 public_holidays 테이블(국가코드·날짜). 리드의 국가/timezone으로 조회.
스케줄 보정 함수nextBusinessSlot(scheduledAt, calendar): 계산된 scheduledAt이 영업시간 밖이면 → 다음 영업일·영업시간 시작으로 shift. 발송 워커의 로더가 픽업 직전 한 번 더 검증.
scheduledAt 계산
│
▼
영업 요일? ──no──► 다음 영업 요일 09:00 으로 이동
│yes
▼
공휴일? ──yes─► 다음 영업일 09:00 으로 이동
│no
▼
09:00~18:00? ─no──► (이른 새벽→당일 09:00 / 저녁 이후→익일 09:00)
│yes
▼
그대로 발송
핵심 난제 — Throttle vs 영업시간 용량
요청에서 직접 지적한 이슈. "10초에 1건"이면 하루 영업시간(9h)에 한 계정당 약 3,240건이 상한. 수만 건이면 영업시간을 넘긴다.
대응 우선순위:
용량 초과분 자동 익일 이월 — 영업 윈도우 capacity = 윈도우초 / throttle. 초과분은 다음 영업일로 밀어 keyset 순서대로 처리.
Provider 전환·다중 계정 — SendGrid는 1.1s/건(≈하루 19,200건)로 SES(10s)보다 9배. 영업일 제약이 빡빡하면 SendGrid 권장 + 계정 분산(per-account throttle은 이미 독립).
발송 시각 분산 — 영업시간 전체에 균등 stagger(이미 존재)하되 capacity 기준으로 일자 분할.
자동화 후모달에서 버튼 한 번 → "이전 → 이후" 미리보기로 어떻게 바뀌는지 확인 → 적용. 수백 개가 한 번에 깔끔해짐.
안전장치: 적용 전 미리보기 필수(바뀌는 행만 강조). 실수해도 되돌릴 수 있게 직전 값을 1회 보관.
설계
확장 기존 BulkActionModal.tsx에 "normalizeCompany" 액션 타입 추가 (현재 status/businessType/copyToGroup).
신규 정규화 유틸 — 접사 제거 규칙:
지역
제거 패턴 (대소문자·공백·점 무시)
한국
(주)㈜주식회사유한회사
영미
IncLLCLtdCo.CorpLimited
유럽/일
GmbHS.A.B.V.株式会社有限会社
미리보기 필수 — 모달에 이전 → 이후 변경 행을 표로 보여주고, 변화 없는 행은 회색 처리. 사용자가 확인 후 적용.
대상: 선택된 리드 / 현재 필터 전체 중 선택.
API — 신규 Bulk Update 필요
현재 /leads/bulk는 생성 전용, 수정은 단건 PUT만 있다. 회사명 일괄 수정을 위한 PATCH /leads/bulk-update 신규 필요 — { leadIds[], field:'companyName', op:'normalize' }. keyset 배치로 대량 처리.
발송: 기존 SendGrid 발송 서비스(email.service.ts) 재사용. 수신자 = enrollment 소유 유저(user_email_accounts 또는 user 이메일).
중복 방지 — 같은 thread 같은 intent는 24h 내 1회만.
알림 설정 (preferences)
현재 알림 preferences 테이블이 없다. 우선 기본 ON으로 출시하고, 이후 경량 notification_preferences(userId, eventType, emailEnabled)를 추가해 끄기 옵션 제공. 기능 3(전달)·기능 1(nudge 답장)도 같은 알림 경로를 공유하므로 함께 설계.
// reply-automation 파이프라인 끝에 1 분기 추가
if ((intent === "positive_interest" || intent === "meeting_request")
&& confidence >= 0.7
&& !alreadyNotified(threadId, intent)) {
await sendPositiveReplyAlert({ to: ownerEmail, lead, intent, aiSummary, snippet })
}
난이도 S · 발송·분류 인프라 완비. intent 게이트 + 메일 템플릿 + 중복 가드만. 가장 빠른 백엔드 기능.
⚠ 예외·리스크 분석 & 대처
"리스크 있는 시나리오가 모두 대처 가능한가?"에 대한 답. 각 기능의 실패 시나리오를 발생 영향·대처·테스트 가능 여부로 정리한다.
관통하는 원칙 — "안전한 실패(fail-safe) 방향"
이 기능들은 전부 자동 발송을 늘린다. 따라서 모든 설계는 "애매하면 보내지 않는다 / 사람이 확인한다 / 한 번만 보낸다" 쪽으로 실패하게 만든다. 오발송(스팸 신고·평판 하락)이 미발송(기회 한 번 놓침)보다 훨씬 비싸기 때문이다.
공통 리스크 4가지 (모든 기능 공유)
리스크
시나리오
대처 (기존 자산 재사용)
위험도
중복 발송
워커 재시도·동시 실행으로 같은 메일 2번 발송
idempotency.ts의 Redis marker(2h TTL) + atomic claim(WHERE status='pending') + BullMQ jobId dedup를 모든 신규 발송(Nudge·OOO재발송·전달)이 그대로 통과
중
AI 오분류
"담당 아님/긍정"을 잘못 판정 → 오발송·오알림
confidence 게이트(0.7~0.8) + 분류 실패 시 neutral·confidence:0 fallback(=동작 안 함) + 기능3은 Review 모드 기본
상
발송 평판
자동 발송량 증가 → bounce·스팸률 상승 → 도메인 평판 하락
기존 verify-email(MillionVerifier)·bounce_check·suppression 파이프라인을 신규 발송도 전부 통과 + 전 기능 opt-in 기본 off
파싱 결과 sanity 범위(now ~ now+90d) 밖이면 버리고 "다음 영업일" fallback
✅ 완전
3 · 전달 (최고위험)
잘못 추출한 주소로 전달 → 무관한 사람에게 콜드메일
정규식+LLM 이중 확인 + confidence≥0.8 + 기본 Review 모드(사람 승인)
⚠️ Review로 완화
전달받은 사람도 "담당 아님" → 재귀 전달 체인
전달 깊이 1로 제한(forward된 발송은 재전달 비활성)
✅ 완전
전달했는데 원본 enrollment가 계속 발송
전달 + 원본 stopped(wrong_contact)를 한 트랜잭션으로
✅ 완전
4 · 영업일 (가장 무거움)
타임존·DST(서머타임) 계산 오류
IANA tz DB 기반 라이브러리(luxon / date-fns-tz)로 DST 자동 처리 + 기존 immutable tz snapshot 패턴
✅ 라이브러리
capacity 이월이 무한 누적 → 영원히 안 나가는 메일(starvation)
keyset 공정 순서(오래된 것 우선) + 적체 임계 모니터링·알림 + provider/계정 증설 가이드
⚠️ 모니터링 필수
lead 국가/timezone 미상
sender(한국) 영업시간으로 명시적 fallback — "모르면 안전한 기본값"
✅ 완전
5 · 회사명
과도 제거("Coca-Cola"→"ca-Cola", "3M Co"의 일부 오인)
word boundary(\b) + 접두/접미 위치 한정 + 적용 전 미리보기 필수 + undo 스냅샷
✅ 미리보기로 차단
6 · 알림
오분류로 알림 폭주(피로) / 한 thread 여러 답장
confidence 게이트 + 24h thread·intent 중복 가드 + 분류 실패 시 무알림(안전한 실패)
✅ 완전
완전 차단이 안 되는 2곳 (잔여 리스크) — 정직하게 명시
기능 3 전달 — 주소 추출은 본질적으로 100% 정확할 수 없다. 그래서 "완전 자동"이 아니라 Review 모드를 기본값으로 출시한다. Auto 모드는 사용자가 위험을 이해하고 켜는 옵션.
기능 4 capacity / Redis fail-open — 발송량이 영업시간 용량을 구조적으로 초과하면 적체는 누적된다(메일 자체가 너무 많은 것이지 버그가 아님). 또 Redis 장애 시 throttle가 fail-open이라 단기 중복 가능 → provider rate limit이 최후 방어선이고, 적체·marker write 실패를 모니터링/알림으로 가시화해 운영이 개입하게 한다.
나머지 시나리오는 코드 구조(unique 제약·트랜잭션·트리거 조건)로 완전 차단 가능하다.
✓ 2026 패턴 적합성 · 테스트 검증
"2026 최적 패턴인가" + "테스트 코드로 문제없는지 보장 가능한가"에 대한 답.
2026 최적 패턴 부합 점검
관점
2026 베스트 프랙티스
본 기획 적용
트리거 방식
polling 대신 이벤트 기반(webhook)
기능 2·3·6은 inbound webhook 트리거(✓). 기능 1·4는 cron 불가피하나 keyset cursor + partial index로 풀스캔 회피
멱등성
모든 발송에 idempotency key, at-least-once 전제
기존 executionId Redis marker 패턴을 신규 발송 전부에 적용(✓)
시간/타임존
IANA tz DB + DST 자동, 시각은 immutable snapshot
luxon/date-fns-tz + 기존 sequenceStepExecutions.timezone snapshot 재사용(✓). Temporal API는 Bun 런타임 성숙도 확인 후 채택(현재는 보류 권장)
LLM 사용
structured output(JSON schema), low temperature, confidence 노출, 저비용 모델
gemini-3.1-flash-lite + temp 0.1 + confidence 필드 + fallback(✓) — 이미 현행 패턴
기존 BulkActionModal·radix dialog·Jotai atom 패턴 그대로 확장(✓) — 자체 SSOT 안 만듦
스키마/DB
UUIDv7 PK, keyset 페이지네이션, 무거운 집계는 analyticsDb
신규 테이블 UUIDv7, bulk-update·nudge scan 모두 keyset(✓)
결론: 6개 모두 이 저장소의 2026 현행 패턴(이벤트·멱등·keyset·structured LLM·React 19) 위에 얹힌다. 레거시 폴링·OFFSET·자체 SSOT 같은 안티패턴은 없다. 유일한 판단 보류는 Temporal API(런타임 성숙도) — 현재는 검증된 tz 라이브러리 권장.
테스트로 문제없는지 — 현실 평가
먼저 솔직한 현황: 이 저장소는 bun test로 ~200개 테스트 파일이 있지만 webhook·AI 분류에는 테스트가 없고, CI는 테스트를 실행하지 않는다(lint+type-check+build만; 테스트는 로컬·pre-commit). 즉 "기존 테스트가 통과하니 안전"이라고 말할 수 없고, 신규 기능은 테스트를 새로 써야 하며 위험 로직은 CI/pre-commit에 테스트를 추가해야 회귀를 잡는다.
검증 대상
테스트 종류
커버 가능?
normalizeCompanyName() (기능 5)
unit — Coca-Cola·3M·주식회사·株式会社·B.V. 등 엣지 케이스 테이블
✅ 쉬움(순수 함수)
nextBusinessSlot() (기능 4)
unit — 주말·공휴일·DST 경계·연말·자정 경계 대량 케이스
✅ 쉬움(순수 함수)
복귀일 파서 (기능 2)
unit — 다양한 OOO 문구/언어/날짜 포맷 + sanity 범위
✅ 쉬움
OOO 감지 (기능 2)
unit — RFC3834·X-Auto-* 헤더 fixture
✅ 쉬움(기존 유틸 확장)
intent 게이트·중복 가드 (기능 1·3·6)
unit — 분류 결과 mock → 발송/알림 여부 판정
✅ 쉬움(AI는 mock)
Nudge 선정·전달 트랜잭션·enrollment 전이
integration — docker compose DB, 동시 상태변경·중복 webhook 재현
⚠️ 가능하나 셋업 필요
throttle race / capacity 이월
load test — 동시 수천 job
⚠️ bun test로 부족 → 부하 테스트
Redis 장애 중복 발송
chaos — Redis 강제 다운
❌ 테스트보다 모니터링·알림으로
테스트 전략 결론
① 위험 로직 대부분이 순수 함수(회사명 정규화·영업일 계산·복귀일 파싱·intent 게이트)라 unit test로 강하게·값싸게 커버된다 — 여기가 버그가 가장 많이 날 곳이고, 가장 쉽게 막힌다. ② DB·race는 integration test(docker compose)로 핵심 시나리오(중복 webhook·동시 전이·이중 발송) 재현. ③ throttle race·Redis 장애처럼 테스트로 못 막는 잔여 리스크는 운영 모니터링·알림으로 가시화한다. ④ 신규 위험 로직 테스트는 CI/pre-commit에 편입(현재 CI 미실행이므로)해야 회귀를 잡는다.
⚙ 설정 위치(UI 배치) · 기본값 정책
"on/off 토글을 어디에 두나, 그리고 처음 켰을 때 기본값은 무엇인가." 설정의 적용 범위(scope)로 위치를 정하고, 평판 리스크로 기본값을 정한다.
위치 결정 기준 — "이 설정은 무엇마다 달라지나?"
적용 범위
최적 위치
이유
캠페인마다 다름
시퀀스 화면
공격적/조심스러운 발송을 캠페인별로
사람마다 다름
유저 설정
"나는 받고 동료는 끄고"
회사 전체 1회
워크스페이스 설정
영업시간·공휴일 같은 조직 공통 규칙
일회성 행동(토글 아님)
데이터 화면의 액션
상태가 아니라 한 번의 실행
기본값 결정 4원칙
A. 새 자동 발송 = opt-in(기본 off) — 단, "명확한 트리거 + 강한 무한루프 가드"가 있으면 on 허용 B. 위험 없는 도달율·편의 개선 = 기본 on — 켜는 게 거의 항상 이득이면 기본 on C. 오발송이 비싸고 되돌리기 어려운 것 = 사람 확인(Review) 기본 — 자동 실행은 명시적 opt-in D. 점진 롤아웃 — 신규 워크스페이스는 안전 기본, 기존 워크스페이스는 갑작스런 동작 변화를 막기 위해 공지 후 단계 전환
기능별 — 위치 × 기본값 × 핵심 파라미터
기능
위치 (scope)
기본 상태
기본 파라미터 / 근거
1 · Nudge
시퀀스 (캠페인별)
OFF
새 자동 발송(원칙 A). 켤 때 기본 cool-down 5영업일·최대 1회. 기존 replyAutomationConfig에 합류
2 · OOO 재발송
시퀀스 (캠페인별, 고급)
ON*
트리거가 명확(OOO 응답 수신)하고 1회 제한 가드 → on 허용(원칙 A 예외). *기존 워크스페이스는 공지 후 전환(원칙 D)
3 · 담당자 전달
시퀀스 (답장 자동화)
Review 기본 (자동전달 OFF)
최고위험(오발송)→ 사람 확인 기본(원칙 C). Auto 모드는 confidence≥0.8 이해 후 opt-in
4 · 영업일 발송
워크스페이스(기본) + 시퀀스 override
ON
도달율에 명백히 이득·위험 없음(원칙 B). 기본 캘린더 월~금·현지 09–18시·국가 공휴일 skip. 기존 워크스페이스는 발송 패턴 변화 있으므로 단계 적용(원칙 D)
5 · 회사명 정리
leads 화면 (액션)
해당 없음
토글이 아닌 일회성 액션. "기본값"은 미리보기 후 적용 강제 + 제거 규칙 체크박스 기본 선택
6 · 긍정 알림
유저 설정 (사람별)
ON
유용·피로 낮음(긍정/미팅만, 원칙 B). confidence≥0.7·24h 중복가드. notification_preferences 없으므로 우선 무조건 on→이후 끄기 제공
기존 워크스페이스 마이그레이션 (원칙 D 상세)
"기본 on"이라도 이미 운영 중인 워크스페이스에 조용히 켜면 안 된다.
기능 4(영업일) — 켜는 순간 발송 시간대·속도가 바뀐다(밤샘 발송 → 영업시간 집중). 적체 가능성 있으므로 인앱 공지 + 명시적 활성화 후 적용. 신규 워크스페이스만 자동 on.
기능 2(OOO 재발송) — 발송량이 늘 수 있어 출시 초기엔 off로 시작 → 안정 확인 후 신규 기본 on.
기능 6(알림) — preferences 테이블 신설 전까지는 전원 on. 끄기 UI가 준비된 뒤에 개별 제어.
원칙: 신규 = 안전한 권장 기본값, 기존 = 동작이 바뀌는 것만 공지·opt-in.
메일함(inbox)에는 설정 토글을 두지 않는다. inbox는 결과를 보고 개별 대응하는 운영 화면 — 정책(설정)과 액션(개별 실행)을 분리한다. 단 개별 액션은 inbox에 노출: "이 답장 전달하기"(기능 3), "이 thread 알림 끄기"(기능 6). 설정 scope가 불명확해지는 토글만 금지.
★ 통합 우선순위 · 로드맵
기능
유형
난이도
의존
우선순위
6 · 긍정·미팅 알림
확장
S
없음
P0 (즉시)
5 · 회사명 정리 모달
확장
S~M
없음
P0 (즉시)
4 · 국가별 영업일
신규
L
없음(기반)
P1 (기반)
3 · 담당자 자동 전달
신규
M
알림(6)
P1
2 · OOO 재발송
확장
S~M
영업일(4)
P2
1 · 미응답 Nudge
신규
M
영업일(4)
P2
권장 진행 순서
Wave 1 (빠른 가치) — 기능 6 + 기능 5. 의존 없음, 며칠 내 체감. 사용자 만족·신뢰 즉시 확보.
Wave 2 (기반) — 기능 4 영업일 캘린더. 가장 무겁지만 기능 1·2가 이 위에 선다. capacity 이월 로직을 여기서 한 번 제대로.
Wave 3 (지능형 자동화) — 기능 3(전달) → 기능 2(OOO 재발송) → 기능 1(Nudge). 모두 답장 intent·영업일 기반을 재사용.
설계 일관성 원칙
① 모든 자동 발송(Nudge·OOO 재발송·전달)은 기능 4 영업일 게이트를 반드시 통과 · ② 모든 자동 알림(전달·긍정)은 단일 알림 경로 + 중복 가드 공유 · ③ intent 확장은 ai-classification.service.ts 한 곳에서 (SSOT) · ④ 자동 발송은 전부 opt-in 기본 off로 출시해 평판 리스크 차단.