국가별 영업일 발송 게이트
2026 기준 최적성 재분석

발송 시점에 한국기준 주말이면 영업일로 미루는 기능 — Redis 스크립트 가능성 · 헥사고날 적합성 · 2026 베스트프랙티스 정합성

대상: send-grid-test / elysia-server 브랜치: alpha @ 07b6986ac 작성: 2026-06-11

00 결론 (TL;DR)

✅ 최적 앞서 제안한 "순수 TS 도메인 규칙 + BullMQ native moveToDelayed 재지연" 안은 2026 기준에서도 최적이다. 웹 그라운딩 결과 오히려 권고가 강화됨.
⚠️ 정정 주말 판정을 위한 전용 Redis Lua/EVAL 스크립트는 2026 기준 안티패턴. ① 공유 가변상태가 없는 무상태 연산이라 Redis가 불필요하고 ② 굳이 서버사이드 로직을 Redis에 둔다면 7.0+에서는 EVAL이 아니라 Redis Functions가 권장이기 때문. → Redis 스크립트는 throttle slot과의 atomic 융합이 꼭 필요할 때만 정당화된다.
질문핵심 근거
Redis 스크립트로 발송시 주말 차단 가능? 가능 KST=UTC+9 고정(서머타임 없음) → 정수연산만으로 요일 판정, 마이크로초급. 단 Redis는 선택사항.
헥사고날 구현 가능? 이미 깔림 domain.ts(순수) + ooo-resend.service.ts(포트 주입) 패턴 존재 → 케이스만 추가.
2026 최적? 조건부 규칙=순수 TS, 지연=BullMQ native, UTC 변환은 자명(KST 고정). Redis는 atomic 필요시만, 그때도 Functions.

01 발송 경로와 삽입 지점

발송 워커 오케스트레이터(sequence-email-worker/processor.ts:46)는 claim 이전runPreChecks()를 호출한다. 여기서 throttle·daily-limit이 이미 job.moveToDelayed(...) + throw DelayedError() 로 재지연하는 패턴이 있어, 주말 게이트를 같은 자리에 한 줄로 얹으면 된다.

PrerunPreChecks — concurrency·throttle·daily-limit+ 주말 게이트
0validateAndClaim — execution claim
1resolveLead + bounce check
2verifyEmail + dedup
3resolveContent
4+5send + status update

claim 에 미루므로 execution이 "processing"으로 박혀 deadlock 되는 일이 없다(이 파일 주석이 명시한 제약과 일치). throttle의 슬롯거부 재지연(pre-check.ts:73)과 완전히 동일한 메커니즘이라 신규 인프라 0.

02 2026 베스트프랙티스 정합성 검증

Redis: EVAL 스크립트 → Functions, 그러나 여기선 둘 다 불요

BullMQ: UTC 변환 + native delayed job

03 헥사고날 설계 (기존 자산 재사용)

레이어기존 자산주말 게이트 적용
도메인(순수)modules/email-automation/domain.tsisBusinessOpen(), nextBusinessSlot()그대로 재사용. workDays=[1..5]면 주말 자동 제외
어댑터(캘린더)business-calendar.adapter.tsbusinessCalendarFor()KST 캘린더 1개
포트 주입ooo-resend.service.ts:103{ calendar, clock, scheduler }동일 시그니처로 send-time 게이트
드라이빙 어댑터scheduler.reschedule = ms => job.moveToDelayed(ms)

핵심 트레이드오프 (Karpathy 지침: 단순성·명시)

⚠️ 상충 영업일 규칙을 Lua 문자열 안에 넣으면 stringly-typed·단위테스트 불가가 되어 헥사고날의 핵심 이점을 스스로 깬다. 규칙은 TS 순수 도메인, Lua/Function은 "clamp 산술"만 하는 멍청한 어댑터로 한정해야 둘 다 만족.
// 도메인: 순수함수, DB·Redis 없이 단위테스트
export function decideSendWindow(now: Date, cal: BusinessCalendar)
  : { sendNow: true } | { deferUntil: Date } {
  if (isBusinessOpen(now, cal)) return { sendNow: true }
  return { deferUntil: nextBusinessSlot(now, cal) }
}

// pre-check 어댑터: 기존 throttle 재지연과 동일 패턴
const w = decideSendWindow(new Date(), KST_CAL)
if ('deferUntil' in w) {
  await job.moveToDelayed(w.deferUntil.getTime())
  throw new DelayedError()
}

04 권장 구현안 (우선순위)

  1. 규칙 = 순수 도메인decideSendWindow()domain.ts에. clock/scheduler mock으로 단위테스트(ooo-resend 방식 그대로).
  2. 발송시점 게이트 = pre-check.ts — Redis 불요. 가장 단순하고 2026 BullMQ 권고(native delay)와 정합. 1순위 권장
  3. atomic이 꼭 필요하면 — producer의 RESERVE_NEXT_SLOT_LUA에 주말 clamp 융합. 단 신규 작성이면 EVAL 대신 Redis Function으로. 조건부
  4. 전용 주말 Lua 스크립트 신설비권장 무상태 로직의 오버엔지니어링, 헥사고날·2026 양쪽에 역행.

05 출처