다중 채널 알림 시스템: 템플릿, 재시도, 선호 설정
이메일, SMS, Telegram을 한 곳에서 템플릿·전송 상태·재시도·사용자 선호에 맞춰 일관되게 처리하는 다중 채널 알림 시스템 설계 가이드.

단일 알림 시스템이 해결하는 문제
이메일, SMS, Telegram을 별도 기능으로 만들면 금방 균열이 생깁니다. 같은 알림이 다른 문구, 다른 시점, 그리고 누가 받는지에 대한 서로 다른 규칙을 갖게 됩니다. 그러면 운영팀은 이메일 제공자, SMS 게이트웨이, 봇 로그에 각각 다른 진실을 쫓아야 합니다.
다중 채널 알림 시스템은 알림을 세 개의 통합이 아니라 하나의 제품으로 취급함으로써 이 문제를 해결합니다. 하나의 이벤트(비밀번호 재설정, 결제 완료, 서버 장애)가 발생하면 시스템은 템플릿, 사용자 선호, 전송 규칙을 바탕으로 채널별 전달 방식을 결정합니다. 채널마다 형식은 달라질 수 있지만 의미, 데이터, 추적은 일관되게 유지됩니다.
대부분 팀은 어떤 채널로 시작했든 동일한 기반이 필요합니다: 변수와 버전 관리를 갖춘 템플릿, 전송 상태 추적(“보냄, 전달, 실패, 이유”), 합리적인 재시도와 폴백, 동의와 조용 시간(quiet hours)을 반영한 사용자 선호, 그리고 운영팀이 추정하지 않고 무슨 일이 있었는지 볼 수 있는 감사 기록입니다.
성공은 좋은 의미로 지루합니다. 메시지는 예측 가능하고: 적절한 사람이 적절한 내용을 적절한 시간에, 허용한 채널을 통해 받습니다. 문제가 생기면 모든 시도가 명확한 상태와 이유 코드로 기록되어 있어 문제 해결이 단순합니다.
예를 들어 "새 로그인" 알림을 생각해보세요. 한 번 만들고 동일한 사용자, 디바이스, 위치 데이터를 채워 이메일로는 상세를, SMS로는 긴급성을, Telegram으로는 빠른 확인을 전송합니다. SMS 제공자가 타임아웃을 내면 시스템은 일정에 따라 재시도하고 타임아웃을 기록하며 알림을 버리지 않고 다른 채널로 폴백할 수 있습니다.
핵심 개념과 간단한 데이터 모델
다중 채널 알림 시스템은 "왜 알리는가"와 "어떻게 전달하는가"를 분리하면 관리하기 쉬워집니다. 즉, 공통 객체 집합과 실제로 다를 때만 채널별 세부를 두세요.
먼저 **이벤트(event)**로 시작하세요. 이벤트는 order_shipped나 password_reset 같은 이름 있는 트리거입니다. 이름은 일관되게: 소문자, 언더스코어, 경우에 따라 과거형을 사용하세요. 이벤트는 템플릿과 선호 규칙이 의존하는 안정된 계약으로 다루세요.
하나의 이벤트에서 알림(notification) 레코드를 생성합니다. 이는 사용자에게 보이는 의도: 누구에게, 무슨 일이 있었는지, 콘텐츠를 렌더링하는 데 필요한 데이터(주문 번호, 배송일, 재설정 코드)입니다. 여기에는 user_id, event_name, locale, priority, scheduled_at 같은 공통 필드를 저장하세요.
그다음 채널별로 **메시지(message)**로 나눕니다. 하나의 알림은 채널별로 0~3개의 메시지를 만들 수 있습니다(이메일, SMS, Telegram). 메시지는 목적지(이메일 주소, 전화번호, Telegram chat_id), template_id, 렌더된 내용(이메일의 제목/본문, SMS의 짧은 텍스트) 같은 채널별 필드를 담습니다.
마지막으로 각 전송을 **전송 시도(DeliveryAttempt)**로 추적합니다. 시도에는 provider request_id, 타임스탬프, 응답 코드, 정규화된 상태가 포함됩니다. 사용자가 "받지 못했어요"라고 말할 때 확인하는 부분입니다.
간단한 모델은 보통 네 개의 테이블 또는 컬렉션에 맞습니다:
- Event (허용된 이벤트 이름과 기본값 카탈로그)
- Notification (사용자 의도별 하나)
- Message (채널별 하나)
- DeliveryAttempt (시도별 하나)
초기에 멱등성(idempotency)을 계획하세요. 각 알림에 (event_name, user_id, external_ref) 같은 결정론적 키를 주어 업스트림 시스템의 재시도가 중복을 만들지 않게 하세요. 워크플로 단계가 재실행되면 멱등성 키가 두 번 SMS가 가는 것을 막아줍니다.
감사에 필요한 것만 장기 보관하세요(이벤트, 알림, 최종 상태, 타임스탬프). 단기 전송 큐와 원시 제공자 페이로드는 운영과 문제 해결에 필요한 기간만 보관하세요.
실용적인 엔드투엔드 플로우(단계별)
다중 채널 알림 시스템은 "무엇을 보낼지 결정하는 것"을 "전송하는 것"과 분리할 때 가장 잘 동작합니다. 이렇게 하면 애플리케이션 속도가 빠르고 실패를 처리하기 쉬워집니다.
실용적인 흐름은 다음과 같습니다:
-
이벤트 생산자가 알림 요청을 생성합니다. 이는
password_reset,invoice_paid,ticket_updated같은 요청일 수 있습니다. 요청에는 사용자 ID, 메시지 유형, 컨텍스트 데이터(주문 번호, 금액, 담당자 이름)가 포함됩니다. 감사 추적을 위해 요청을 즉시 저장하세요. -
라우터가 사용자 및 메시지 규칙을 로드합니다. 사용자 선호(허용 채널, 옵트인, 조용 시간)와 메시지 규칙(예: 보안 알림은 이메일 먼저 시도)을 조회합니다. 라우터가 Telegram, 그다음 SMS, 그다음 이메일 같은 채널 계획을 결정합니다.
-
시스템이 채널별 전송 작업을 큐에 넣습니다. 각 작업에는 템플릿 키, 채널, 변수들이 포함됩니다. 작업을 큐에 넣어 사용자 동작이 전송에 의해 차단되지 않게 합니다.
-
채널 워커가 제공자 통해 전달합니다. 이메일은 SMTP나 이메일 API로, SMS는 SMS 게이트웨이로, Telegram은 봇을 통해 전달됩니다. 워커는 멱등성 있게 설계되어 동일 작업을 재시도해도 중복 전송이 발생하지 않아야 합니다.
-
상태 업데이트는 한곳으로 흘러 들어옵니다. 워커는 queued, sent, failed, 가능하면 delivered 상태를 기록합니다. 제공자가 "accepted"만 응답하면 그 또한 기록하고 delivered와 구분해 다루세요.
-
폴백과 재시도는 같은 상태에서 실행됩니다. Telegram이 실패하면 라우터(또는 재시도 워커)가 컨텍스트를 잃지 않고 다음에 SMS를 예약할 수 있습니다.
예: 사용자가 비밀번호를 변경하면 백엔드가 사용자와 IP 주소를 포함한 하나의 요청을 발행합니다. 라우터는 사용자가 Telegram을 선호하지만 조용 시간으로 인해 밤에는 차단되어 이메일을 지금, Telegram을 아침에 예정하고 둘을 동일한 알림 레코드 아래에서 추적합니다.
AppMaster에 구현한다면 요청, 작업, 상태 테이블을 Data Designer에 두고 라우팅과 재시도 로직을 Business Process Editor에서 표현하며 전송은 비동기로 처리해 UI가 반응성을 유지하게 하세요.
채널 간에 작동하는 템플릿 구조
좋은 템플릿 시스템은 한 가지 아이디어에서 시작합니다: 당신은 이벤트에 대해 알리는 것이지 "이메일을 보내는 것"이나 "SMS를 보내는 것"이 아닙니다. 이벤트당 하나의 템플릿을 만들고 그 아래에 채널별 변형을 저장하세요(Password reset, Order shipped, Payment failed 등).
모든 채널 변형에서 동일한 변수를 유지하세요. 이메일이 first_name과 order_id를 사용하면 SMS와 Telegram도 정확히 같은 이름을 사용해야 합니다. 그래야 한 채널은 정상인데 다른 채널은 빈칸이 되는 미묘한 버그를 피할 수 있습니다.
반복 가능한 간단한 템플릿 형태
각 이벤트에 대해 채널별로 소규모 필드 집합을 정의하세요:
- 이메일: subject, preheader(선택), HTML 본문, 텍스트 대체
- SMS: 일반 텍스트 본문
- Telegram: 일반 텍스트 본문, 선택적 버튼 또는 짧은 메타데이터
채널마다 바뀌는 것은 포맷뿐이며 의미는 같아야 합니다.
SMS는 짧기 때문에 특별 규칙이 필요합니다. 내용이 너무 길 때 어떻게 할지 미리 결정하세요: 문자 수 제한을 정하고 잘라내기 규칙(잘라내고 ... 추가하거나 선택적 줄을 먼저 제거)을 선택하고 긴 URL과 불필요한 문장 부호를 피하며 핵심 행동(코드, 마감, 다음 단계)을 앞에 배치하세요.
비즈니스 로직을 복사하지 않는 로케일 처리
언어는 별도 워크플로가 아니라 매개변수로 다루세요. 이벤트와 채널별로 번역을 저장하고 동일한 변수로 렌더링하세요. "Order shipped" 로직은 그대로 두고 제목과 본문만 로케일별로 바뀝니다.
미리보기 모드는 큰 가치를 줍니다. 샘플 데이터(긴 이름 같은 엣지 케이스 포함)로 템플릿을 렌더링해 지원팀이 이메일, SMS, Telegram 변형을 실제로 검증할 수 있게 하세요.
신뢰할 수 있고 디버그 가능한 전송 상태
나중에 한 질문에 답할 수 있어야 합니다: 이 알림에 무슨 일이 일어났는가? 좋은 다중 채널 알림 시스템은 의도한 메시지와 각 전송 시도를 분리합니다.
이메일, SMS, Telegram 전반에서 같은 의미를 갖는 소수의 공통 상태부터 시작하세요:
- queued: 시스템에 수락되어 워커를 기다리는 상태
- sending: 전송 시도가 진행 중인 상태
- sent: 제공자 API에 성공적으로 전달된 상태
- failed: 조치 가능한 오류로 시도가 종료된 상태
- delivered: 가능한 경우 사용자가 도달했다는 증거가 있는 상태
이 상태들은 메시지 메인 레코드에 유지하되 모든 시도를 히스토리 테이블에 기록하세요. 그 히스토리가 디버깅을 쉽게 만듭니다: 시도 #1은 타임아웃으로 실패했고 시도 #2는 성공했거나, SMS는 잘 전송됐지만 이메일은 계속 바운스되었다는 식으로요.
시도별로 저장할 항목
제공자 응답을 정규화해 제공자들이 다른 단어를 써도 검색하고 이슈를 그룹화할 수 있게 하세요.
- provider_name과 provider_message_id
- response_code(예: TIMEOUT, INVALID_NUMBER, BOUNCED 같은 정규화 코드)
- raw_provider_code와 raw_error_text(지원 케이스용)
- started_at, finished_at, duration_ms
- channel(email, sms, telegram)과 destination(마스킹된 형태)
부분적 성공을 대비하세요. 하나의 알림이 세 채널 메시지를 만들고 공통의 parent_id와 비즈니스 컨텍스트(order_id, ticket_id, alert_type)를 가질 수 있습니다. SMS는 전송됐지만 이메일이 실패하면 세 개의 무관한 인시던트가 아니라 한 장소에서 전체 이야기를 보길 원합니다.
"delivered"의 실제 의미
"Sent"는 "delivered"가 아닙니다. Telegram은 API가 메시지를 수용했는지만 알 수 있을 수 있습니다. SMS와 이메일은 종종 웹후크나 제공자 콜백에 따라 전달 여부가 결정되며 모든 제공자가 동일하게 신뢰할 수는 없습니다.
채널별로 delivered의 정의를 미리 정하세요. 가능하면 웹후크로 확인된 전달을 사용하고, 그렇지 않으면 delivered를 알 수 없음으로 처리하고 sent로 보고하세요. 그래야 보고가 정직하고 지원 답변이 일관됩니다.
재시도, 폴백, 중단 시점
재시도는 알림 시스템에서 자주 실수하는 부분입니다. 너무 빨리 재시도하면 폭풍이 생기고, 영원히 재시도하면 중복과 지원 문제를 만듭니다. 목표는 간단합니다: 실제로 성공 가능성이 있을 때만 다시 시도하고, 의미 없을 때는 중단하세요.
실패를 분류하는 것부터 시작하세요. 이메일 제공자의 타임아웃, SMS 게이트웨이의 502, 일시적 Telegram API 오류는 보통 재시도 가능한 오류입니다. 잘못된 이메일 주소, 유효성 검증에 실패한 전화번호, 봇을 차단한 Telegram 채팅은 재시도해도 소용이 없습니다. 이들을 동일하게 다루면 비용을 낭비하고 로그를 채웁니다.
실용적인 재시도 계획은 상한이 있고 백오프를 사용합니다:
- 시도 1: 즉시 전송
- 시도 2: 30초 후
- 시도 3: 2분 후
- 시도 4: 10분 후
- 경보의 경우 최대 유효기간(예: 30~60분) 지나면 중지
중단은 데이터 모델에 명확히 반영되어야 합니다. 재시도 한도를 넘으면 메시지를 dead-letter(또는 failed-permanently)로 표시하세요. 마지막 오류 코드와 짧은 오류 메시지를 유지해 지원팀이 추정 없이 조치할 수 있게 하세요.
성공 후 반복 전송을 막으려면 멱등성을 구현하세요. 논리적 메시지당(보통 notification_id + user_id + channel) 멱등성 키를 만들어 제공자가 늦게 응답해도 두 번째 시도는 중복으로 인식되어 건너뛰게 하세요.
폴백은 자동 공황 상태가 아니라 의도적으로 설계되어야 합니다. 심각도와 시간에 따라 에스컬레이션 규칙을 정의하세요. 예: 비밀번호 재설정은 개인정보 위험 때문에 다른 채널로 폴백하면 안 되지만, 운영 인시던트 경고는 두 번의 Telegram 실패 후 SMS를 시도하고 10분 후 이메일을 시도할 수 있습니다.
사용자 선호, 동의, 조용 시간
사람을 존중할 때 시스템이 "스마트"하게 느껴집니다. 가장 단순한 방법은 사용자가 알림 유형별로 채널을 선택하게 하는 것입니다. 많은 팀은 보안, 계정, 제품, 마케팅처럼 유형을 묶어 규칙과 법적 요구사항을 구분합니다.
채널이 없을 때도 작동하는 선호 모델로 시작하세요. 사용자는 이메일은 있지만 전화번호가 없을 수 있고, Telegram을 아직 연결하지 않았을 수도 있습니다. 그런 상황을 오류로 처리하지 말고 정상으로 다루세요.
대부분 시스템은 컴팩트한 필드 집합이 필요합니다: 알림 유형(보안, 마케팅, 청구), 유형별 허용 채널(이메일, SMS, Telegram), 채널별 동의(날짜/시간, 출처, 증거 필요 시), 채널별 옵트아웃 사유(사용자 선택, 이메일 반송, "STOP" 회신), 그리고 사용자의 시간대 포함 조용 시간 규칙(시작/종료).
조용 시간은 시스템이 자주 실패하는 지점입니다. 사용자의 시간대(단순 오프셋이 아닌)를 저장해 섬머타임 변화로 놀라지 않게 하세요. 메시지가 조용 시간에 예약되면 실패 처리하지 말고 연기(deferred)로 표시하고 다음 허용 발송 시간을 선택하세요.
기본값은 중요합니다, 특히 중요한 알림의 경우. 일반적인 접근은: 보안 알림은 조용 시간을 무시(그러나 필요한 법적 하드 옵트아웃은 존중), 비핵심 업데이트는 조용 시간과 채널 선택을 따릅니다.
예: 비밀번호 재설정은 허용된 가장 빠른 채널로 즉시 발송되어야 합니다. 주간 요약은 아침까지 기다리고 SMS는 사용자가 명시적으로 활성화하지 않으면 건너뜁니다.
운영: 모니터링, 로그, 지원 워크플로
알림이 이메일, SMS, Telegram을 오가면 지원팀은 빠르게 답을 필요로 합니다: 보냈나, 도달했나, 무엇이 실패했나? 다중 채널 알림 시스템은 여러 제공자를 써도 조사할 수 있는 한 곳처럼 느껴져야 합니다.
누구나 사용할 수 있는 간단한 관리자 뷰로 시작하세요. 사용자, 이벤트 유형, 상태, 시간 창으로 검색 가능하게 하고 최신 시도를 먼저 보여주세요. 각 행은 채널, 제공자 응답, 다음 예정된 작업(재시도, 폴백, 중지)을 보여줘야 합니다.
문제를 초기에 잡아내는 지표
장애는 흔히 하나의 깔끔한 오류로 나타나지 않습니다. 소수의 수치를 추적하고 정기적으로 검토하세요:
- 채널별 전송률(분당 메시지)
- 제공자 및 오류 코드별 실패율
- 재시도율(몇 건이 두 번째 시도를 필요로 했는지)
- 전달 시간(queued에서 delivered까지, p50 및 p95)
- 드롭률(사용자 선호, 동의 결여, 최대 재시도로 중지된 비율)
모든 것을 연관지어 보세요. 이벤트가 발생할 때 correlation ID를 생성하고 템플릿, 큐잉, 제공자 호출, 상태 업데이트 전반에 전달하세요. 로그에서 그 ID는 여러 채널로 퍼진 이벤트를 추적하는 실(thread)이 됩니다.
지원 친화적 재전송(replay) 가이드
재전송은 필수지만 사람에게 스팸을 보내거나 이중 청구를 하지 않도록 가드레일이 필요합니다. 안전한 재전송 흐름은 보통 다음을 의미합니다: 전체 이벤트 배치가 아닌 특정 메시지 ID만 재전송, 전송 전에 정확한 템플릿 버전과 렌더된 콘텐츠를 보여주기, 재전송 사유와 실행자 기록, 이미 전달된 메시지는 명시적으로 강제로 하지 않는 한 차단, 사용자 및 채널별 속도 제한 적용.
알림의 보안 및 개인정보 기본
다중 채널 알림 시스템은 개인 데이터(이메일, 전화번호, 채팅 ID)를 다루고 로그인, 결제, 지원 같은 민감한 순간을 포함합니다. 모든 메시지 본문과 로그 라인이 나중에 보일 수 있다고 가정하고 저장 범위와 볼 수 있는 사람을 제한하세요.
가능하면 템플릿에 민감한 데이터를 넣지 마세요. 템플릿은 재사용 가능하고 단순해야 합니다: "Your code is {{code}}" 같은 형태는 괜찮지만 전체 계정 상세, 긴 토큰, 계정을 탈취할 수 있는 정보는 피하세요. 일회용 코드나 재설정 토큰이 꼭 필요하면 원시값이 아닌(예: 해시와 만료 시간) 검증에 필요한 정보만 저장하세요.
알림 이벤트를 저장하거나 로그할 때는 적극적으로 마스킹하세요. 지원 담당자는 보통 코드 자체가 아니라 코드가 전송되었는지를 알면 됩니다. 전화번호와 이메일도 전송에는 전체 값이 필요하지만 대부분 화면에서는 마스킹된 버전을 보여주세요.
대부분 사고를 막는 최소한의 통제
- 역할 기반 접근 제어: 메시지 본문과 전체 수신자 정보를 볼 수 있는 역할을 제한
- 디버그 접근과 지원 접근 분리: 문제 해결이 개인정보 유출로 이어지지 않게
- 웹훅 보호: 서명된 콜백이나 공유 비밀 사용, 타임스탬프 검증, 알 수 없는 출처 거부
- 저장 시 민감 필드 암호화, 전송 시 TLS 사용
- 보존 정책 정의: 상세 로그는 짧게, 이후에는 집계나 해시된 식별자만 보관
실용적 예: 비밀번호 재설정 SMS가 실패해 Telegram으로 폴백할 때는 시도, 제공자 상태, 마스킹된 수신자만 저장하되 재설정 링크 원문은 DB나 로그에 저장하지 마세요.
예시 시나리오: 한 알림, 세 채널, 실제 결과
고객 마야(Maya)는 Password reset과 New login 두 알림 유형을 사용하도록 설정했고 Telegram을 우선, 그다음 이메일을 선호합니다. SMS는 비밀번호 재설정에만 폴백으로 허용합니다.
어느 저녁 마야가 비밀번호 재설정을 요청합니다. 시스템은 안정된 ID를 가진 단일 알림 레코드를 만들고 현재 선호에 따라 채널 시도를 확장합니다.
마야가 보는 것은 단순합니다: Telegram 메시지가 몇 초 내에 도착해 짧은 재설정 코드와 만료 시간을 줍니다. Telegram이 성공했기 때문에 다른 것은 도착하지 않습니다.
시스템이 기록하는 것은 더 상세합니다:
- Notification: type=PASSWORD_RESET, user_id=Maya, template_version=v4
- Attempt #1: channel=TELEGRAM, status=SENT then DELIVERED
- 정책상(첫 성공 후 중지) 이메일이나 SMS 시도는 생성되지 않음
그 주 후에 새로운 디바이스에서 New login 알림이 트리거됩니다. 마야는 로그인 알림을 Telegram만 허용해 두었습니다. 시스템은 Telegram을 보내지만 제공자가 일시적 오류를 반환합니다. 시스템은 백오프를 두고 두 번 재시도한 뒤 FAILED로 표시하고 중지합니다(이 알림 유형은 폴백 불허).
진짜 실패 사례: 마야가 여행 중 또 다른 비밀번호 재설정을 요청했습니다. Telegram을 보냈지만 60초 내에 전달이 확인되지 않으면 SMS 폴백이 설정되어 있습니다. SMS 제공자가 타임아웃을 기록했습니다. 시스템은 타임아웃을 기록하고 한 번 재시도했으며 두 번째 시도가 성공했습니다. 마야는 1분 후 SMS 코드를 받았습니다.
마야가 지원에 연락하면 지원팀은 사용자와 시간 창으로 검색해 시도 이력: 타임스탬프, 제공자 응답 코드, 재시도 횟수, 최종 결과를 즉시 볼 수 있습니다.
빠른 체크리스트, 흔한 실수, 다음 단계
다중 채널 알림 시스템은 두 가지 질문에 빠르게 답할 수 있을 때 운영하기 쉽습니다: "정확히 무엇을 보내려고 했는가?"와 "그 뒤에 무슨 일이 있었는가?" 채널이나 이벤트를 추가하기 전에 이 체크리스트를 사용하세요.
빠른 체크리스트
- 명확한 이벤트 이름과 소유자(예:
invoice.overdue는 billing 소유) - 템플릿 변수는 한 번 정의(필수 vs 선택, 기본값, 형식 규칙)
- 상태 사전 합의(created, queued, sent, delivered, failed, suppressed) 및 각 상태의 의미
- 재시도 한도와 백오프(최대 시도, 간격, 중지 규칙)
- 보존 규칙(메시지 본문, 제공자 응답, 상태 이력을 얼마나 보관할지)
한 가지만 하려면, sent와 delivered의 차이를 문장으로 적어두세요. Sent는 시스템이 한 행동입니다. Delivered는 제공자가 보고한 것(지연되거나 누락될 수 있음)입니다. 이 둘을 섞으면 지원팀과 이해관계자가 혼란스러워집니다.
피해야 할 흔한 실수
- sent를 성공으로 처리해 과장된 전달률을 보고하는 것
- 채널별 템플릿을 방치해 이메일, SMS, Telegram이 서로 모순되게 되는 것
- 제공자가 타임아웃했지만 나중에 수락한 경우 멱등성 없이 재시도해 중복을 만드는 것
- 영원히 재시도해 일시적 장애를 소음으로 만드는 것
- "혹시 몰라"라는 이유로 로그와 상태 기록에 과도한 개인 데이터를 저장하는 것
하나의 이벤트와 하나의 기본 채널로 시작하고 두 번째 채널은 폴백으로 추가하세요(병렬 발송으로 시작하지 마세요). 흐름이 안정되면 이벤트별로 확장하고 템플릿과 변수를 공유해 메시지 일관성을 유지하세요.
핸드코딩 없이 이걸 구축하고 싶다면 AppMaster (appmaster.io)은 핵심 부분에 실용적입니다: Data Designer에서 이벤트, 템플릿, 전송 시도를 모델링하고 Business Process Editor에서 라우팅과 재시도를 구현하며 이메일, SMS, Telegram을 통합해 상태 추적을 한 곳에 유지하세요.


