# Notifly Integration

> 노티플라이(Notifly) SDK를 Mobile(iOS/Android/Flutter/React Native) 및 Web(JavaScript/Tag Manager) 프로젝트에 연동합니다. 공식 Notifly 문서와 SDK 샘플을 단일 기준으로 삼아 설치/초기화/MCP 설정/검증/트러블슈팅을 단계별로 안내합니다.

- Skill: `notifly-tech/notifly-integration` (Agent Skill, multi-file: 16 files)
- Install (CLI): `npx skillmds@latest add notifly-tech/notifly-integration`
- Raw SKILL.md: https://api.skillmd.com/api/skills/notifly-tech/notifly-integration/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: notifly-tech (https://skillmd.com/u/notifly-tech)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/notifly-tech/notifly-integration

---


# Notifly SDK 연동 스킬

사용자가 **Notifly SDK**를 설치/설정/연동하려고 할 때 이 스킬을 사용하세요.
대상은:

- 푸시 알림
- 인앱 팝업 메시지 / 웹 팝업
- 유저 식별/유저 프로퍼티
- 이벤트 트래킹(플랫폼 SDK가 지원하는 범위)
- 웹 푸시 (Service Worker 기반)
- 웹 연동 (JavaScript SDK / Google Tag Manager)

## 연동 전략 (MCP 우선)

이 스킬은 항상 **MCP 우선**으로 동작하여, 최신의 검증된 문서/SDK 소스를 기준으로
의사결정합니다.

### 1단계: MCP 사용 가능 여부 확인

- Notifly MCP 도구가 있는지 확인:
  - `notifly-mcp-server:search_docs`
  - `notifly-mcp-server:search_sdk`

### 2단계: 기본 경로 (MCP 사용 가능)

- `notifly-mcp-server:search_docs`로 대상 플랫폼의 **공식 설치/초기화 단계**를
  확인
- `notifly-mcp-server:search_sdk`로 **정확한 API 시그니처/공식 샘플 코드**를
  확인
- MCP 결과가 존재하면 이를 **단일 기준**으로 취급 (추측 금지)

### 3단계: 대체 경로 (MCP 사용 불가)

- 이 레포의 정적 자료를 사용:
  - `references/`: 체크리스트/설명/문제 해결
  - `examples/`: 공식 문서/샘플에 맞춘 코드 패턴
- MCP가 필요하면 먼저 설치/구성:
  - 참고: `references/mcp-integration.md`
  - 또는 실행: `bash skills/integration/scripts/install-mcp.sh --help`

## 에이전트 작업 가이드라인

Cursor, Claude Code, Codex, Amp 등 AI IDE에서 이 스킬을 사용할 때:

- **사전 탐색부터 시작**
  - 플랫폼(들)과 진입점을 식별 (`AppDelegate.swift`, `Application`, `main.dart`,
    `index.js` 등)
  - 수정할 파일을 미리 선언
- **필요하면 질문**
  - RN/Flutter 같이 iOS/Android가 동시에 있는 경우, 어떤 플랫폼부터 할지 확인
  - “푸시만” vs “푸시 + 인앱 팝업” 범위를 확인
- **공식 소스 우선**
  - 플랫폼별 공식 문서를 기준으로 진행
  - API는 `search_sdk`로 확인해서 추측하지 않기
- **마지막에 검증 요약**
  - 변경 파일/설정 위치/검증 방법(콘솔 확인 포함)을 요약

## 연동 워크플로우

### 0단계: MCP 설정 (선택이지만 권장)

MCP 도구가 없다면 먼저 `notifly-mcp-server`를 구성하세요:

- 공식 가이드: `https://docs.notifly.tech/ko/devtools/notifly-mcp-server.md`
- 이 레포: `references/mcp-integration.md`
- 자동 설치/구성: `bash skills/integration/scripts/install-mcp.sh --help`

### 1단계: 사전 준비(필수 확인)

진행 전 반드시 확인:

- **Mobile**:
  - **Firebase 연동 완료** (Notifly는 FCM 사용)
  - **iOS APNs 인증을 Firebase에 등록** (iOS / Flutter iOS / RN iOS)
  - **Android 인앱 팝업은 Android 11 (API 30)+ 필요**
- **Web (웹 푸시 사용 시)**:
  - **VAPID 키 생성**: 콘솔 **설정 → SDK 설정 → 웹사이트 설정**
  - **HTTPS 필수**: Web Push/Service Worker/PushSubscription은 secure context에서만
    동작합니다. 로컬 웹푸시 검증도 `https://localhost`로 서버를 띄워 테스트하세요.
  - **Service Worker 파일 제공**: 번들러 사용 시 SW 파일이 누락되지 않도록
    public/static assets 복사 설정

공식 가이드:

- Android: `https://docs.notifly.tech/ko/developer-guide/android-sdk.md`
- iOS: `https://docs.notifly.tech/ko/developer-guide/ios-sdk.md`
- Flutter: `https://docs.notifly.tech/ko/developer-guide/flutter-sdk.md`
- React Native:
  `https://docs.notifly.tech/ko/developer-guide/react-native-sdk.md`
- JavaScript(Web): `https://docs.notifly.tech/ko/developer-guide/javascript-sdk`

### 2단계: 자격 증명(SDK)

Notifly SDK 설정의 실제 입력은 플랫폼 무관하게 `projectId`와 `username`을 기준으로
확인합니다. 일부 SDK API는 호환성을 위해 `password` 인자/필드를 계속 요구하지만, 현재
정책상 password 값은 사용하지 않습니다.

- `NOTIFLY_PROJECT_ID`
- `NOTIFLY_USERNAME`
- `password` 인자/필드가 필요하면 빈 문자열(`""`) 또는 `username`과 같은 더미값

Notifly 콘솔에서 확인: `https://console.notifly.tech/` → Project Settings → SDK
credentials.

**권장 사항**:

- iOS/Android/Flutter/RN/Web 모두 password를 별도 secret/env로 요구하지 마세요.
  특히 Web에서 `NEXT_PUBLIC_NOTIFLY_PASSWORD` 또는 `NEXT_PUBLIC_NOTIFLY_PROJECT_PASSWORD`
  같은 공개 password env를 새로 만들지 않습니다.
- SDK 타입/시그니처가 `password` 필드를 요구하면 필드는 유지하되, `""`, `username`,
  또는 프로젝트가 정한 더미값을 넘깁니다.
- `projectId`/`username` 값을 소스에 하드코딩/커밋하지 마세요.
- 플랫폼에 맞는 런타임/빌드타임 주입 방식을 사용하세요. Web은 번들 공개성을 고려해
  프로젝트가 정한 public config/server-injected config/빌드타임 config 정책을 따릅니다.
- `.env.example`의 존재/내용은 프로젝트마다 다를 수 있으므로 연동 품질의 필수 판정
  기준으로 삼지 않습니다.
- `projectId`가 외부 설정값이면 프로젝트 규칙(예: 32자 hex)에 맞게 검증하고,
  missing/invalid를 구분해 보고하세요. 기존 validation/test가 있으면 보존합니다.

### 3단계: 플랫폼 식별(프로젝트 타입)

프로젝트가 어느 플랫폼인지 식별:

- **iOS**: `.xcodeproj` / `.xcworkspace`, `Podfile`, Swift/Obj-C 소스
- **Android**: `build.gradle(.kts)`, `AndroidManifest.xml`, Kotlin/Java 소스
- **Flutter**: `pubspec.yaml`, `lib/main.dart`, `ios/` + `android/`
- **React Native**: RN 의존성이 있는 `package.json`, `ios/` + `android/`
- **Web (JavaScript)**:
  - `package.json`에 `notifly-js-sdk` 의존성, 또는 HTML의 CDN `<script>`
  - `public/notifly-service-worker.js` (또는 동등한 루트 경로 SW 파일)

구조가 애매하면 멈추고 사용자에게 질문하세요.

**우선순위 규칙(중요)**:

- **React Native** 또는 **Flutter**가 확인되면, 네이티브 `ios/`, `android/`가
  존재해도 **RN/Flutter를 1차 플랫폼**으로 취급합니다.

### 4단계: SDK 설치 (플랫폼별)

아래 “플랫폼 플레이북”을 기준으로 진행합니다. 가능한 한 공식 문서의 표현을
그대로 따르고, API는 MCP로 확인하세요.

## 플랫폼 플레이북

### iOS (Swift / Objective-C)

**설치(공식)**:

- CocoaPods: `pod 'notifly_sdk'`
- Swift Package Manager: `https://github.com/team-michael/notifly-ios-sdk`

**프로젝트 설정(공식)**:

- **Push Notifications** 활성화
- **Background Modes** 활성화 (Remote notifications, Background fetch)
- 최소 iOS 타겟 **13.0+**

**초기화(공식 패턴)**:

- `AppDelegate`에서 `FirebaseApp.configure()` 및 Notifly 초기화 수행
- 알림 권한 요청 후 원격 알림 등록
- `UNUserNotificationCenter` 델리게이트 설정
- APNs 토큰/푸시 콜백을 Notifly로 전달

공식 가이드: `https://docs.notifly.tech/ko/developer-guide/ios-sdk.md`

예시: `examples/ios-integration.swift`

### Android (Kotlin / Java)

**설치(공식)**:

- JitPack 저장소 추가
- 의존성 추가:
  `implementation 'com.github.team-michael:notifly-android-sdk:<latest>'`

**초기화(공식)**:

- `Application.onCreate()`에서 초기화:
  - `Notifly.initialize(applicationContext, NOTIFLY_PROJECT_ID, BuildConfig.NOTIFLY_USERNAME, "")`
  - SDK 시그니처가 `password` 값을 요구하면 빈 값/`username` 더미값을 넘기며,
    별도 `NOTIFLY_PASSWORD` secret을 요구하지 않습니다.

**유저 식별(초기화 후, 공식)**:

- `Notifly.setUserId(context, userId)` (로그아웃 시:
  `Notifly.setUserId(context, null)`)
- `Notifly.setUserProperties(context, params)`

**이벤트 트래킹(초기화 후, 공식)**:

- `Notifly.trackEvent(context, eventName, eventParams, segmentationEventParamKeys)`

예시: `examples/android-integration.kt`

### Flutter

**설치(공식)**:

- `flutter pub add notifly_flutter`
- iOS: `cd ios && pod install`

**초기화(공식)**:

- `Firebase.initializeApp()` 보장
- `await NotiflyPlugin.initialize(projectId: ..., username: ..., password: "")`
  (`password` 인자가 필요하면 빈 값/`username` 더미값 사용)
- (선택) 콘솔에서 “자동 권한 요청”이 비활성화된 경우:
  `await NotiflyPlugin.requestPermission()`
- (선택) 인앱 팝업 이벤트 구독(공식 예시):
  `NotiflyPlugin.inAppEvents.listen(...)`

예시: `examples/flutter-integration.dart`

### React Native

**설치(공식)**:

- npm 패키지: `notifly-sdk`
- iOS: `cd ios && pod install`

**설정(공식)**:

- RN은 iOS/Android 네이티브 연동이 필요(공식 RN 문서 참조)
- 네이티브 연동 후, JS에서 `notifly-sdk` API 사용

**JS API 사용(네이티브 연동 후)**:

- `notifly.setUserId(userId)` (로그아웃: `notifly.setUserId(null)` 또는
  `notifly.setUserId()`)
- `notifly.setUserProperties({...})`
- `notifly.setEmail(email)`
- `notifly.setPhoneNumber(phoneNumber)`
- `notifly.setTimezone(timezone)`

예시: `examples/react-native-integration.tsx`

### Web (JavaScript SDK)

**먼저 범위를 분리합니다.** 웹 팝업과 웹 푸시는 같은 SDK를 쓰지만 실패 지점이
다릅니다. 자세한 계약은 `references/web-javascript.md`를 함께 확인하세요.

- **웹 팝업 only**: Service Worker/Notification 권한이 아니라
  `initialize → user state sync → trackEvent → campaign condition match → renderer`
  경로가 핵심입니다.
- **웹 푸시 only**: HTTPS, Notifly 콘솔의 VAPID/웹사이트 SDK 설정,
  Service Worker path/scope, 브라우저 권한, PushSubscription 생성이 핵심입니다.
- **웹 팝업 + 웹 푸시**: 초기화는 하나지만, 검증은 user/event 축과
  Service Worker/permission 축을 따로 수행합니다.

**필수 선행(웹 푸시 사용 시, 공식)**:

- 콘솔에서 VAPID 키 생성: 설정 → SDK 설정 → 웹사이트 설정
- HTTPS secure context에서 서비스. Web Push는 plain HTTP에서 검증하지 않습니다.
  로컬 테스트도 `https://localhost`로 서버를 띄워 Service Worker/권한/구독 흐름을
  확인하세요.
- Service Worker 파일 제공: 기본 권장 경로는 `/notifly-service-worker.js`입니다.
  단, 공식 문서상 파일명/경로는 변경 가능하며 Notifly 콘솔의
  `serviceWorkerPath` 설정과 실제 제공 경로가 반드시 일치해야 합니다.
- 기존 PWA/Firebase/OneSignal/Braze/Workbox Service Worker가 있는지 먼저 확인하고,
  root scope 충돌 가능성이 있으면 무작정 새 SW를 추가하지 않습니다.

**1) Service Worker 등록(웹 푸시 사용 시)**:

- public/static 경로에 Service Worker 파일을 생성하거나 기존 SW에 통합
- 내용(공식 패턴):
  - `self.importScripts("https://cdn.jsdelivr.net/npm/notifly-js-sdk@2/dist/NotiflyServiceWorker.js");`
- 번들러 사용 시 SW 파일이 번들에 흡수/삭제되지 않도록 assets copy 설정
- 실제 URL이 HTML fallback이 아니라 JavaScript 파일로 200 응답하는지 확인

예시: `examples/notifly-service-worker.js`

**2) SDK 설치(선택)**:

- npm/yarn/pnpm: `notifly-js-sdk` 설치
- 또는 CDN으로 로드 후 `window.notifly` 접근

**3) SDK 초기화(현재 SDK 2.5.0+ 공식 패턴)**:

- 코드에는 `projectId`, `username`, 그리고 SDK 시그니처 호환용 `password` 필드를
  둡니다. 단, password 값은 사용하지 않으므로 별도 secret/env를 요구하지 말고
  `""`, `username`, 또는 프로젝트가 정한 더미값을 넘깁니다. 특히 Web에서
  `NEXT_PUBLIC_NOTIFLY_PASSWORD`/`NEXT_PUBLIC_NOTIFLY_PROJECT_PASSWORD`를 새로 요구하지
  않습니다.
- `projectId`가 env/config/콘솔 입력처럼 외부에서 들어오면 형식 검증(프로젝트 규칙,
  예: 32자 hex)과 missing/invalid 오류 분리를 유지합니다. 기존 config parser와 테스트가
  있으면 삭제하지 말고 확장합니다.
- SDK 2.5.0+에서는 세션/웹푸시 세부 옵션(VAPID/SW 경로/권한 팝업/지연시간 등)이
  콘솔 웹사이트 SDK 설정값으로 대체됩니다.
- SDK 2.5.0+ 신규 연동에서 `pushSubscriptionOptions`나 top-level
  `serviceWorkerPath`를 임의로 추가하지 않습니다. Legacy SDK 2.4 이하를 명시적으로
  지원할 때만 `pushSubscriptionOptions`를 사용합니다.
- 웹 팝업 HTML 내부에서 사용자 정의 이벤트 로깅이 필요한 경우에만 SDK 2.17.2+에서
  `allowUserSuppliedLogEvent: true`를 추가합니다. 기존 프로젝트에 이 옵션/env/config
  plumbing이나 테스트가 있으면 제거하지 말고 보존합니다.

예시: `examples/web-integration.js`

**4) 권한 요청(웹 푸시 사용 시)**:

- 먼저 HTTPS 로컬 서버 또는 배포 HTTPS 도메인에서 페이지를 엽니다. plain HTTP에서
  권한/Service Worker/PushSubscription 흐름을 검증하지 않습니다.
- Next.js 로컬 테스트는 `npm run dev -- --experimental-https` 또는
  `npx next dev --experimental-https`로 실행하고 `https://localhost:3000`에서 확인합니다.
  필요하면 `--experimental-https-key`, `--experimental-https-cert`로 mkcert 인증서를 지정합니다.
- Next.js가 아니면 대상 프로젝트의 `package.json`/lockfile/scripts로 현재 웹 프레임워크를
  먼저 식별한 뒤, 해당 프레임워크의 공식 local HTTPS dev-server 방법을 찾아 실행합니다.
- 콘솔에서 자동 권한 팝업을 켜면 방문 시 안내 → 브라우저 권한 요청 순서로 동작
- 특정 타이밍에만 요청하려면 SDK 2.7.0+에서 콘솔 자동 노출을 끄고
  `notifly.requestPermission(...)` 호출
- `requestPermission(...)` 호출은 “권한 프롬프트를 시도했다”는 뜻일 뿐입니다. `Notification.permission`,
  Service Worker 등록, PushSubscription 생성, Notifly device property logging을 별도로 확인합니다.
- SDK 초기화/ready 상태와 웹푸시 구독 verified/subscribed 상태를 분리해서 UI/문서/리포트에 표시합니다.
- 브라우저 권한이 이미 `denied`이면 SDK가 다시 요청할 수 없으므로 브라우저/site
  설정에서 사용자가 직접 변경해야 합니다.

**5) 유저/이벤트(웹 팝업/타깃팅 핵심)**:

- 로그인 직후 권장 순서: `setUserId → setUserProperties → trackEvent`
- `notifly.setUserId(userId | null)` (`null`/무인자는 로그아웃 처리이며 문서상 유저
  데이터 삭제성 동작이 있으므로 의도 확인)
- `notifly.setUserProperties({...})`
- `notifly.trackEvent(name, params, segmentationEventParamKeys)`
  (`segmentationEventParamKeys`는 최대 1개 키)

**6) Google Tag Manager(GTM) 옵션(선택)**:

- 코드 수정 없이 초기화/유저/이벤트를 구성 가능(공식 GTM 가이드 참고)
- SDK script load timing과 dataLayer 이벤트 순서를 보장해야 합니다.
- 단, 웹 푸시를 쓰는 경우 SW 파일 제공은 여전히 필요합니다.
- CSP가 있으면 `script-src`, `connect-src`, `worker-src`에서 Notifly/CDN 호출을
  허용해야 합니다.

### 5단계: SDK 초기화 위치 확정(레포 기준 증빙)

이 단계는 “어디에 코드를 넣는지”와 “레포에서 증명 가능한지”를 점검합니다.
기존 앱에 provider/config/client/test 구조가 있으면 새 단일 파일로 덮어쓰기보다 그 구조를
보존한 채 필요한 SDK 호출과 검증만 추가하는 편이 좋습니다. 다만 이 항목들은 기존 앱의
명시적 계약을 깨지 않는 한 hard blocker가 아니라 quality/parity 신호로 보고합니다.

웹 데모/재적용 검토에서 다음은 기본적으로 **non-blocking parity signal**입니다. 사용자가
명시적으로 hard requirement로 지정했거나 기존 앱의 계약을 실제로 깨는 경우에만 blocker로
올립니다:

- 기존 provider/config/client/test 구조 보존
- root-level SDK init과 route coverage
- permission CTA analytics event 보존
- demo fixture의 exact SDK version pinning 보존
- validator가 marker pass를 넘어 behavioral contract 차이를 잡는 능력

#### iOS 초기화 체크리스트

- **엔트리포인트**: `AppDelegate.swift` (또는 SwiftUI에서
  `@UIApplicationDelegateAdaptor(AppDelegate.self)` 사용)
- **필수 포함**:
  - `FirebaseApp.configure()`
  - `Notifly.initialize(projectId:username:password)` (`password`는 빈 값/더미값)
  - `UNUserNotificationCenter.current().delegate = self`
  - 아래 콜백 전달:
    - `application(_:didRegisterForRemoteNotificationsWithDeviceToken:)`
    - `application(_:didFailToRegisterForRemoteNotificationsWithError:)`
    - `userNotificationCenter(_:didReceive:withCompletionHandler:)`
    - `userNotificationCenter(_:willPresent:withCompletionHandler:)`

#### Android 초기화 체크리스트

- **엔트리포인트**: 커스텀 `Application` 클래스 (Kotlin/Java)
- `AndroidManifest.xml`의 `android:name`으로 등록
- **필수 포함**: `Application.onCreate()`에서 `Notifly.initialize(...)`

#### Flutter 초기화 체크리스트

- **엔트리포인트**: `lib/main.dart` (+ iOS 브릿지 파일은 공식 문서대로)
- **필수 포함**: `await NotiflyPlugin.initialize(...)`
- **iOS 참고**: 공식 Flutter 문서는 `ios/Runner/AppDelegate.mm` 작업을 기대함
  (`flutter-sdk.md` 참조)

#### React Native 초기화 체크리스트

- **네이티브**: 공식 RN 문서대로 iOS `AppDelegate.mm`, Android `Application`
  연동 수행
- **JS**: 공식 RN SDK 샘플 패턴대로 API 사용(예시 파일 참조)

### 6단계: 검증(필수)

1. 모바일 스크립트 실행(앱 프로젝트 루트에서):

- `bash skills/integration/scripts/validate-sdk.sh`

> 참고: 위 스크립트는 **모바일 플랫폼(iOS/Android/Flutter/RN)** 검증용입니다.

2. Web(JavaScript) 정적 검증:

- `bash skills/integration/scripts/validate-web-sdk.sh /path/to/web-app`

이 스크립트는 `notifly-js-sdk` 설치/CDN, `notifly.initialize(...)`, 자격 증명 마커,
Service Worker 후보, `NotiflyServiceWorker.js` import, legacy 옵션, user/event API
마커를 확인합니다. 추가로 projectId 검증/`allowUserSuppliedLogEvent`/수동 권한 요청의
런타임 의미를 경고로 표시합니다. 정적 검증은 충분조건이 아니므로 아래 런타임 검증까지 수행하세요.

3. 빌드/실행 후 콘솔에서 확인:

- 초기화 로그/동작 확인
- 푸시 토큰 등록(네이티브) 확인
- Notifly 콘솔에서 이벤트/기기 등록 확인

4. Web 런타임 검증(웹 푸시/웹 팝업):

- 웹 푸시는 HTTPS secure context에서만 검증합니다. 로컬도 `https://localhost`로 실행합니다.
  - Next.js: `npm run dev -- --experimental-https` 또는 `npx next dev --experimental-https`
  - Next.js가 아니면 `package.json`으로 프레임워크를 식별하고, 해당 프레임워크의 공식
    local HTTPS 실행법을 찾아 서버를 띄운 뒤 테스트합니다.
- 콘솔에 설정한 Service Worker path가 실제로 200 JS 응답인지 확인
  (`/notifly-service-worker.js`가 기본 예시이며, HTML fallback이면 실패)
- DevTools → Application → Service Workers에서 등록/scope 확인
- Network에서 `/sdk-configurations?project_id=...&type=website` 200 확인
- 브라우저에서 알림 권한 요청/허용 흐름이 의도대로 동작하는지 확인
  (`requestPermission(...)` 호출 자체는 prompt 시도일 뿐 성공/구독 증명이 아님)
- 권한 허용 후 PushSubscription 생성 및 device property logging 확인
- SDK 초기화/ready 상태와 웹푸시 verified/subscribed 상태를 분리해 확인
- `setUserId → setUserProperties → trackEvent` 호출 후 콘솔에서 반영 확인
- 웹 팝업 캠페인 조건에 맞는 이벤트 호출 시 modal 노출 확인

### 7단계: 플랫폼별 레포 검증 체크리스트

#### iOS

- 의존성 존재 (Pods 또는 SPM)
- Capability 설정 완료 (Push + Background Modes)
- `AppDelegate`에서 APNs 콜백을 Notifly로 전달

#### Android

- JitPack repo 설정
- SDK 의존성 존재
- `Application` 클래스 존재 및 매니페스트 등록
- `Application.onCreate()`에서 `Notifly.initialize(...)`

#### Flutter

- `pubspec.yaml`에 `notifly_flutter` 포함
- `ios/Podfile.lock`이 `pod install` 이후 업데이트됨
- `NotiflyPlugin.initialize(...)` 호출

#### React Native

- `package.json`에 `notifly-sdk` 포함
- `ios/Podfile.lock`이 `pod install` 이후 업데이트됨
- 공식 문서대로 네이티브 연동 완료
- JS 호출이 공식 샘플 패턴과 일치

#### Web (JavaScript)

- `notifly-js-sdk` 설치(npm) 또는 CDN 로드 확인
- 앱 코드에서 현재 SDK 2.5.0+ 패턴인
  `notifly.initialize({ projectId, username, password })` 호출 확인
- SDK 호환용 `password` 필드는 필요하면 유지하되, password 값은 사용하지 않으므로
  별도 공개 env/secret을 요구하지 않습니다. `""`, `username`, 또는 프로젝트 더미값을
  넘기고, 프로젝트의 주입/노출 정책을 확인합니다.
- 외부 config에서 받은 `projectId` 검증과 missing/invalid 오류 분리를 보존합니다.
  기존 config parser/provider/tests가 있으면 삭제하지 말고 확장합니다.
- 신규 SDK 2.5.0+ 연동에서 `pushSubscriptionOptions` 또는 top-level
  `serviceWorkerPath`를 추가하지 않았는지 확인(legacy SDK 2.4 이하 예외)
- (웹 팝업) `setUserId → setUserProperties → trackEvent` 호출 순서와 이벤트/속성 설계가
  캠페인 조건과 일치
- (웹 팝업) 웹 팝업 HTML 내부 custom event logging이 필요할 때만
  `allowUserSuppliedLogEvent: true` 사용. 기존 plumbing/test가 있으면 보존
- (웹 푸시) 콘솔의 Service Worker path와 실제 제공 경로가 일치
- (웹 푸시) SW 파일이 `NotiflyServiceWorker.js`를 importScripts 하는지 확인
- (웹 푸시) 기존 PWA/Firebase/OneSignal/Braze/Workbox Service Worker와 scope/handler
  충돌이 없는지 확인
- (웹 푸시) 권한 요청 및 구독 흐름이 동작하는지 확인
  (`requestPermission(...)` 호출, `Notification.permission`, PushSubscription, device logging을 분리)
- (웹 푸시) SDK ready와 push subscribed/verified 상태를 같은 의미로 보고하지 않음

### 8단계: 문서화

연동 후 README/내부 문서에 다음을 기록:

- 필요한 자격 증명과 주입 방법(시크릿은 노출하지 않기)
- iOS/Android 빌드/런 방법
- Notifly 콘솔에서 검증하는 방법

## 문서 사용 노트 (MCP vs 정적 자료)

- MCP 사용 가능: `search_docs` / `search_sdk` 결과를 단일 진실원으로 취급
- MCP 사용 불가: 공식 문서 링크 + 이 스킬 폴더의 `examples/`, `references/` 사용

## 점진적 공개(Progressive Disclosure)

- **레벨 1**: `SKILL.md` / `SKILL.ko.md`
- **레벨 2**: `references/`
- **레벨 3**: `examples/`
- **레벨 4**: `scripts/`

## 참고 자료(References)

- `references/sdk-reference.md`
- `references/error-handling.md`
- `references/framework-patterns.md`
- `references/web-javascript.md`
- `references/mcp-integration.md`

## 예시(Examples)

- `examples/ios-integration.swift`
- `examples/android-integration.kt`
- `examples/flutter-integration.dart`
- `examples/react-native-integration.tsx`
- `examples/notifly-service-worker.js`
- `examples/web-integration.js`

## 스크립트(Scripts)

- `scripts/install-mcp.sh` (클라이언트에 MCP 서버 구성)
- `scripts/validate-sdk.sh` (모바일 SDK 연동 마커 검증)
- `scripts/validate-web-sdk.sh` (JavaScript/Web SDK 연동 마커 검증)

