단건 · 멀티캐스트 · 토픽 발송
기기 ID, 사용자 ID, 원문 토큰, 토픽 중 무엇으로도 대상을 지정합니다. 멀티캐스트는 요청당 500건 상한이고 그 이상은 대량 발송 잡으로 넘깁니다. 단건 발송은 PENDING 로그를 먼저 커밋한 뒤 @Async 스레드에서 FCM을 호출하므로 API 응답이 FCM 지연에 묶이지 않습니다.
FCM 연동은 하루면 됩니다. 그다음부터가 문제입니다 — 만료 토큰 정리, 수만 건 발송 중 서버 재시작, 수신 동의와 야간 제한, 실패 원인 추적, 테넌트 격리. 이 플랫폼은 그 운영 문제들을 스키마와 파이프라인 수준에서 해결한 상태로 납품됩니다.
Spring Boot 4.0.7 · Java 21 · PostgreSQL 18 · React 19 — 온프레미스 / 고객사 클라우드 배포
아래 항목은 모두 구현 · 테스트가 끝난 동작입니다. 각 카드의 태그는 실제 클래스 · 컬럼 · 정책 이름이며 요구사항 추적표(RTM)의 기능 ID와 1:1로 연결됩니다.
기기 ID, 사용자 ID, 원문 토큰, 토픽 중 무엇으로도 대상을 지정합니다. 멀티캐스트는 요청당 500건 상한이고 그 이상은 대량 발송 잡으로 넘깁니다. 단건 발송은 PENDING 로그를 먼저 커밋한 뒤 @Async 스레드에서 FCM을 호출하므로 API 응답이 FCM 지연에 묶이지 않습니다.
건별로 상태 · FCM 메시지 ID · 오류 코드 · 전체 payload를 남깁니다. 상세 화면에서는 토큰을 역조회해 어느 기기 · 어느 사용자였는지까지 보여주고, 오류 코드별 조치 안내를 함께 붙입니다.
FCM이 UNREGISTERED · INVALID_ARGUMENT 를 반환하면 해당 토큰을 즉시 비활성 처리합니다. 판정 코드는 PushSendCore 한 곳에서만 관리해 발송 경로별로 기준이 갈리지 않습니다.
알림을 카테고리로 나누고 사용자별 구독 상태를 관리합니다. 마케팅 카테고리는 옵트인 이력을 남기고 야간(21~08)에는 발송을 차단합니다. 거래 · 보안 알림은 카테고리 단위로 야간 예외를 지정합니다.
워커가 UPDATE … RETURNING + FOR UPDATE SKIP LOCKED 로 PENDING 아이템을 배치 선점합니다. 프로세스가 죽어도 선점되지 않은 아이템은 그대로 남아 다음 틱(5초)에 이어서 처리되고, 같은 아이템이 두 번 나가지 않습니다.
DRAFT로 저장해 두고 예약 시각에 스케줄러(매분)가 자동 발송합니다. 대상 목록 · 이미지 · 딥링크 · 커스텀 데이터를 메시지에 함께 저장하므로 같은 메시지를 재사용 · 재발송할 수 있습니다.
SUPER_ADMIN · TENANT_ADMIN · OPERATOR · VIEWER 4단계를 메서드 단위 @PreAuthorize 로 강제합니다. 조회 · 발송 범위는 owner_app_id 기준으로 잘려 타 테넌트 데이터가 쿼리 결과에 섞이지 않습니다.
일별 집계는 서버에서 계산합니다(페이지 상한을 넘는 순간 클라이언트 집계는 조용히 왜곡됩니다). CSV는 화면에 그려진 페이지가 아니라 조건에 맞는 전체를 내보내고, 상한에서 잘리면 응답 헤더로 알립니다.
React 19 + TypeScript 단일 페이지 앱입니다. 라이트 · 다크 테마, 한국어 · 영어, 모바일 대응이 기본 포함이며 컴포넌트 · 레이아웃 규칙이 문서로 고정돼 있어 화면을 늘려도 형태가 갈리지 않습니다. 이미지를 클릭하면 크게 볼 수 있습니다.
KPI 4종, 7일 추이, 플랫폼 분포, 당일 실패 사유 Top을 한 화면에 둡니다. 일별 집계는 서버가 기간을 좁혀 세므로 발송량이 늘어도 그래프가 왜곡되지 않습니다.
대상은 등록 기기 검색 팝업에서 고르거나 원문 토큰을 직접 넣습니다. 딥링크 · 알림 이미지 · 커스텀 데이터는 한 구획으로 모았고, 발송 버튼 옆에 무엇이 비었는지가 항상 표시돼 비활성 이유를 찾을 필요가 없습니다.
CSS 커스텀 프로퍼티 기반 테마 — 차트 색까지 테마별로 지정해 다크에서 축 · 범례가 묻히지 않습니다
단건 · 소량은 지연을 줄이는 경로, 대량은 내구성을 확보하는 경로로 분리했습니다. 같은 코드로 둘 다 처리하려 하면 API가 FCM 지연에 묶이거나, 반대로 수만 건이 메모리에서 사라집니다.
캠페인(기획 단위) 아래 잡(실행 단위), 그 아래 아이템(대상 1건), 최종 결과는 발송 로그에 남습니다. 4계층이라 캠페인 전체 성과와 이 사람에게 왜 안 갔나를 같은 데이터로 답할 수 있습니다.
아이템 생성 시 활성 기기만 전개합니다. 요청 120건 중 활성 96대라면 그 차이를 발송 전에 미리보기로 보여주고, 미등록 · 비활성은 SKIPPED로 남겨 나중에 원인을 셀 수 있게 합니다.
PENDING 로그를 먼저 커밋하고 응답합니다. FCM이 느려도 클라이언트는 기다리지 않고, 프로세스가 죽어도 PENDING이 남아 복구 대상이 됩니다 — 발송 요청이 흔적 없이 사라지지 않습니다.
표준 키(title · body · type · deeplink · imageUrl · messageId · campaignId · sentAt)와 커스텀 데이터를 PushService 한 곳에서 합칩니다. 예약 키 · 표준 키 충돌은 서버와 관리자 화면 양쪽에서 막습니다.
모바일 앱은 토큰 등록 엔드포인트 하나만 호출하면 됩니다. 기존 앱과의 호환을 위해 이 경로는 계약을 고정해 유지하며, 응답은 전 구간 단일 포맷입니다.
FCM notification 블록을 쓰지 않고 data 만 채워 보냅니다. 앱이 포그라운드 · 백그라운드 모두에서 같은 코드로 알림을 그리므로 OS · 런처에 따라 표시가 갈리지 않습니다. 이미지도 data.imageUrl 로 전달합니다.
Android는 priority=HIGH, iOS는 content-available 백그라운드 푸시로 보냅니다. 앱이 깨어나 직접 알림을 구성하는 방식이라 iOS에서도 data-only 정책을 유지합니다.
검색어 · 기간 · 상태는 쿼리 파라미터로 처리합니다. 클라이언트에서 현재 페이지만 걸러내면 2페이지의 일치 항목이 안 보여 없다고 오인하게 됩니다. 페이지 크기는 서버가 100으로 클램프합니다.
조건에 맞는 전체를 대상으로 하며 상한은 10,000행입니다. 초과 시 X-Export-Total · X-Export-Truncated 헤더로 알려 잘린 파일로 집계하는 사고를 막습니다. 헤더는 화면 열 이름과 같은 한글, UTF-8 BOM으로 Excel 호환입니다.
기능 ID · 상태 · 테스트 매핑을 담은 요구사항 추적표, API 계약서, 화면 레이아웃 규칙서를 함께 납품합니다. 담당자가 바뀌어도 같은 기준으로 화면과 API를 늘릴 수 있습니다.
152개 테스트가 Testcontainers로 PostgreSQL 18을 띄워 검증합니다. mock DB는 쓰지 않습니다 — JSONB 매핑, SKIP LOCKED, 마이그레이션 순서처럼 실제로 깨지는 지점은 가짜 DB에서 드러나지 않습니다.
기존 Flyway 스크립트는 수정하지 않습니다. 컬럼 삭제 · 타입 변경은 승인 절차를 거친 별도 마이그레이션으로만 진행하므로, 운영 중 배포가 데이터를 되돌릴 수 없게 만들지 않습니다.
기기 조회 API는 마스킹된 토큰만 반환합니다. 관리자 화면 · CSV · 로그 어디에도 원문 FCM 토큰이 남지 않습니다. 서비스 계정 키는 환경변수 경로로만 참조하고 저장소에 넣지 않습니다.
버튼 크기 6단계 · 색 5종, 팝업 크기 4단계, 표 텍스트 3계층, 필터 배치 순서를 규칙서로 고정했습니다. 목록 팝업은 단일 컴포넌트 하나(모드 3종)로 통일해 화면이 늘어도 조작 방식이 갈리지 않습니다.
수신 동의, 개인정보 노출, 권한 분리, 보존 기간 — 도입 심사에서 반드시 나오는 질문들입니다.
마케팅 알림은 옵트인 원칙입니다. 동의 · 철회 이력을 남기고 야간(21~08) 발송을 차단합니다. 거래 · 보안 알림은 카테고리 단위로 야간 예외를 지정해 법적 의무와 서비스 필요를 함께 만족시킵니다.
FCM 토큰은 마스킹 값만 노출합니다. 발송 로그의 오류 원문은 화면에 문장으로 두지 않고 코드별 조치 안내로 치환합니다. 공지 본문 HTML은 저장 · 표시 시 화이트리스트 기반으로 정화(sanitize)해 저장형 XSS를 차단합니다.
4단계 역할을 메서드 단위로 강제하고 조회 범위를 owner_app_id 로 제한합니다. 발송 요청자 · 시각 · payload가 로그에 남아 누가 무엇을 언제 보냈는지를 사후에 확인할 수 있습니다. 보존 배치는 매일 03:00에 정책 기간을 넘긴 로그를 정리합니다.
플랫폼은 완성 상태입니다. 도입 작업은 고객사 환경에 맞추는 일에 집중됩니다.
발송 유형(거래 · 마케팅 · 공지), 예상 물량, 앱 구성(단일 · 멀티 브랜드), 배포 환경(온프레미스 · 클라우드)을 확정합니다. 테넌트 · 앱 구조와 카테고리 체계를 여기서 정합니다.
고객사 Firebase 프로젝트에 서버를 연결하고, 앱은 토큰 등록 엔드포인트를 호출하도록 수정합니다. 알림 렌더링은 data payload 계약서에 맞춰 앱 측에서 구현합니다(계약서 제공).
담당자 계정과 4단계 역할, 앱별 접근 범위를 설정합니다. 브랜딩 · 용어는 화면 규칙서 범위 안에서 조정합니다.
실제 기기로 단건 → 소량 → 대량 순으로 검증합니다. 발송 로그 · 통계로 결과를 확인하고, 운영 런북(실행 · 장애 확인 절차)과 문서 일체를 인수합니다.