กลับไปหน้าบทความ
#ระบบแจ้งเตือน#Web Development#Notification System#Backend#UX

ออกแบบระบบแจ้งเตือนบนเว็บแบบมืออาชีพ: In-app, Email และ Push ทีละขั้น

บทความนี้สรุปแนวทางออกแบบระบบแจ้งเตือนบนเว็บให้ส่งถึงผู้ใช้โดยไม่รบกวน พร้อมตรวจสอบย้อนหลัง ป้องกันการส่งซ้ำ และรองรับการขยายระบบในระยะยาว ครอบคลุมตั้งแต่โครงสร้างข้อมูล การจัดคิว การตั้งค่า retry ไปจนถึงประสบการณ์ใช้งาน

23 มกราคม 2569อ่านประมาณ 3 นาที

แชร์บทความ

ออกแบบระบบแจ้งเตือนบนเว็บแบบมืออาชีพ: In-app, Email และ Push ทีละขั้น

ออกแบบระบบแจ้งเตือนบนเว็บแบบมืออาชีพ: In-app, Email และ Push ทีละขั้น

ระบบแจ้งเตือนที่ดีไม่ได้มีหน้าที่แค่ “ส่งข้อความออกไป” เท่านั้น แต่ต้องส่งให้ถึงผู้ใช้ในเวลาที่เหมาะสม ไม่สร้างความรำคาญ ตรวจสอบย้อนหลังได้ และพร้อมเติบโตไปกับระบบในอนาคต แนวคิดสำคัญคือการแยกให้ชัดระหว่าง เหตุการณ์ต้นทาง (Event) กับ กระบวนการส่ง (Delivery) เพื่อให้ระบบมีความยืดหยุ่นและควบคุมได้ง่ายขึ้น

บทความนี้จะเรียบเรียงแนวทางออกแบบระบบแจ้งเตือนบนเว็บแบบเป็นขั้นตอน ตั้งแต่การวางโครงสร้างข้อมูลไปจนถึงการดูแลเสถียรภาพของระบบในงานจริง

1) วางแนวคิดพื้นฐานให้ถูกตั้งแต่ต้น

ก่อนเริ่มลงมือสร้างระบบ ควรกำหนดเป้าหมายของ Notification ให้ชัดเจนว่า ต้องมีคุณสมบัติหลักดังนี้

  • ส่งถึงผู้ใช้ได้จริง
  • ไม่รบกวนเกินความจำเป็น
  • ตรวจสอบย้อนหลังได้
  • ขยายระบบได้เมื่อปริมาณงานเพิ่มขึ้น

หลักการสำคัญคือแยก 2 ส่วนออกจากกัน

  • Event: เหตุการณ์ที่เกิดขึ้นในระบบ เช่น ชำระเงินสำเร็จ มีคนตอบคอมเมนต์ หรือมีการล็อกอินผิดปกติ
  • Delivery: วิธีการนำเหตุการณ์นั้นไปส่งยังผู้ใช้ผ่านช่องทางต่าง ๆ เช่น In-app, Email หรือ Push

เมื่อแยกสองส่วนนี้ชัดเจน จะช่วยลดปัญหาการส่งซ้ำ จัดการ retry ได้ง่าย และตรวจสอบย้อนหลังได้ละเอียดกว่าเดิม

2) กำหนดประเภทการแจ้งเตือนให้เป็นมาตรฐาน

ควรกำหนด notification_type ที่ชัดเจนตั้งแต่ต้น เช่น

  • order_paid
  • comment_reply
  • security_login
  • weekly_digest

แต่ละประเภทควรมีข้อมูลกำกับ เช่น

  • ระดับความสำคัญ (priority)
  • ช่องทางเริ่มต้น (default channels)

ตัวอย่างเช่น

  • security_login อาจส่งทั้ง In-app และ Email เป็นค่าเริ่มต้น
  • weekly_digest อาจส่งเฉพาะ Email
  • comment_reply อาจส่ง In-app และ Push

การออกแบบส่วนนี้ให้ดี จะช่วยให้เพิ่ม notification ประเภทใหม่ได้ง่ายและควบคุมพฤติกรรมของแต่ละประเภทได้เป็นระบบ

3) ออกแบบตารางหลักสำหรับ In-app Notification

สำหรับการแจ้งเตือนภายในแอปหรือหน้าเว็บ ควรมีตาราง notifications เป็นแกนกลาง เช่น

  • id (uuid)
  • user_id
  • type
  • title
  • body
  • data_json สำหรับ payload เช่น order_id, url
  • url (optional)
  • status เช่น unread, read, archived
  • created_at
  • read_at

ดัชนีที่แนะนำ

  • (user_id, created_at)
  • (user_id, status)

ตารางนี้ช่วยให้ดึงรายการแจ้งเตือนของผู้ใช้ได้เร็ว รองรับการแสดงผลแบบยังไม่อ่าน อ่านแล้ว หรือเก็บถาวรได้สะดวก

4) เก็บเหตุการณ์ต้นทางเพื่อกันยิงซ้ำและตรวจสอบย้อนหลัง

ควรมีตาราง notification_events เพื่อเก็บต้นตอของการแจ้งเตือน เช่น

  • id (uuid)
  • event_key เช่น order:123:paid
  • type
  • actor_id ถ้ามีผู้กระทำ
  • target_user_id
  • payload_json
  • created_at

สิ่งสำคัญมากคือการตั้ง unique index ที่ event_key เพื่อป้องกันเหตุการณ์เดียวกันถูกสร้างซ้ำจากการยิง request ซ้ำหรือระบบทำงานซ้ำโดยไม่ตั้งใจ

ข้อดีของการมีตารางนี้คือ

  • กันการสร้าง notification ซ้ำ
  • ตรวจสอบย้อนหลังได้ว่าเหตุการณ์ใดถูกสร้างเมื่อไร
  • ใช้เป็นจุดอ้างอิงหลักสำหรับการส่งผ่านหลายช่องทาง

5) แยกตาราง Delivery สำหรับแต่ละช่องทาง

เมื่อมี Event แล้ว ควรสร้างตาราง notification_deliveries สำหรับติดตามการส่งแต่ละช่องทางโดยเฉพาะ เช่น

  • id (uuid)
  • event_id
  • user_id
  • channel เช่น inapp, email, push
  • provider เช่น ses, sendgrid, fcm, webpush
  • status เช่น pending, sending, sent, failed, canceled
  • attempts
  • next_retry_at
  • last_error
  • idempotency_key (unique)
  • created_at
  • sent_at

แนะนำให้ตั้ง unique index ที่ (channel, idempotency_key) เพื่อป้องกันการส่งซ้ำในช่องทางเดียวกัน

6) ใช้ idempotency_key เพื่อกันส่งซ้ำ

แนวทางที่เข้าใจง่ายและใช้งานได้ดี คือกำหนด

idempotency_key = event_key + ":" + channel

เช่น

  • order:123:paid:email
  • order:123:paid:push

ผลคือแม้ worker จะทำงานซ้ำ หรือมีการ retry เพราะปัญหาชั่วคราว ระบบก็จะไม่ส่งข้อความเดิมซ้ำในช่องทางเดิมโดยไม่จำเป็น

7) เปิดให้ผู้ใช้ตั้งค่า Preference ได้ละเอียด

ระบบแจ้งเตือนที่ดีควรให้ผู้ใช้เลือกรับหรือปิดการแจ้งเตือนแยกตามประเภทและช่องทางได้ โดยอาจใช้ตาราง notification_preferences เช่น

  • user_id
  • type
  • channel
  • enabled (true/false)
  • quiet_hours_json (optional)
  • created_at
  • updated_at

ควรตั้ง unique constraint ที่ (user_id, type, channel) และมี fallback ไปใช้ค่าเริ่มต้นจาก notification_type หากยังไม่มี record ของผู้ใช้

จุดนี้ช่วยลดความรำคาญและทำให้ผู้ใช้รู้สึกว่าควบคุมระบบได้

8) รองรับ Push และ WebPush ด้วยตาราง Device/Endpoint

สำหรับช่องทาง Push Notification ควรมีตาราง notification_endpoints เช่น

  • id
  • user_id
  • platform เช่น ios, android, web
  • token_or_subscription_json
  • status เช่น active, revoked
  • last_seen_at
  • created_at

พร้อมกำหนด unique เช่น

  • unique(user_id, token) หรือ
  • unique(token)

ตารางนี้ช่วยให้จัดการอุปกรณ์ของผู้ใช้ได้ดีขึ้น เช่น ปิด token ที่ใช้ไม่ได้ หรือล้าง endpoint ที่หมดอายุ

9) วาง Flow การทำงานให้เรียบง่ายและชัดเจน

เส้นทางการทำงานที่แนะนำมีดังนี้

  1. ระบบธุรกิจสร้าง event เช่น OrderPaid
  2. บันทึกลง notification_events พร้อม event_key
  3. สร้าง notifications สำหรับ In-app ทันทีหรือผ่าน job
  4. สร้าง notification_deliveries ตาม preference ของผู้ใช้
  5. ส่ง delivery เข้า queue เพื่อให้ worker แต่ละช่องทางนำไปส่ง

Flow นี้ช่วยให้ส่วนธุรกิจไม่ต้องผูกติดกับรายละเอียดการส่ง และทำให้ระบบตอบสนอง request หลักได้เร็วขึ้น

10) เลือก Queue และ Worker ให้เหมาะกับขนาดงาน

หากเริ่มต้นจากระบบขนาดเล็กถึงกลาง อาจใช้

  • Redis Queue
  • BullMQ
  • Sidekiq
  • RQ

หากเป็นระบบขนาดใหญ่ที่ต้องรองรับงานปริมาณมาก อาจพิจารณา

  • Kafka
  • RabbitMQ

หลักการสำคัญคือ event ควรถูกสร้างให้เร็ว ส่วนการส่งสามารถทำแบบ asynchronous ได้ เพื่อไม่ให้ request หลักของผู้ใช้ช้าเพราะต้องรอ Email หรือ Push ส่งสำเร็จก่อน

11) แยก Worker ตามช่องทางเพื่อลดผลกระทบข้ามระบบ

ควรแยก worker ตามประเภทการส่ง เช่น

  • worker-email สำหรับ Email โดยเฉพาะ
  • worker-push สำหรับ Push โดยเฉพาะ
  • worker-inapp สำหรับ In-app ซึ่งมักเป็นการบันทึกข้อมูลลงฐานข้อมูล

ข้อดีคือ

  • ควบคุม rate limit ของแต่ละช่องทางได้
  • แก้ปัญหาเฉพาะทางได้ง่าย เช่น bounce ของ email หรือ invalid token ของ push
  • หาก Email provider มีปัญหา ก็ไม่ทำให้ Push ล่มตามไปด้วย

12) ตั้งค่า Retry แบบมืออาชีพ

ระบบแจ้งเตือนควรมี retry policy ที่ชัดเจน โดยเฉพาะสำหรับความผิดพลาดชั่วคราว เช่น provider timeout หรือเครือข่ายขัดข้อง

ตัวอย่างการใช้ exponential backoff

  • ครั้งที่ 1 รอ 1 นาที
  • ครั้งที่ 2 รอ 5 นาที
  • ครั้งที่ 3 รอ 15 นาที
  • ครั้งที่ 4 รอ 60 นาที
  • ครั้งที่ 5 รอ 6 ชั่วโมง

ควรกำหนด attempts_max เช่น 5 ครั้ง และเมื่อเกินเพดานให้เปลี่ยนสถานะเป็น failed พร้อมบันทึก last_error เพื่อใช้วิเคราะห์ปัญหาในภายหลัง

13) ป้องกันการส่งซ้ำเมื่อ Worker ล่มกลางทาง

ปัญหาคลาสสิกของระบบ asynchronous คือ worker อาจ crash ระหว่างการส่ง ทำให้เกิดความไม่แน่ใจว่าส่งสำเร็จหรือยัง

แนวทางที่ควรใช้คือ

  • อัปเดต status = sending แบบ atomic ก่อนเรียก provider
  • เมื่อส่งสำเร็จแล้วจึงเปลี่ยนเป็น sent และบันทึก sent_at
  • หากรายการค้างอยู่ในสถานะ sending เกินเวลาที่กำหนด ให้มีระบบ recovery เพื่อนำกลับเข้าคิวใหม่

วิธีนี้ช่วยลดโอกาสส่งซ้ำและทำให้ระบบกู้คืนงานค้างได้อย่างปลอดภัย

14) สร้าง Delivery ตาม Preference และ Quiet Hours

ตรรกะการสร้าง Delivery ควรทำงานตามลำดับดังนี้

  • อ่าน default channels จาก notification_type
  • ตรวจสอบ notification_preferences ของผู้ใช้
  • ตัดช่องทางที่ผู้ใช้ปิดออก
  • หากอยู่ในช่วง quiet hours ให้เลื่อนเวลาไปส่งภายหลังผ่าน next_retry_at

แนวคิดนี้ช่วยให้ระบบฉลาดขึ้น ไม่รบกวนผู้ใช้ในเวลาที่ไม่เหมาะสม เช่น เวลากลางคืน หรือเวลาที่ผู้ใช้เลือกปิดการแจ้งเตือนชั่วคราว

15) ใช้ Dedup Window เพื่อลดการสแปมจากเหตุการณ์ถี่เกินไป

บางเหตุการณ์อาจเกิดขึ้นต่อเนื่องในเวลาสั้น ๆ เช่น การตอบคอมเมนต์หลายครั้งในเธรดเดียว หากส่งทันทีทุกครั้งอาจสร้างประสบการณ์ที่ไม่ดี

วิธีแก้ทำได้หลายแบบ เช่น

  • ใส่ช่วงเวลาใน event_key เช่น thread:55:reply:2026-01-19T10:00
  • ใช้ aggregate_key แล้วรวมหลายเหตุการณ์เป็น digest

แนวคิดนี้เหมาะกับ notification ที่ไม่จำเป็นต้อง real-time เป๊ะทุกเหตุการณ์ แต่ควรลดความถี่เพื่อความสบายของผู้ใช้

16) เพิ่ม Observability เพื่อดูแลระบบในระยะยาว

ระบบแจ้งเตือนที่ใช้งานจริงควรมีเครื่องมือสำหรับติดตามสุขภาพของระบบ เช่น

  • metrics: จำนวน sent, failed, retry แยกตาม channel
  • log ที่ผูกกับ event_id และ delivery_id
  • dashboard สำหรับดู backlog ของ queue และ retry rate

เมื่อมีข้อมูลเหล่านี้ ทีมพัฒนาจะสามารถวิเคราะห์ปัญหาได้เร็วขึ้น เช่น Email ส่งช้าผิดปกติ, Push ล้มเหลวมากขึ้น หรือ queue เริ่มค้างสะสม

17) คำนึงถึงความปลอดภัยและความเป็นส่วนตัว

ใน payload_json ไม่ควรเก็บข้อมูลอ่อนไหว เช่น

  • เลขบัตรประชาชน
  • token ลับ
  • ข้อมูลทางการเงินที่ละเอียดเกินจำเป็น

สำหรับ Email ควรใส่เฉพาะข้อมูลเท่าที่จำเป็น และลิงก์ที่แนบไปต้องตรวจสอบสิทธิ์การเข้าถึงเสมอ เพื่อไม่ให้เกิดปัญหาข้อมูลรั่วไหลหากอีเมลถูกส่งต่อหรือถูกเข้าถึงโดยผู้ไม่เกี่ยวข้อง

18) ปรับ UX ให้ระบบดูเป็นมืออาชีพมากขึ้น

นอกจาก backend ที่ดีแล้ว ประสบการณ์ใช้งานก็สำคัญไม่แพ้กัน ตัวอย่างฟีเจอร์ที่ช่วยให้ดูเป็นระบบมากขึ้น ได้แก่

  • จัดกลุ่ม In-app เป็น วันนี้ / เมื่อวาน / ก่อนหน้านี้
  • มีปุ่ม mark all as read
  • คลิกแล้วพาไปยังหน้าปลายทางได้ทันที
  • สำหรับ Push ให้รองรับ deep link และ action button หากแพลตฟอร์มรองรับ

รายละเอียดเล็ก ๆ เหล่านี้ทำให้ระบบแจ้งเตือนใช้งานง่ายขึ้นและเพิ่มความรู้สึกเป็นมืออาชีพได้อย่างชัดเจน

19) ตัวอย่างเคสจริงแบบสั้น

สมมติว่าเกิดเหตุการณ์ OrderPaid

  • กำหนด event_key = order:123:paid
  • สร้าง In-app notification 1 รายการ
  • สร้าง deliveries 2 รายการ คือ email และ push ตาม preference ของผู้ใช้
  • ให้ worker แยกกันส่ง Email และ Push
  • หากล้มเหลว ให้ retry ตามนโยบายที่ตั้งไว้
  • ใช้ event_key และ idempotency_key ช่วยกันป้องกันการสร้างซ้ำและส่งซ้ำ

ตัวอย่างนี้สะท้อนโครงสร้างที่ดีของระบบแจ้งเตือน คือแยกความรับผิดชอบชัดเจน ควบคุมแต่ละช่องทางได้ และพร้อมรับปัญหาที่เกิดขึ้นจริง

สรุป

การสร้างระบบแจ้งเตือนบนเว็บแบบมืออาชีพไม่ใช่แค่การส่งข้อความให้ผู้ใช้เห็น แต่คือการออกแบบทั้งกระบวนการให้เชื่อถือได้ ยืดหยุ่น และดูแลได้ในระยะยาว หลักสำคัญที่ควรยึดคือ

  • ใช้ event_key แบบ unique เพื่อกันการสร้าง event ซ้ำ
  • ใช้ idempotency_key เพื่อกันการส่งซ้ำในแต่ละ channel
  • แยก queue และ worker ตามช่องทางเพื่อลดผลกระทบข้ามกัน
  • ใช้ retry แบบ backoff และมีระบบ recovery สำหรับงานค้าง
  • รองรับ preference และ quiet hours เพื่อลดความรำคาญของผู้ใช้

หากค่อย ๆ ทำตาม checklist เหล่านี้ทีละขั้น ระบบแจ้งเตือนของคุณจะไม่เพียงแค่ใช้งานได้ แต่ยังพร้อมขยาย รองรับงานจริง และรักษาคุณภาพประสบการณ์ของผู้ใช้ได้อย่างต่อเนื่อง