알림톡 (NCP SENS) — 발송 · 템플릿 코드레벨 동작 흐름

마지막 업데이트 2026-07-29

알림톡 (NCP SENS) — 발송 · 템플릿 코드레벨 동작 흐름: 입력: 구성 요소, 주요 처리 단계: 발송 흐름 ( /api/alimtalk/send ), 결과: 읽을 때 주의할 함정 흐름
동작 흐름 요약
  1. 입력: 구성 요소
  2. 주요 처리 단계: 발송 흐름 ( /api/alimtalk/send )
  3. 결과: 읽을 때 주의할 함정
문서 읽는 법 · 설명식

이 문서는 이렇게 읽으면 됩니다

알림톡 (NCP SENS) — 발송·템플릿 코드레벨 동작 흐름의 핵심을 설명식으로 먼저 안내합니다. 기술적 결론과 원문 근거는 아래 본문에 보존되어 있습니다.

핵심 흐름 펼쳐 보기
  1. 비유와 핵심 질문으로 먼저 전체 구조를 잡습니다.
  2. 실제 컴포넌트·파일·데이터 흐름을 따라 내려갑니다.
  3. 코드 라인과 주의사항에서 구현 근거를 확인합니다.
  • 구성 요소
  • 발송 흐름 (/api/alimtalk/send)
  • NCP SENS 서명 — makeSignature
알림톡 / NCP SENS 카카오 비즈메시지 HmacSHA256 서명 템플릿 5분 캐시 #{} 변수 치환 전화번호 정규화 미접속 트리거 백로그 P0 #5

TL;DR

아동이 수업에 들어오지 않을 때 보호자에게 카카오 알림톡을 보낸다. 경로는 NCP SENS(Simple & Easy Notification Service). 구조는 두 층 — ① 로컬 템플릿 설정(DynamoDB CRUD: 어떤 카카오 템플릿을 쓸지 title·channelId·templateCode·순서 관리), ② 발송(/api/alimtalk/send: NCP에서 템플릿 본문을 가져와 변수 치환 후 NCP messages API로 전송).

발송은 완전 자동이 아니라 진행자 확인식이다 — 모니터 대시보드가 미접속 5분/10분에 토스트를 띄우고, 진행자가 "발송할까요?"를 클릭해야 보낸다. NCP 호출은 매 요청 HmacSHA256 서명이 필요하고, 템플릿 본문은 5분 메모리 캐시된다.

구성 요소

요소경로 / 권한역할
발송POST /api/alimtalk/send · withAuthMemberNCP 서명 → 템플릿 fetch(캐시) → 변수 치환 → 전화번호 정규화 → NCP messages 전송
템플릿 조회GET /api/alimtalk-templates · withAuthMember로컬 DynamoDB 템플릿 목록
템플릿 생성/수정/삭제/정렬POST/PUT/DELETE … · withAuthAdminOrDevelopercreateAlimtalkTemplate 외. order 드래그&드롭 reorder
DB 레이어db-queries.ts:3006getAlimtalkTemplates·createAlimtalkTemplate (테이블 ppi-alimtalk-template-{stage})
클라 hookentities/alimtalk-template/model/use-alimtalk-templates.ts템플릿 상태 관리
발송 트리거features/monitor/hooks/use-session-checker.ts미접속 5/10분 토스트 + alimtalkAction

발송 흐름 (/api/alimtalk/send)

1
요청 + credential 검증
send/route.ts:88-120
{channelId, templateCode, phone, variables?} 필수 필드 확인. NCP_ACCESS_KEY/NCP_SECRET_KEY/NCP_SENS_SERVICE_ID 없으면 500 NCP_CREDENTIALS_NOT_CONFIGURED.
2
템플릿 본문 fetch (5분 캐시)
fetchTemplateContent :44
${channelId}:${templateCode} 캐시 히트(5분 TTL)면 즉시 반환. 미스면 NCP GET /templates?channelId&templateCodetemplateCode 매칭 content 추출 → 캐시 저장. 못 찾으면 TEMPLATE_CONTENT_NOT_FOUND.
3
변수 치환
:122-127
variables의 각 key에 대해 content.replaceAll("#{key}", value). (단순 문자열 치환 — 프롬프트 변수 엔진과 무관.)
4
전화번호 정규화
formatPhoneNumber :84
숫자만 추출 후 82로 시작하면 0+나머지로. NCP body의 countryCode는 항상 "82" 하드코딩.
5
NCP messages 전송
:129-160
POST /alimtalk/v2/services/{SERVICE_ID}/messages body {plusFriendId:channelId, templateCode, messages:[{countryCode:"82", to, content}]}. 성공 → {success, requestId}, 실패 → ALIMTALK_SEND_FAILED + detail(원 status 전달).

NCP SENS 서명 — makeSignature

NCP API Gateway는 모든 호출에 HmacSHA256 서명을 요구한다. send/route.ts:13-30:

function makeSignature(method, url, timestamp) {
  const message = `${method} ${url}\n${timestamp}\n${NCP_ACCESS_KEY}`;  // url=query 포함 정확히 일치
  return crypto.createHmac("sha256", NCP_SECRET_KEY).update(message).digest("base64");
}
// 헤더: x-ncp-apigw-timestamp(ms) · x-ncp-iam-access-key · x-ncp-apigw-signature-v2

timestamp = Date.now()가 message와 헤더 양쪽에 들어가므로 매 요청 새 서명이다. GET(템플릿 조회)·POST(발송) 각각 자기 method·uri로 서명한다.

발송 트리거 — 미접속 토스트

발송은 모니터 대시보드의 미접속 감지에서 출발한다. features/monitor/hooks/use-session-checker.ts:

코드 맵 — 파일별 역할

파일역할
app/api/alimtalk/send/route.ts발송 — 서명·템플릿 캐시·치환·정규화·NCP 전송 (전부 한 파일)
app/api/alimtalk-templates/*로컬 템플릿 CRUD + [id]/update + reorder
lib/db-queries.ts:3006getAlimtalkTemplates·createAlimtalkTemplate (DynamoDB)
types/db/alimtalk-template.types.tsAlimtalkTemplate {title, channelId, templateCode, order?}
entities/alimtalk-template/model/use-alimtalk-templates.ts클라 템플릿 상태 hook
features/monitor/hooks/use-session-checker.ts미접속 5/10분 토스트 + 발송 액션

읽을 때 주의할 함정

1. "템플릿"이 두 종류다. 로컬 AlimtalkTemplate(DynamoDB)은 어떤 카카오 템플릿을 쓸지를 가리키는 매핑·순서만 담고 본문(content)이 없다. 실제 본문은 발송 시 templateCode로 NCP에서 fetch한다(send/route.ts:44). 본문을 로컬에서 찾으려 하면 안 된다.

2. 발송은 자동이 아니라 진행자 확인식. 미접속 5/10분 토스트의 alimtalkAction을 진행자가 눌러야 발송된다(use-session-checker.ts:83). "왜 자동 발송이 안 되냐"가 아니라 설계가 수동 확인이다.

3. 템플릿 캐시는 인스턴스 메모리 + 5분 stale. templateCache는 모듈 전역 Map이라 서버 인스턴스별로 따로 산다(send/route.ts:41). 카카오에서 템플릿 본문을 바꿔도 최대 5분 + 인스턴스별로 반영이 어긋날 수 있다.

4. 서명의 url은 query까지 정확히 일치해야 한다. makeSignature(method, uri, ...)uri가 실제 fetch URL과 다르면(예: query 인코딩 차이) NCP가 서명 불일치로 거부한다. 템플릿 조회 GET은 ?channelId&templateCode query를 포함한 uri로 서명한다.

5. #{} 표기가 프롬프트 변수와 겹치지만 무관. 알림톡 변수 치환은 content.replaceAll("#{key}", value) 단순 문자열 치환이다(send/route.ts:124). 프롬프트 변수 시스템#{} 파싱 엔진과는 별개다.

6. 권한이 동작별로 다르다. 발송·템플릿 조회는 withAuthMember(진행자), 템플릿 생성/수정/삭제는 withAuthAdminOrDeveloper다(alimtalk-templates/route.ts). 진행자는 발송만, 템플릿 관리는 admin/dev만.

관련 문서

모니터 대시보드 — 미접속 감지·토스트가 발송을 트리거하는 곳 아동 알림 프리셋 — 또 다른 알림 시스템(세션 중 환경음 프리셋, 알림톡과 별개) 운영·관리·인프라 문서화 백로그 — 이 문서는 P0 #5 항목