n8n 템플릿 매핑 가이드
🎯 개요
User Engagement 시스템에서 이벤트 → 알림톡 템플릿 매핑은 n8n 워크플로우에서 관리됩니다. 이를 통해 기획자가 코드 수정 없이 템플릿 매핑을 자유롭게 변경할 수 있습니다.
🏗️ 아키텍처
📋 n8n 워크플로우 구조
1. Event Reception & Parsing
// Step 1: Pub/Sub 이벤트 수신
const event = JSON.parse($input.body);
const eventType = event.type;
const eventData = event.payload;
// Step 2: 이벤트 타입별 분기
switch (eventType) {
case 'sleep.rtib.pre_notification.triggered':
return {
eventType: 'RTIB_PRE_NOTIFICATION',
userId: eventData.userId,
templateData: {
userName: eventData.userName || '고객',
nextRtibDay: eventData.nextRtibDayIndex,
currentDay: eventData.currentDayIndex
}
};
case 'user.registration.completion.notification.sent':
return {
eventType: 'REGISTRATION_COMPLETION',
userId: eventData.userId,
templateData: {
userName: eventData.userName || '고객',
phoneNumber: eventData.phoneNumber || '000-0000-0000'
}
};
// ... 기타 이벤트 타입들
}
2. Template Mapping Decision
// Step 2: 템플릿 매핑 (기획자가 자유롭게 수정 가능!)
const templateMappings = {
// === User Engagement Timeline Templates ===
'RTIB_PRE_NOTIFICATION': {
templateId: 'SLEEPQ_RTIB_PRE_001', // NHN 심의 완료 ID
templateName: 'rTIB 전날 알림',
priority: 'HIGH',
buttons: [
{
buttonType: 'WL',
buttonName: '지금 수면기록하기',
buttonUrl: 'sleepq://sleep-log'
}
]
},
'REGISTRATION_COMPLETION': {
templateId: 'SLEEPQ_REGISTRATION_001',
templateName: '회원가입 완료 안내',
priority: 'HIGH',
buttons: [
{
buttonType: 'WL',
buttonName: '슬립큐로 이동하기',
buttonUrl: 'sleepq://home'
}
]
},
'SLEEP_LOG_REMINDER_INSUFFICIENT': {
templateId: 'SLEEPQ_SLEEP_LOG_001',
templateName: '수면기록 부족 리마인드',
priority: 'HIGH',
buttons: [
{
buttonType: 'WL',
buttonName: '수면기록하러 가기',
buttonUrl: 'sleepq://sleep-log'
}
]
},
'INACTIVITY_REMINDER': {
templateId: 'SLEEPQ_INACTIVITY_001',
templateName: '미접속 사용자 리마인드',
priority: 'NORMAL',
buttons: [
{
buttonType: 'WL',
buttonName: '슬립큐 접속하기',
buttonUrl: 'sleepq://home'
}
]
},
'CRM_CALL_REMINDER': {
templateId: 'SLEEPQ_CRM_CALL_001',
templateName: 'CRM 전화상담 리마인드',
priority: 'NORMAL',
buttons: [
{
buttonType: 'WL',
buttonName: '상담 일정 조정하기',
buttonUrl: 'sleepq://crm-schedule'
}
]
},
'RTIB_PRESCRIPTION_NOTIFICATION': {
templateId: 'SLEEPQ_RTIB_PRESCRIPTION_001',
templateName: 'rTIB 처방 알림',
priority: 'HIGH',
buttons: [
{
buttonType: 'WL',
buttonName: '수면목표시간 확인하기',
buttonUrl: 'sleepq://rtib'
}
]
},
'QUESTIONNAIRE_REMINDER': {
templateId: 'SLEEPQ_QUESTIONNAIRE_001',
templateName: '설문조사 리마인드',
priority: 'HIGH',
buttons: [
{
buttonType: 'WL',
buttonName: '수면건강설문조사 참여하기',
buttonUrl: 'sleepq://questionnaire'
}
]
},
'FINAL_QUESTIONNAIRE_REMINDER': {
templateId: 'SLEEPQ_FINAL_QUESTIONNAIRE_001',
templateName: '최종 설문조사 리마인드',
priority: 'NORMAL',
buttons: [
{
buttonType: 'WL',
buttonName: '최종 설문하러 가기',
buttonUrl: 'sleepq://final-questionnaire'
}
]
},
'TREATMENT_END_WARNING': {
templateId: 'SLEEPQ_TREATMENT_END_001',
templateName: '치료 종료 7일 전 안내',
priority: 'NORMAL',
buttons: [
{
buttonType: 'WL',
buttonName: '문의하기',
buttonUrl: 'sleepq://contact'
}
]
},
'TREATMENT_COMPLETION': {
templateId: 'SLEEPQ_TREATMENT_COMPLETION_001',
templateName: '치료 완료 안내',
priority: 'NORMAL',
buttons: [
{
buttonType: 'WL',
buttonName: '슬립큐 재처방받기',
buttonUrl: 'https://soldoc.co.kr'
},
{
buttonType: 'WL',
buttonName: '문의하기',
buttonUrl: 'sleepq://contact'
}
]
}
};
// Step 3: 매핑 결과 반환
const mapping = templateMappings[$input.eventType];
if (!mapping) {
throw new Error(`Unknown event type: ${$input.eventType}`);
}
return {
templateId: mapping.templateId,
templateName: mapping.templateName,
priority: mapping.priority,
buttons: mapping.buttons,
userId: $input.userId,
templateData: $input.templateData
};
3. User Data Enrichment
// Step 3: 사용자 정보 보강 (User 도메인 API 호출)
const userInfo = await $http.get(`/api/v1/users/${$input.userId}/profile`);
return {
...previous_step_output,
phoneNumber: userInfo.phoneNumber,
userName: userInfo.name || '고객',
templateData: {
...previous_step_output.templateData,
userName: userInfo.name || '고객'
}
};
4. Direct Notification API Call
// Step 4: Direct 알림톡 API 호출
const response = await $http.post('/api/v1/notifications/alimtalk/direct', {
userId: $input.userId,
templateId: $input.templateId,
phoneNumber: $input.phoneNumber,
templateData: $input.templateData,
buttons: $input.buttons,
deduplicationKey: `${$input.eventType}_${$input.userId}_${new Date().toISOString().split('T')[0]}`,
priority: $input.priority
});
// Step 5: 결과 로깅
if (response.success) {
console.log(`✅ 알림톡 발송 성공: ${$input.userId} (${$input.templateId})`);
} else {
console.error(`❌ 알림톡 발송 실패: ${$input.userId} (${response.errorMessage})`);
}
return response;
🔧 기획자를 위한 템플릿 관리 가이드
새로운 메시지 추가 프로세스
1단계: NHN Cloud Console에서 템플릿 생성
- NHN Cloud Console 접속
- 알림톡 템플릿 생성
- 심의 신청 및 승인 대기
- 승인 완료 시
templateId확보
2단계: n8n 워크플로우 업데이트
- n8n 관리 페이지 접속
- "User Engagement Template Mapping" 워크플로우 선택
templateMappings객체에 새로운 매핑 추가:
'NEW_MESSAGE_TYPE': {
templateId: 'SLEEPQ_NEW_001', // NHN에서 발급받은 ID
templateName: '새로운 메시지 이름',
priority: 'HIGH', // LOW, NORMAL, HIGH, URGENT
buttons: [ // 선택사항
{
buttonType: 'WL', // WL(웹링크), AL(앱링크) 등
buttonName: '버튼 이름',
buttonUrl: 'sleepq://some-action'
}
]
}
- 워크플로우 저장 및 활성화
3단계: 이벤트 발송 테스트
- 해당 도메인에서 이벤트 발송
- n8n 로그에서 매핑 결과 확인
- 실제 알림톡 발송 결과 확인
기존 메시지 수정
템플릿 내용 변경
- NHN Cloud Console에서 템플릿 수정
- 심의 재신청 (필요한 경우)
- n8n 워크플로우는 수정 불필요 ✅
버튼 변경
- n8n 워크플로우에서 해당 템플릿의
buttons배열 수정 - 즉시 적용 (배포 불필요) ✅
우선순위 변경
- n8n 워크플로우에서
priority값 변경 - 즉시 적용 ✅
📊 템플릿 매핑 현황 관리
n8n 내부 모니터링
// 템플릿 사용 현황 로깅
const templateUsageLog = {
eventType: $input.eventType,
templateId: mapping.templateId,
userId: $input.userId,
timestamp: new Date().toISOString(),
success: response.success
};
// 외부 모니터링 시스템으로 전송 (선택사항)
await $http.post('/api/v1/analytics/template-usage', templateUsageLog);
템플릿 상태 체크
// 주기적으로 템플릿 상태 확인 (별도 워크플로우)
const templateCheck = await $http.get(`/api/v1/notifications/alimtalk/templates/${templateId}/status`);
if (!templateCheck.isActive || templateCheck.approvalStatus !== 'APPROVED') {
// Slack 또는 이메일로 알림
await $http.post('/webhook/slack', {
text: `⚠️ 알림톡 템플릿 비활성화됨: ${templateId} (${templateCheck.approvalStatus})`
});
}
🎯 장점
기획자 관점
- ✅ 실시간 변경: 코드 배포 없이 즉시 템플릿 수정
- ✅ 직관적 관리: n8n GUI에서 시각적으로 매핑 관리
- ✅ 빠른 A/B 테스트: 템플릿 변경 후 즉시 테스트 가능
개발자 관점
- ✅ 관심사 분리: 비즈니스 로직과 기술 구현 분리
- ✅ 유지보수성: 템플릿 변경으로 인한 코드 수정 불필요
- ✅ 확장성: 새로운 채널 추가 시에도 n8n에서 관리
운영 관점
- ✅ 장애 대응: 템플릿 문제 시 n8n에서 즉시 대체 템플릿으로 변경
- ✅ 모니터링: n8n 로그로 템플릿 사용 현황 추적
- ✅ 롤백: 문제 발생 시 이전 버전으로 즉시 되돌리기
🔄 Migration Plan
현재 → 목표
- 도메인 → Strategy → TemplateMapper (하드코딩) → NHN API
+ 도메인 → n8n Webhook → Template Mapping (GUI) → Direct API → NHN API
단계별 이관
- Phase 1: Direct API 구현 완료 ✅
- Phase 2: n8n 워크플로우 템플릿 매핑 구현
- Phase 3: 기존 Strategy 제거 및 Direct API 전환
- Phase 4: 기획자 교육 및 권한 이양
📝 실제 API 명세
Direct 알림톡 API
// POST /api/v1/notifications/alimtalk/direct
{
"userId": "user123",
"templateId": "SLEEPQ_RTIB_PRE_001", // n8n에서 결정
"phoneNumber": "010-1234-5678",
"templateData": {
"userName": "홍길동",
"nextRtibDay": 8,
"currentDay": 7
},
"buttons": [
{
"buttonType": "WL",
"buttonName": "지금 수면기록하기",
"buttonUrl": "sleepq://sleep-log"
}
],
"deduplicationKey": "rtib_pre_user123_2025-08-20",
"priority": "HIGH"
}
Direct 푸시 알림 API
// POST /api/v1/notifications/push/direct
{
"userId": "user123",
"title": "슬립큐",
"body": "홍길동님, 수면 목표 시간 처방까지 하루 남았어요!",
"deepLink": "sleepq://sleep-log",
"icon": "sleepq_icon",
"badge": 1,
"data": {
"type": "rtib_pre_notification",
"nextRtibDay": 8
},
"deduplicationKey": "rtib_pre_push_user123_2025-08-20"
}
🎉 결론
이제 기획자가 n8n GUI에서 자유롭게 템플릿 매핑을 관리할 수 있습니다!
- 🚀 새로운 메시지: NHN 승인 → n8n 매핑 추가 → 즉시 사용
- 🔧 기존 메시지 수정: n8n에서 클릭 몇 번으로 즉시 변경
- 📊 A/B 테스트: 다른 템플릿 ID로 쉽게 전환
- 🛡️ 장애 대응: 문제 템플릿 즉시 비활성화 또는 대체
개발자는 더 이상 템플릿 매핑 변경으로 인한 배포를 하지 않아도 됩니다! ✨