๐Ÿ“ฃ ์•Œ๋ฆผ ์‹œ์Šคํ…œ

Phase 1 ๋Ÿฐ์นญ ํ•„์ˆ˜. ์˜ˆ์•ฝยท์„ธ์…˜ยท๊ฒฐ์ œยท์ •์‚ฐยท๋งค์นญ ์•Œ๋ฆผ.

์ฑ„๋„

์ฑ„๋„ ์šฉ๋„ ๋น„์šฉ
ํ‘ธ์‹œ (FCMยทAPNs) ์ธ์•ฑ ์•Œ๋ฆผ (์‹ค์‹œ๊ฐ„) ๋ฌด๋ฃŒ
์นด์นด์˜ค ์•Œ๋ฆผํ†ก ํ•ต์‹ฌ ํŠธ๋žœ์žญ์…˜ (Solapi) ํšŒ๋‹น ~10์›
SMS ์•Œ๋ฆผํ†ก ์‹คํŒจ ์‹œ fallback ํšŒ๋‹น ~20์›
์ด๋ฉ”์ผ ์˜์ˆ˜์ฆยท์•ฝ๊ด€ ๋ณ€๊ฒฝยท๋งˆ์ผ€ํŒ… ๋ฌด๋ฃŒ (์ž์ฒด SMTP ๋˜๋Š” SendGrid)
์ธ์•ฑ (๋ฒจ ์•„์ด์ฝ˜) ๋ชจ๋“  ์•Œ๋ฆผ ๋ณด๊ด€ ๋ฌด๋ฃŒ

์•Œ๋ฆผ ์นดํ…Œ๊ณ ๋ฆฌ

ํŠธ๋žœ์žญ์…˜ (ํ•„์ˆ˜, ์˜ตํŠธ์•„์›ƒ โŒ)

  • ์˜ˆ์•ฝ ํ™•์ •ยท๋ณ€๊ฒฝยท์ทจ์†Œ (ํšŒ์›ยท๋ฉ˜ํ† )
  • ์„ธ์…˜ ์ž„๋ฐ• (T-1h, T-15๋ถ„, T-5๋ถ„)
  • ๋งค์นญ (๋ฉ˜ํ† ์—๊ฒŒ ํšŒ์› ๋งค์นญ)
  • ๊ฒฐ์ œ ์„ฑ๊ณตยท์‹คํŒจยท๊ฐฑ์‹  ์•Œ๋ฆผ
  • ํ™˜๋ถˆ ์ฒ˜๋ฆฌ
  • ์ •์‚ฐ ์ž…๊ธˆ (๋ฉ˜ํ† )
  • ๋ฉ˜ํ†  ๋…ธ์‡ผ ๋ณด์ƒ

์šด์˜ (ํ•„์ˆ˜)

  • ํ‰๊ฐ€ ์š”์ฒญ (T+90 ํ›„)
  • ๋ฉ˜ํ†  ์ปดํ”Œ๋ ˆ์ธ ๋“ฑ๋ก
  • ๋“ฑ๊ธ‰ ๋ณ€๋™ (Pro ์ธ์ฆ ํ†ต๊ณผยท๋“ฑ๊ธ‰ ๋ณด๋ฅ˜)
  • ์ผ์‹œ์ •์ง€ ์‹œ์ž‘ยท์ข…๋ฃŒ
  • ์•ฝ์ • ๋งŒ๋ฃŒ ์ž„๋ฐ• (D-7)

๋งˆ์ผ€ํŒ… (์˜ตํŠธ์ธ, ๋ณ„๋„ ๋™์˜)

  • ์‹ ๊ทœ ๋ฉ˜ํ†  ์ถ”์ฒœ
  • ํ”„๋กœ๋ชจ์…˜ยท์ด๋ฒคํŠธ
  • AI ์›”๊ฐ„ ๋ถ„์„

ํŠธ๋ฆฌ๊ฑฐ ๋งคํŠธ๋ฆญ์Šค

์ด๋ฒคํŠธ ํšŒ์› ๋ฉ˜ํ†  ๊ด€๋ฆฌ์ž
ํšŒ์› ์˜ˆ์•ฝ ํ™•์ • ํ‘ธ์‹œ + ์•Œ๋ฆผํ†ก ํ‘ธ์‹œ -
์„ธ์…˜ ์ž„๋ฐ• T-1h ํ‘ธ์‹œ ํ‘ธ์‹œ -
์„ธ์…˜ ์ž„๋ฐ• T-15๋ถ„ ํ‘ธ์‹œ ํ‘ธ์‹œ (ํšŒ์› ์ •๋ณด) -
ํšŒ์› ๋…ธ์‡ผ (T+15) ํ‘ธ์‹œ (ํšŒ์ฐจ ์ฐจ๊ฐ) - ์ธ์•ฑ
๋ฉ˜ํ†  ๋…ธ์‡ผ (T+15) ํ‘ธ์‹œ + ์•Œ๋ฆผํ†ก (๋ณด์ƒ) ํ‘ธ์‹œ (ํŒจ๋„ํ‹ฐ) ํ‘ธ์‹œ (๊ธด๊ธ‰)
๋ฉค๋ฒ„์‹ญ ๊ฐฑ์‹  D-7 ํ‘ธ์‹œ + ์ด๋ฉ”์ผ - -
์ •์‚ฐ ์ž…๊ธˆ (๊ฒฉ์ฃผ) - ํ‘ธ์‹œ + ์ด๋ฉ”์ผ -
Pro ์ธ์ฆ ํ†ต๊ณผ - ํ‘ธ์‹œ + ์ด๋ฉ”์ผ -
์ปดํ”Œ๋ ˆ์ธ ๋“ฑ๋ก ์ธ์•ฑ (์ ‘์ˆ˜ ํ™•์ธ) ํ‘ธ์‹œ + ์ด๋ฉ”์ผ ํ‘ธ์‹œ (๊ธด๊ธ‰)
๊ฒฐ์ œ ์‹คํŒจ ํ‘ธ์‹œ + ์•Œ๋ฆผํ†ก - -
6h ์ด๋‚ด ์ทจ์†Œ (ํšŒ์›) ์ธ์•ฑ (ํšŒ์ฐจ ์ฐจ๊ฐ ์•ˆ๋‚ด) ์•Œ๋ฆผํ†ก (๋ณด์ƒ) ์ธ์•ฑ

๋ฐ์ดํ„ฐ ๋ชจ๋ธ

type NotificationChannel = 'push' | 'kakao' | 'sms' | 'email' | 'in_app'
type NotificationType = 'transaction' | 'operations' | 'marketing'

interface NotificationTemplate {
  id: string
  type: NotificationType
  eventKey: string  // "session_imminent_t-1h"
  channels: NotificationChannel[]
  titleTemplate: string  // "{memberName}๋‹˜ 1์‹œ๊ฐ„ ํ›„ ์„ธ์…˜ ์‹œ์ž‘"
  bodyTemplate: string
  kakaoTemplateCode?: string  // Solapi ํ…œํ”Œ๋ฆฟ ์ฝ”๋“œ
}

interface NotificationDelivery {
  id: string
  templateId: string
  recipientType: 'member' | 'mentor' | 'admin'
  recipientId: string
  channel: NotificationChannel
  status: 'pending' | 'sent' | 'failed' | 'bounced'
  sentAt?: string
  payload: Record<string, any>
  errorMessage?: string
}

interface NotificationPreference {
  recipientType: string
  recipientId: string
  marketingOptIn: boolean
  channels: { push: boolean, kakao: boolean, sms: boolean, email: boolean }
}

์นด์นด์˜ค ์•Œ๋ฆผํ†ก (Solapi)

  • ๋ณธ์‚ฌ ์นด์นด์˜ค ์ฑ„๋„ (@PT-Platform ๊ฐ€์„ค) ๋“ฑ๋ก
  • ์•Œ๋ฆผํ†ก ํ…œํ”Œ๋ฆฟ ์‚ฌ์ „ ์Šน์ธ (Solapi ์ฝ˜์†”)
  • ํšŒ์›ยท๋ฉ˜ํ† ์— ํœด๋Œ€ํฐ ๋ณธ์ธ ์ธ์ฆ ์‹œ ๋™์˜ ์ž๋™ ์ฒ˜๋ฆฌ
  • ์•Œ๋ฆผํ†ก ์‹คํŒจ ์‹œ SMS fallback

API ์—”๋“œํฌ์ธํŠธ

  • POST /api/notifications/internal/send โ€” ์‹œ์Šคํ…œ ๋‚ด๋ถ€ ํ˜ธ์ถœ (์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜)
  • GET /api/notifications/me โ€” ๋‚ด ์•Œ๋ฆผ ๋ฆฌ์ŠคํŠธ (์ธ์•ฑ)
  • PATCH /api/notifications/me/:id/read โ€” ์ฝ์Œ ์ฒ˜๋ฆฌ
  • PATCH /api/notifications/me/preferences โ€” ์ฑ„๋„ยท๋งˆ์ผ€ํŒ… ์„ค์ •

์šด์˜

  • Queue ์‹œ์Šคํ…œ (Redis ๋“ฑ) โ€” ์•Œ๋ฆผ ๋ฐœ์†ก ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ
  • ์žฌ์‹œ๋„ ์ •์ฑ… (์‹คํŒจ ์‹œ 5๋ถ„ยท30๋ถ„ยท2h ๊ฐ„๊ฒฉ)
  • ๋ชจ๋‹ˆํ„ฐ๋ง (์„ฑ๊ณต๋ฅ ยท์ง€์—ฐ ์‹œ๊ฐ„)
  • ์˜ตํŠธ์•„์›ƒ ์ฒ˜๋ฆฌ (๋งˆ์ผ€ํŒ…๋งŒ โ€” ํŠธ๋žœ์žญ์…˜์€ ๊ฐ•์ œ)

๋น„์šฉ ์ถ”์ • (Phase 1, 1์ง€์ )

  • ํšŒ์› 200๋ช… ร— ์›” ํ‰๊ท  8 ์•Œ๋ฆผํ†ก = 1,600๊ฑด ร— 10์› = 16,000์›/์›”
  • ๋ฉ˜ํ†  12๋ช… ร— ์›” 100 ์•Œ๋ฆผ = 1,200๊ฑด ร— 10์› = 12,000์›/์›”
  • SMS fallback (10% ๊ฐ€์ •) = ~5,000์›/์›”
  • ํ‘ธ์‹œยท์ด๋ฉ”์ผ = ๋ฌด๋ฃŒ
  • ํ•ฉ โ‰ˆ 33,000์›/์›”/์ง€์ 

2026-05-13 ์ดˆ์•ˆ โ€” 5์ฑ„๋„ + ์นดํ…Œ๊ณ ๋ฆฌ + ํŠธ๋ฆฌ๊ฑฐ ๋งคํŠธ๋ฆญ์Šค + ๋ฐ์ดํ„ฐ ๋ชจ๋ธ