---
title: 본사 운영자 알림 템플릿 관리 기술 매뉴얼
---

# GLOBEAU 뷰티 플랫폼 - 본사 운영자 알림 템플릿 관리 기술 매뉴얼
(Technical Specification & Operations Manual for Notification Templates)

---

## 문서 제어 및 개정 이력 (Document Control & Revision History)

### 기본 제어 정보
| 항목 | 내용 |
|---|---|
| **문서명** | GLOBEAU 플랫폼 본사 운영자 알림(메시지) 템플릿 관리 기술 매뉴얼 |
| **현재 버전** | v1.2.0 |
| **최종 개정일** | 2026-10-16 |
| **작성/관리자** | 본사 시스템개발팀 |
| **적용 시스템** | GLOBEAU 본사 관리자 (/admin) - 메시지/알림 관리 |
| **문서 목적** | 플랫폼 내 자동·수동 알림의 근간이 되는 알림 템플릿의 설계, 설정 레지스트리 관리, 관리자 UI/UX 제어, 백엔드 발송 파이프라인 연동 기준 정립 |
| **문서 상태** | 개발 및 운영 공식 표준 (Production Standard) |

### 개정 및 변경 이력
| 버전 | 개정일자 | 구분 | 주요 변경 내역 | 작업자 |
|:---:|:---:|:---:|---|:---:|
| **v1.2.0** | 2026-10-16 | 기능개선 | • NotificationTemplate 모델 다차원 배열 `htmlspecialchars()` 타입 예외 방어 및 1차원 문자열 보장<br>• 모바일 목업 실시간 프리뷰 및 반응형 인터랙션(EUC-KR 바이트 계산, 커서 위치 보존 삽입) 고도화<br>• 기술 문서 표준화(문서 제어 정보와 본문 챕터 번호 체계 분리) | 개발팀 |
| **v1.1.0** | 2026-10-14 | 리팩토링 | • `Setting` 모델 연동(캐시 자동 무효화 및 복수 관리자 번호 JSON 역직렬화 일괄 발송 구축)<br>• 단일 사건 식별키(`batch_id`) 연동 관제 로그(`notification_logs`) 적재 아키텍처 확립 | 개발팀 |
| **v1.0.0** | 2026-10-10 | 신규제정 | • 알림 템플릿 관리 시스템 초기 기술 매뉴얼 제정 | 개발팀 |

---

## 1. 시스템 개요 및 아키텍처 역할 (System Overview)

### 1.1 알림 시스템의 핵심 가치와 목적
GLOBEAU 플랫폼에서 알림 시스템은 단순한 텍스트 전달 도구가 아니라, **플랫폼 시스템 Bot, 일반 고객(사용자), 뷰티샵 점주(파트너), 본사 운영팀 간의 비즈니스 트랜잭션을 실시간으로 연결하고 조율하는 중추 소통 허브**입니다.

    [플랫폼 비즈니스 이벤트 발생 (입점신청, 예약, 결제)]
                            │
                            ▼
    ┌────────────────────────────────────────────────────────┐
    │       알림 템플릿 마스터 (Notification Template)       │
    │  - 발송 여부 통제 (is_active, is_web_notify)           │
    │  - 채널 결정 (ALIMTALK, SMS, LMS, EMAIL, WEB)          │
    │  - 본문 문구 조립 및 #{치환변수} 자동 매핑             │
    └────────────────────────────────────────────────────────┘
                            │
              ┌──────────────┴──────────────┐
              ▼                             ▼
    [일반 사용자 & 뷰티샵 점주]     [본사 통합 운영팀]
    - 마이페이지 웹 알림함 등록     - 실시간 접수/심사 통보 알림
    - 모바일 SMS/LMS/알림톡 수신   - 통합 관제 모니터링 로그 적재

### 1.2 알림 템플릿의 위치와 역할
알림 템플릿(`notification_templates`)은 백엔드 소스코드와 프론트엔드 발송 인터페이스 사이에서 **"무엇을(본문/제목), 누구에게(수신대상), 어떤 경로로(채널), 어떤 조건에서(온/오프)"** 보낼 것인가를 규정하는 최상위 발송 규칙 마스터입니다.
본사 운영자는 코드 재배포 없이 어드민 화면에서 템플릿의 문구를 수정하고 발송 여부를 즉시 통제할 수 있습니다.

---

## 2. 설정 파일 기반 중앙 레지스트리 (Configuration Architecture)

알림의 무결성을 유지하고 하드코딩으로 인한 오타 및 런타임 오류를 원천 차단하기 위해, 카테고리와 치환변수는 라라벨 설정 파일(`config/`)을 **단일 원천(Single Source of Truth, SSOT)**으로 삼아 운영됩니다.

### 2.1 알림 분류 및 수신 대상 레지스트리 (`config/notification_categories.php`)
업무 도메인에 따른 2단 계층 구조(대분류/소분류) 및 알림을 수신하는 대상 주체 목록을 중앙 정의합니다.

    return [
        // 1. 알림 수신 대상 (Target) 정의 - 모델 및 뷰에서 동적 연동
        'targets' => [
            'user'  => '일반회원',
            'shop'  => '뷰티샵 관리자',
            'admin' => '시스템 관리자',
        ],

        // 2. 알림 템플릿 업무 분류 (대분류 - 소분류)
        'categories' => [
            'user' => [
                'name' => '회원',
                'subcategories' => [
                    'welcome' => '회원가입 환영',
                    'leave'   => '회원 탈퇴',
                    'dormant' => '휴면 계정',
                    'profile' => '정보 변경',
                ],
            ],
            'auth' => [
                'name' => '인증',
                'subcategories' => [
                    'verify'        => '본인인증',
                    'find_id'       => '아이디 찾기',
                    'find_password' => '비밀번호 재설정',
                ],
            ],
            'shop' => [
                'name' => '뷰티샵',
                'subcategories' => [
                    'apply'   => '입점 신청',
                    'approve' => '입점 승인',
                    'reject'  => '입점 반려',
                    'cancel'  => '입점 취소',
                ],
            ],
            'reservation' => [
                'name' => '예약',
                'subcategories' => [
                    'apply'   => '예약 접수',
                    'confirm' => '예약 확정',
                    'cancel'  => '예약 취소',
                ],
            ],
        ],
    ];

### 2.2 치환변수 중앙 레지스트리 및 처리 메커니즘 (`config/notification_variables.php`)
치환변수는 시스템 비즈니스 데이터와 메시지 템플릿을 연결하는 핵심 매개체입니다.

#### 1. 치환변수 적용 가능 3대 영역 (Where)
정의된 치환변수(`#{변수명}`)는 알림 템플릿의 다음 3개 핵심 필드 어디에서나 독립적 또는 복합적으로 사용할 수 있습니다.

- **발송 제목 (`title`)**: LMS(장문문자) 서두 제목, 카카오 알림톡 강조 타이틀, EMAIL 제목에 동적 데이터 주입
  - 작성 예시: `[GLOBEAU] #{shop_name} 입점 신청이 완료되었습니다.`
  - 치환 결과: `[GLOBEAU] 글로뷰티 강남본점 입점 신청이 완료되었습니다.`
- **발송 본문 (`content`)**: 인앱 웹 알림 및 모바일 문자/알림톡 본문 전체에 동적 데이터 주입
  - 작성 예시: `#{user_name} 원장님, 신청하신 #{shop_name}(코드: #{shop_code}) 매장의 심사 접수가 완료되었습니다.`
  - 치환 결과: `홍길동 원장님, 신청하신 글로뷰티 강남본점(코드: SHOP-000015) 매장의 심사 접수가 완료되었습니다.`
- **이동 경로 (`action_url`)**: 웹 알림 클릭 시 이동할 URL 및 문자 메시지 하단에 첨부되는 바로가기 링크 생성
  - 작성 예시: `/shops/#{shop_code}/status`
  - 치환 결과: `https://globeau.kr/shops/SHOP-000015/status` (시스템 도메인 자동 결합)

#### 2. 치환변수 동작 및 처리 방식 (How)
- **운영자 입력 방식 (UI/UX)**:
  - 본사 어드민 템플릿 등록/수정 화면의 에디터 상단 **[치환변수 찾기 / 삽입]** 모달을 통해 검색 후 [삽입] 클릭 시, 에디터의 현재 커서 위치에 `#{변수명}` 형태로 정확하게 삽입됩니다.
  - 모달 닫힘 애니메이션이 완료되는 즉시 에디터로 포커스가 복원되며, 삽입된 변수 바로 뒤에 커서가 위치하여 연속적인 문구 작성이 가능합니다.
- **백엔드 치환 엔진 (Backend Parser)**:
  - 비즈니스 컨트롤러에서 `$notificationService->send(..., variables: ['shop_name' => '글로뷰티', ...])` 형태로 연관 배열을 전달합니다.
  - `NotificationTemplate` 모델의 파서(`parseTitle`, `parseContent`)와 `NotificationService`의 `resolveActionUrl`이 `strtr` 함수를 이용해 텍스트 내의 `#{변수명}`을 실제 데이터값으로 1:1 일괄 치환합니다.
  - 변수 키에 `#{}` 기호가 이미 포함되어 있든 순수 영문 키(`shop_name`)이든 상관없이 시스템이 정규화(`#{...}`)하여 안전하게 치환합니다.
- **미정의 변수 안전 방어 (Fail-Safe)**:
  - 컨트롤러에서 특정 변수값을 누락하여 전달하지 못한 경우(Null), 시스템 오류(Fatal Error)를 발생시키지 않고 공백(`""`) 또는 기본 대시(`"-"`)로 안전하게 대체 처리합니다.

#### 3. 설정 파일 구조 예시 (`config/notification_variables.php`)

    return [
        'groups' => [
            'common' => [
                'name' => '공통 변수',
                'variables' => [
                    '#{site_url}'    => ['label' => '플랫폼 기본 URL', 'example' => 'https://globeau.kr'],
                    '#{service_name}'=> ['label' => '서비스 브랜드명', 'example' => 'GLOBEAU'],
                    '#{cs_number}'   => ['label' => '고객센터 전화번호', 'example' => '1588-2845'],
                    '#{created_at}'  => ['label' => '신청/처리 일시', 'example' => '2026-10-15 14:00'],
                ],
            ],
            'user' => [
                'name' => '회원 관련',
                'variables' => [
                    '#{user_name}'   => ['label' => '회원 이름(고객명)', 'example' => '홍길동'],
                    '#{user_phone}'  => ['label' => '회원 휴대폰 번호', 'example' => '01012345678'],
                    '#{code}'        => ['label' => '인증번호 6자리', 'example' => '482910'],
                ],
            ],
            'shop' => [
                'name' => '뷰티샵 입점',
                'variables' => [
                    '#{shop_name}'   => ['label' => '신청 뷰티샵 매장명', 'example' => '글로뷰티 강남본점'],
                    '#{shop_code}'   => ['label' => '매장 고유 식별코드', 'example' => 'SHOP-000015'],
                    '#{reject_reason}'=>['label' => '입점 반려 사유', 'example' => '사업자등록증 이미지 식별 불가'],
                ],
            ],
            'reservation' => [
                'name' => '예약 관리',
                'variables' => [
                    '#{reserved_at}' => ['label' => '예약 방문 일시', 'example' => '2026-10-20 15:30'],
                    '#{menu_name}'   => ['label' => '예약 시술 프로그램', 'example' => '시그니처 수분케어'],
                ],
            ],
        ],
    ];

### 2.3 설정 파일 기반 운영의 장점 및 유지보수 규칙
1. **SSOT(단일 원천) 보장**: 모델 클래스나 컨트롤러 내부에 문자열 상수를 중복 선언하지 않으므로, 신규 변수나 카테고리 추가 시 config 파일만 수정하면 화면과 유효성 검증 규칙이 자동 동기화됩니다.
2. **배포 시 주의사항**: 라라벨 운영 환경에서는 성능 최적화를 위해 설정이 캐싱되므로, config 파일 수정 후 반드시 캐시 초기화 명령어를 실행해야 합니다.

    php artisan config:clear
    php artisan config:cache

---

## 3. 템플릿 식별 코드(Template Code) 표준 명명 규칙

모든 템플릿 식별 코드는 시스템 내 유일해야 하며, 코드 자체만으로 목적, 행위, 대상을 즉시 판별할 수 있도록 아래 표준 3단 구조를 엄격히 준수합니다.

### 3.1 표준 명명 형식
- **구조**: `[도메인 엔티티]_[행위/상태]_[수신 대상 주체]`
- **규격**: 영문 대문자, 숫자, 언더바(`_`)만 허용 (`/^[A-Z0-9_]+$/`)

### 3.2 구성 요소별 명명 가이드

| 구분 | 허용 명칭 예시 | 설명 |
|---|---|---|
| **도메인 (Entity)** | `SHOP`, `RESERVATION`, `USER`, `AUTH`, `PAYMENT` | 알림이 발생하는 업무 도메인 영역 |
| **행위 (Action)** | `APPLY`, `APPROVE`, `REJECT`, `CANCEL`, `CONFIRM`, `REMIND` | 해당 도메인에서 발생한 구체적인 비즈니스 이벤트 |
| **수신 대상 (Target)** | `USER`, `SHOP`, `ADMIN` | 알림을 전달받는 최종 수신 주체 |

### 3.3 표준 템플릿 코드 예시
- `SHOP_APPLY_ADMIN`: 뷰티샵 입점 신청 접수 시 시스템이 본사 최고관리자에게 발송하는 통보 알림
- `SHOP_APPLY_USER`: 뷰티샵 입점 신청 완료 시 시스템이 신청자(원장님)에게 발송하는 접수 확인 알림
- `SHOP_APPLY_APPROVE_USER`: 본사 심사 결과 입점이 최종 승인되었을 때 신청자에게 발송하는 승인 축하 알림
- `RESERVATION_CONFIRM_USER`: 뷰티샵 예약이 최종 확정되었을 때 고객에게 발송하는 상세 안내 알림
- `AUTH_VERIFY_CODE`: 회원가입 또는 본인확인 시 6자리 인증번호를 전송하는 1회성 알림

---

## 4. 관리자 UI/UX 기능 명세 (Admin Interface Specification)

### 4.1 템플릿 목록 화면 (`list.blade.php`)

#### 1. 수신 대상(Target) 탭 바 네비게이션
- `\App\Models\NotificationTemplate::getTargets()`와 연동되어 상단에 가로 스크롤 가능한 탭을 동적 렌더링합니다.
- [전체 보기], [일반회원], [뷰티샵 관리자], [시스템 관리자] 탭 클릭 시 `target` 쿼리스트링을 유지하며 즉시 필터링합니다.

#### 2. 실시간 검색 및 다중 상세 필터 (`admin_notification.js` 연동)
- **대분류 (`#categorySelect`)**: 대분류 선택 변경 시 소분류 셀렉트 박스(`subcategorySelect`)의 옵션을 라라벨 config 기반으로 동적 재렌더링하고 값을 초기화한 뒤 폼을 즉시 제출합니다.
- **소분류 (`#subcategorySelect`) / 채널 (`#typeSelect`) / 웹 알림 여부 (`#is_web_notifySelect`)**: 옵션 변경 즉시 필터링 폼이 자동 제출(`searchForm.submit()`)됩니다.
- **키워드 검색 (`#keywordInput`)**: 식별 코드, 발송 제목, 관리자 설명을 대상으로 부분 일치(`LIKE %keyword%`) 검색을 수행하며, 엔터 키 입력 시 즉시 실행됩니다.

#### 3. 2단 인라인 AJAX 토글 스위치 (실시간 제어)
화면 새로고침 없이 발송 정책을 즉각 변경하는 2개의 토글 스위치를 제공합니다.

| 구분 | 1순위: 발송 스위치 (`is_active`) | 2순위: 웹 알림 스위치 (`is_web_notify`) |
|:---:|---|---|
| **스위치 명칭** | `[●] 발송 ON` | `[●] 웹 ON` |
| **제어 속성** | `is_active` (Boolean) | `is_web_notify` (Boolean) |
| **핵심 역할** | 알림 템플릿의 전체 발송 파이프라인 가동 여부 | 사용자 인앱 알림함(`notifications`) 동시 적재 여부 |
| **OFF 시 동작** | 문자(SMS/LMS), 알림톡, 웹 알림 등 **모든 채널 전면 차단** | 외부 발송(문자/알림톡)만 진행, **웹 알림함에만 미적재** |
| **비동기 처리** | `PATCH .../toggle-active` (인라인 AJAX) | `PATCH .../toggle-web-notify` (인라인 AJAX) |

- **1순위: 발송 활성화 (`is_active`)**:
  - `PATCH /admin/notifications/templates/{id}/toggle-active` 통신 수행.
  - 비활성화(OFF) 시 외부 발송(문자/알림톡) 및 웹 알림함 적재가 모두 전면 차단됩니다.
- **2순위: 웹 알림함 동시 적재 (`is_web_notify`)**:
  - `PATCH /admin/notifications/templates/{id}/toggle-web-notify` 통신 수행.
  - 외부 발송과 별개로 사용자의 마이페이지 인앱 알림함에 안내 카드를 등록할지 여부를 독립 제어합니다.
  - 스위치 변경 시 목업 화면 상단의 미니 배지(`badge-web-{id}`) 디자인이 실시간(웹 ON/OFF)으로 동기화됩니다.

#### 4. 스마트폰 모바일 목업 그리드 렌더링
- 카드 갤러리 형태로 실제 모바일 화면을 축약 렌더링합니다.
- 채널별 테마 분기: `ALIMTALK`(카카오 노란색 테마), `EMAIL`(보라색 헤더 테마), `SMS/LMS`(모바일 기본 블루 테마).
- 본문 텍스트 길이 제약: 긴 템플릿의 경우 카드 레이아웃을 해치지 않도록 높이를 제한(`max-height`)하고 말줄임 처리하며, 전체 내용은 [수정] 화면에서 상세 열람할 수 있도록 유도합니다.

#### 5. 안전 삭제 처리 (Soft Delete)
- [삭제] 버튼 클릭 시 SweetAlert2 컨펌 창을 띄워 식별 코드를 강조 안내합니다.
- 확인 시 숨겨진 `#globalDeleteForm`을 통해 `DELETE` 메서드를 전송하며, 데이터는 물리 삭제되지 않고 휴지통(`deleted_at`)으로 안전하게 이동합니다.

---

### 4.2 템플릿 등록 및 수정 화면 (`create.blade.php`, `edit.blade.php`)

#### 1. 폼 기본 입력 및 식별 코드 제약
- **신규 등록 (`create`)**: 식별 코드를 직접 입력받으며, 자바스크립트가 소문자를 영문 대문자로 실시간 자동 변환합니다.
- **기존 수정 (`edit`)**: 템플릿 코드는 시스템 호출 키이므로 변조 방지를 위해 `readonly` 처리(수정 불가)됩니다.
- **대/소분류 셀렉트 박스**: 대분류 변경 시 소분류 항목이 비동기로 즉시 재구성됩니다.

#### 2. 치환변수 탐색 및 커서 자동 삽입 모달 (`variableSelectModal`)
운영자가 변수명을 외울 필요 없이 클릭 한 번으로 본문에 안전하게 삽입할 수 있는 인터페이스입니다.

![알림 치환변수 선택 모달](images/notification_template_manual_01/image.png "▲ [그림 4-1] 알림 템플릿 치환변수 탐색 및 삽입 모달")

- **엔터 키 폼 제출 방지 (중요)**:
  모달 컨테이너(`<div class="modal fade">`)를 반드시 템플릿 등록/수정용 `<form>` 태그 **바깥에 독립 배치**하여, 검색창에서 엔터를 입력하더라도 메인 폼이 서브밋되지 않도록 원천 차단했습니다.
- **커서 위치 보존 및 자동 이동**:
  본문 에디터(`textarea`)에서 포커스가 벗어나기 전 커서 시작 위치(`selectionStart`)와 끝 위치(`selectionEnd`)를 실시간 기억합니다.
  모달에서 [삽입] 클릭 시 보관된 위치에 변수를 삽입한 후, 부트스트랩 모달이 완전히 닫히는 시점(`hidden.bs.modal`)에 포커스를 복원하여 **삽입된 변수 바로 뒤에 커서가 깜빡이도록 처리**했습니다.

#### 3. 실시간 글자수 및 EUC-KR 바이트 계산기
- 본문 입력 시 한글은 2바이트, 영문/숫자/기호는 1바이트로 정밀 계산하여 화면에 표시합니다.
- 90바이트 초과 시 단문 SMS에서 장문 LMS로 자동 전환됨을 운영자에게 실시간 안내합니다.

#### 4. 웹 알림 연결 경로 (`action_url`) 입력 규칙 및 변환 메커니즘
- **본문과의 역할 분리**: 템플릿 본문(`content`)에는 복잡한 URL을 직접 기재하지 않고 순수 안내 문구만 작성하며, 이동 경로는 `action_url` 전용 필드에 분리 입력합니다.
- **입력 형식**:
  - 도메인 주소(`https://...`)를 제외한 **상대 경로(`/mypage/partner`)** 형태로 입력합니다.
  - 경로 내부에도 `#{shop_code}`, `#{reservation_id}` 등의 치환변수를 자유롭게 포함할 수 있습니다 (예: `/shops/#{shop_code}/status`).
- **화면 노출 제어**: 웹 알림 스위치(`inputIsWebNotify`)가 활성화(ON)된 상태일 때만 입력 컨테이너가 화면에 펼쳐집니다.

- **변환 처리 헬퍼 및 참조 경로 (How & Where)**:
  - **처리 위치**: `app/Services/NotificationService.php` 파일 내 `resolveActionUrl()` 메서드
  - **1단계 (변수 치환)**: 컨트롤러에서 넘겨준 `$variables` 연관 배열을 참조하여 URL 내 `#{key}` 태그를 실제 데이터값으로 1:1 치환합니다.
    - *예시*: `/shops/#{shop_code}/status` → `/shops/SHOP-000015/status`
  - **2단계 (절대 도메인 결합)**: 라라벨 글로벌 헬퍼 함수인 `url($actionUrl)`을 호출하여 절대 URL을 완성합니다.
    - *참조 대상*: 라라벨 `Illuminate\Routing\UrlGenerator` 엔진이 `config/app.php`의 `url` 설정값(`.env`의 `APP_URL`) 또는 현재 실행 중인 HTTP 요청 호스트 도메인을 참조합니다.
    - 이미 `http://` 또는 `https://`로 시작하는 절대 주소가 아니라면 자동으로 서비스 도메인을 결합합니다.
    - *예시*: `/shops/SHOP-000015/status` → `https://globeau.kr/shops/SHOP-000015/status`

- **최종 적용처**:
  - **인앱 웹 알림 (`SystemWebNotification`)**: `notifications` 테이블의 `data->url`에 주입되어 사용자 마이페이지 알림 상세 화면의 **[관련 페이지 바로가기]** 버튼 링크로 연결됩니다.
  - **모바일 문자 (SMS/LMS)**: 문자는 버튼이 없으므로 본문 최하단에 `\n\n▶ 바로가기: https://globeau.kr/...` 형태로 자동 덧붙여 전송됩니다.
  - **통합 관제 로그 (`notification_logs`)**: `action_url` 컬럼에 도메인이 결합된 완성 URL이 저장되어, 본사 운영자가 관제 화면에서 클릭 한 번으로 해당 업무 화면으로 이동(Jump Link)할 수 있습니다.

#### 5. 실시간 모바일 목업 프리뷰 동기화
좌측 폼에서 제목, 본문, 채널, 분류, 이동 경로, 수신 대상을 입력하거나 수정하는 즉시, 우측 스마트폰 목업 스크린에 동일하게 렌더링되어 발송 전 최종 형태를 직관적으로 검증할 수 있습니다.

![실시간 목업 프리뷰](images/notification_template_manual_01/image-2.png "▲ [그림 4-2] 알림 템플릿 실시간 모바일 목업 프리뷰 화면")

---

## 5. 백엔드 서비스 파이프라인 및 연동 코드 (Backend Pipeline)

비즈니스 로직(Controller)에서 템플릿을 호출하여 실제 발송을 수행할 때는 아래 표준 4단계 파이프라인을 엄격히 준수합니다.

### 5.1 알림 발송 표준 4단계 처리 절차

```mermaid
flowchart TD
    classDef default text-align:left;

    STEP1["<b>[1단계] 서비스 인스턴스 획득</b><br/>$notificationService = app(NotificationService::class);"]
    --> STEP2["<b>[2단계] 동일 사건 추적 묶음키 선발행</b><br/>$batchId = NotificationLog::generateBatchId(); // M 포함 12자리"]
    --> STEP3["<b>[3단계] 치환변수 맵 조립 및 수신자 목록 조회</b><br/>$variables = ['shop_name' => ..., 'user_name' => ...];<br/>$adminPhones = Setting::get('notification_admin_phones', []);"]
    --> STEP4["<b>[4단계] 수신 주체별 NotificationService::send() 호출</b><br/>• 본사 관리자 통보: SHOP_APPLY_ADMIN (userId: null, batchId 바인딩)<br/>• 신청자(원장님) 통보: SHOP_APPLY_USER (userId: $user->id, batchId 바인딩)"]
```

### 5.2 뷰티샵 입점 신청 연동 실제 코드 (ShopApplyController 예시)

    // ==========================================
    // 알림 발송 처리 (SHOP_APPLY_ADMIN, SHOP_APPLY_USER)
    // ==========================================

    // 1. 알림 서비스(NotificationService) 인스턴스 획득 (서비스 컨테이너 동적 주입)
    $notificationService = app(NotificationService::class);

    // 2. 동일 발송 건(관리자 통보 및 신청자 확인) 추적용 묶음 고유키(batch_id) 생성
    $batchId = NotificationLog::generateBatchId();

    // 3. 템플릿 본문 치환변수(#{변수}) 조립 및 발송 메타데이터 설정
    $applicantName  = $currentUser?->name ?? $shop->ceo_name;
    $applicantPhone =$currentUser?->phone ?? preg_replace('/[^0-9]/', '', $validated['phone']);$shopCode       = $shop->code ?? ('SHOP-' . str_pad($shop->id, 6, '0', STR_PAD_LEFT));
    $createdAtText  =$shop->created_at?->format('Y-m-d H:i') ?? now()->format('Y-m-d H:i');
    $csNumber       = Setting::get('cs_phone', '1588-2845');

    $variables = [
        'shop_name'   => $shop->name,
        'user_name'   => $applicantName,
        'user_phone'  => $applicantPhone,
        'shop_code'   => $shopCode,
        'created_at'  => $createdAtText,
        'cs_number'   => $csNumber,
    ];

    // Setting 모델에서 본사 알림 수신 대상 전화번호 목록 조회 (배열 자동 역직렬화 활용)
    $adminPhones = Setting::get('notification_admin_phones', []);
    if (is_string($adminPhones)) {
        $adminPhones = array_filter(array_map('trim', explode(',',$adminPhones)));
    }

    // 4-1. 본사 운영팀 통보 발송 (다중 관리자 번호 순회 발송, 동일 batch_id 바인딩)
    foreach ($adminPhones as$adminPhone) {
        $notificationService->send(
            templateCode: 'SHOP_APPLY_ADMIN',
            receiver:     $adminPhone,
            variables:    $variables,
            userId:       null, // 외부 운영자 직수신
            batchId:      $batchId,
            actionUrl:    route('admin.shops.show', ['id' => $shop->id]),
            extraData:    [
                'source_type'   => 'shop',
                'source_id'     => $shop->id,
                'receiver_type' => 'ADMIN',
                'dispatch_type' => 'AUTO',
            ]
        );
    }

    // 4-2. 신청자(원장님) 입점신청 완료 안내 알림 발송 (동일 batch_id 공유)
    if ($applicantPhone) {
        $notificationService->send(
            templateCode: 'SHOP_APPLY_USER',
            receiver:     $applicantPhone,
            variables:    $variables,
            userId:       $currentUser?->id, // 회원 ID 지정 -> 웹알림함 및 문자 동시 적재
            batchId:      $batchId,
            actionUrl:    route('mypage.apply.status'),
            extraData:    [
                'source_type'   => 'shop',
                'source_id'     => $shop->id,
                'receiver_type' => 'SHOP',
                'dispatch_type' => 'AUTO',
            ]
        );
    }

### 5.3 `NotificationService::send()` 파라미터 상세 명세표

| 파라미터명 | 타입 | 필수 여부 | 설명 및 설정 규칙 |
|---|---|:---:|---|
| `templateCode` | `string` | **필수** | 템플릿 마스터 테이블에 등록된 고유 코드 (`SHOP_APPLY_ADMIN` 등) |
| `receiver` | `string` | **필수** | 실제 발송 대상 연락처 (휴대폰 번호 또는 이메일 주소) |
| `variables` | `array` | 선택 | `['변수키' => '치환값']` 연관 배열 (키 앞의 `#{}` 유무 무관 자동 파싱) |
| `userId` | `?int` | 선택 | **수신 회원 계정 ID (`users.id`)**. 인앱 알림함(`notifications`)에 적재할 대상 회원이며, 관리자 직발송 시에는 `null` 지정 |
| `batchId` | `?string` | 선택 | **본사 통합 관제 묶음키**. 전달 시 `notification_logs`에 영구 적재되며, 미전달(`null`) 시 1회성 발송으로 판단하여 관제 로그 적재 생략 |
| `actionUrl` | `?string` | 선택 | 템플릿 기본 URL을 오버라이드할 경우 전달하는 상대 경로 URL |
| `extraData` | `array` | 선택 | 감사 로그용 다형성 메타데이터 (`source_type`, `source_id`, `sender_type`, `sender_id`, `receiver_type`, `dispatch_type`) |

---

## 6. 데이터 무결성 및 장애 방어 대책 (Integrity & Fail-Safe)

### 6.1 치환 파서(Parser) 안전장치
- 컨트롤러에서 전달한 `$variables` 배열에 템플릿에 정의된 변수가 일부 누락되더라도 시스템 오류가 발생하지 않습니다.
- 정의되지 않은 미치환 태그는 공백 또는 기본값으로 안전하게 대체되며, 발송 직전 정규식 검사를 통해 비정상적인 포맷이 노출되지 않도록 방어합니다.

### 6.2 통신망 장애 격리 및 실패 감사 로그 보존 (Fail-Safe Audit)
- 외부 통신사(카페24) 게이트웨이 타임아웃, 결번, 잔여 포인트 부족 등의 사유로 전송이 실패하더라도 스크립트 실행이 중단(Fatal Exception)되지 않습니다.
- 발송 실패 시 원천 통신 영수증인 `sms_logs`는 물론, 통합 관제 테이블 `notification_logs`에도 `status = 'FAIL'`, `error_message = '원인'`이 온전히 기록되어 본사 운영자가 누락 건을 즉시 인지하고 수동 재발송할 수 있습니다.

### 6.3 본문 길이에 따른 전송 채널 자동 승격
- 운영자가 템플릿 기본 채널을 `SMS`로 지정했더라도, 치환 완료된 최종 문자열이 EUC-KR 기준 90바이트를 초과하면 시스템이 내부적으로 채널을 `LMS`로 자동 승격하여 전송합니다.
- 메시지 내용이 중간에 잘리는 사고를 원천 방지하며, 통신사 과금 규격을 정확히 맞춥니다.

---

## 7. 운영자 점검 체크리스트 (Operations Checklist)

신규 알림 템플릿을 등록하거나 기존 템플릿을 개정할 때는 다음 항목을 반드시 점검합니다.

- [ ] **식별 코드 규격 준수**: `[도메인]_[행위]_[수신자]` 표준 포맷을 만족하며 중복된 코드가 없는가?
- [ ] **카테고리 매핑 일치**: `config/notification_categories.php`의 대분류/소분류에 정확히 부합하는가?
- [ ] **수신 대상 지정**: 일반회원(`user`), 뷰티샵(`shop`), 최고관리자(`admin`) 중 목적에 맞는 대상을 지정했는가?
- [ ] **치환변수 오타 확인**: 모달 검색을 통해 `config/notification_variables.php`에 정의된 정규 변수를 사용했는가?
- [ ] **이동 경로(URL) 유효성**: 도메인을 제외한 상대 경로로 입력되었으며, 웹 알림 활성화 여부와 일치하는가?
- [ ] **모바일 목업 최종 검증**: 우측 미리보기 화면에서 제목, 본문, 채널 뱃지가 정상 노출되는가?
- [ ] **운영 서버 배포 후 캐시 갱신**: config 파일 변경 시 `php artisan config:clear` 명령어를 실행했는가?