Skip to main content

Campaign

ภาพรวม​

Campaign คือเมนูที่ทีมการตลาดใช้ส่งข้อความออกไปหาเพื่อนใน LINE OA ทั้งแบบส่งหาทุกคน (Broadcast) และส่งเฉพาะกลุ่มเป้าหมาย (Multicast) จุดสำคัญที่ต้องรู้ก่อนเริ่มคือ แคมเปญไม่ได้สร้างเนื้อหาข้อความเอง ต้องมี Rich message ที่เปิดใช้งานอยู่แล้วจากเมนู Rich message ก่อนเสมอ

บทบาทที่ใช้งาน — ทีมการตลาด/แอดมินที่ได้รับสิทธิ์โมดูล Campaign (โมดูลนี้มี ModuleGate บังคับจริงที่ backend — ดูหัวข้อ สิทธิ์การเข้าถึง)

เส้นทางผู้ใช้งาน​

1. ดูรายการแคมเปญ​

เปิดเมนู Campaign หน้ารายการเริ่มที่แท็บ ส่งแล้ว เสมอ เรียงจากใหม่ไปเก่าตามวันที่ส่ง สลับไปดูแท็บอื่นได้ — ตั้งเวลา (เรียงจากใกล้ที่สุดขึ้นก่อน), ยกเลิก และ ฉบับร่าง (ทั้งสองแท็บนี้เรียงตามวันที่แก้ไขล่าสุด) แต่ละแท็บมีคอลัมน์วันที่ที่ความหมายเปลี่ยนไปตามแท็บ (วันที่ส่ง / ยกเลิกเมื่อ / แก้ไขล่าสุดเมื่อ) กำกับด้วย "(เวลากรุงเทพฯ)" เสมอ

ตัวกรองมี 3 ช่อง — ค้นหาจากชื่อ, เลือกผู้รับ (ดึงจาก endpoint แยกที่คืนเฉพาะกลุ่มเป้าหมายที่ มีแคมเปญอยู่จริง เท่านั้น ไม่เสนอตัวเลือกที่ไม่มีข้อมูลรองรับ — ถ้ามีแคมเปญ Broadcast อยู่จริงจะมีตัวเลือก "เพื่อนทั้งหมดใน OA" ขึ้นก่อนเสมอ), และช่วงวันที่ ผลการกรองจะถูกจดจำไว้ใน sessionStorage ระหว่างที่อยู่ในหน้านี้

2. สร้างและส่งแคมเปญ​

  1. กดปุ่มสร้างแคมเปญใหม่ เข้าสู่ฟอร์ม
  2. กรอกชื่อแคมเปญ — ต้องไม่ซ้ำกับแคมเปญอื่นในระบบเดียวกัน ไม่เกิน 100 ตัวอักษร (ข้อจำกัดความยาวนี้บังคับแค่ฝั่งหน้าเว็บเท่านั้น — API ฝั่ง backend ไม่มีการตรวจความยาวชื่อเลย ตรวจแค่ว่าไม่ใช่ค่าว่าง)
  3. เลือกผู้รับ — ส่งหาเพื่อนทั้งหมดบน OA (Broadcast) หรือเลือกกลุ่มเป้าหมายที่เตรียมไว้แล้วจากเมนู Segment audience (Multicast)
  4. เลือกเนื้อหา — เลือก Rich message ที่เปิดใช้งานอยู่จากรายการ ระบบจะโชว์ตัวอย่างข้อความในกรอบแชททันที พร้อม Quick Reply ที่ผูกไว้ (ถ้ามี)
    • หากเลือกส่งแบบ Broadcast ตัวเลือก Rich message ที่มี merge tag (เช่น {{name}}) จะยังปรากฏในรายการแต่ถูก disabled พร้อมคำอธิบายว่าใช้ไม่ได้กับ "เพื่อนทั้งหมดใน OA" — และถ้าสลับจาก Multicast มา Broadcast ทั้งที่เพิ่งเลือก Rich message แบบมี merge tag ไว้ ระบบจะล้างค่าที่เลือกให้อัตโนมัติพร้อมข้อความอธิบายเหตุผล ไม่ปล่อยให้ปุ่มบันทึกดูเหมือนใช้งานไม่ได้แบบไม่มีสาเหตุ
    • หากเลือกส่งแบบ Multicast และ Rich message ที่เลือกมี merge tag จะมีเช็กบ็อกซ์เพิ่มขึ้นมาให้ติ๊ก "ข้ามการส่งให้ผู้ใช้ที่มีข้อมูล merge tag ไม่ครบถ้วน"
  5. เลือกวิธีส่ง — ส่งทันที, ตั้งเวลา, หรือเก็บเป็นฉบับร่าง
    • ถ้าเลือก ฉบับร่าง ระบบจะผ่อนคลายทุกช่องอื่นให้ไม่บังคับ ต้องมีแค่ชื่อแคมเปญเท่านั้นจึงจะบันทึกได้
    • ถ้าเลือก ตั้งเวลา ระบบเติมวันเวลาให้อัตโนมัติเป็นปัจจุบัน + 35 นาที (เผื่อเวลา 5 นาทีให้กรอกฟอร์มทัน) แต่กฎจริงคือต้องห่างจากเวลาปัจจุบันอย่างน้อย 30 นาที ตรวจสอบสองชั้น (ขณะพิมพ์ + ตอนกดบันทึก) หากเลือกวันเป็นวันนี้ ช่องเลือกชั่วโมง/นาทีที่ผ่านมาแล้วจะถูกปิดไว้ให้
  6. กดบันทึก ระบบเปิดกล่องยืนยันที่ข้อความและปุ่มเปลี่ยนไปตามวิธีส่งที่เลือก (ส่งทันที / บันทึกแคมเปญ / บันทึกเป็นฉบับร่าง) เมื่อยืนยัน ระบบเรียก POST /campaign (สร้าง/คัดลอก) หรือ PUT /campaign/{id} (แก้ไข)

3. ดูผลลัพธ์แคมเปญที่ส่งแล้ว​

  1. จากแท็บ "ส่งแล้ว" คลิกชื่อแคมเปญ (หรือคลิกตัวเลข "กิจกรรมทั้งหมด" ในตาราง) เพื่อเปิดหน้าดูผลลัพธ์ — ทุกช่องในฟอร์มถูกปิดการแก้ไข
  2. ดูการ์ดสรุป 4 ใบ — ส่งแล้ว, การเข้าถึง, คลิกที่ไม่ซ้ำ, คลิกทั้งหมด สามการ์ดหลัง (ยกเว้น "ส่งแล้ว") กดได้เพื่อเปิดแผงด้านข้างแสดงรายชื่อผู้ใช้ LINE จริงที่ทำกิจกรรมนั้น พร้อมรูปโปรไฟล์ ชื่อ จำนวนครั้ง เวลาที่ทำครั้งแรก/ครั้งล่าสุด และแบ่งหน้าได้
  3. เจาะลึกลงตารางแยกรายข้อความ (Message A, B, C…) เพื่อดูว่าข้อความ/ปุ่มใดถูกคลิกมากที่สุด — ข้อความชนิดรูปภาพ/วิดีโอแสดงคอลัมน์จำนวนการดู ส่วนชนิดรูปภาพกดได้/Flex แสดงคอลัมน์ตำแหน่ง ข้อมูล ประเภท คลิกไม่ซ้ำ คลิกทั้งหมด (คลิกตัวเลขเพื่อเปิดแผงรายชื่อผู้ใช้เจาะจงปุ่ม/ลิงก์นั้นได้)
  4. ข้อความชนิดตัวอักษรล้วนจะระบุตรงว่า "ไม่สามารถติดตามการคลิกได้"

4. จัดการแคมเปญที่มีอยู่​

  • ฉบับร่าง — แก้ไขได้ (ปุ่มดินสอ) หรือลบได้ (ต้องยืนยันก่อน)
  • ตั้งเวลาไว้ — ยกเลิกได้ (ต้องยืนยันก่อน แคมเปญที่ยกเลิกจะย้ายไปแท็บ "ยกเลิก" ทันที)
  • ทุกสถานะ — คัดลอกได้เสมอ (พาไปฟอร์มสร้างใหม่โดยดึงข้อมูลจากแคมเปญเดิมมาเป็นต้นแบบ)

รายละเอียดสำคัญที่ตรวจสอบจากซอร์สโค้ดจริง​

ยืนยันจาก internal/modules/campaign/{controller,service,dto,tracking}.go ของ line-management-cms-api-go:

  • การส่งทันทีเป็นแบบ "จองสิทธิ์ก่อนส่ง" (claim-based) — เมื่อกด "ส่งทันที" backend ไม่ได้เซ็ตสถานะเป็น "sent" ตรง ๆ แต่ใช้คำสั่ง SQL แบบมีเงื่อนไขเพื่อเปลี่ยนสถานะเป็น sending ก่อน (UPDATE ... WHERE status NOT IN ('sending','sent')) ถ้าไม่มีแถวถูกแก้ไข (เช่น มีอีกคำขอกดส่งซ้ำพร้อมกัน หรืองานเดิมกำลังส่งอยู่แล้ว) จะตอบกลับ 409 Conflict ทันทีโดยไม่ publish งานซ้ำ เป็นกลไกป้องกันการกดส่งซ้ำสองครั้ง (double-click) ไม่ให้ส่งข้อความซ้ำ
  • สถานะ sending ไม่มีอยู่ในหน้าจอฝั่งหน้าเว็บเลย — enum สถานะที่ frontend รู้จักมีแค่ sent / scheduled / cancel / draft เท่านั้น หมายความว่าในช่วงเวลาสั้น ๆ ระหว่างกดส่งทันทีจนกว่า worker จะประมวลผลเสร็จและเปลี่ยนสถานะเป็น sent แคมเปญนั้นจะไม่ปรากฏในแท็บใดเลย ของหน้ารายการ (ไม่อยู่ในตั้งเวลา ไม่อยู่ในฉบับร่าง ไม่อยู่ในส่งแล้ว) เป็นพฤติกรรมชั่วคราวที่ควรรู้ไว้เผื่อผู้ใช้สงสัยว่าแคมเปญที่กดส่งไปหายไปไหน
  • การตรวจ merge tag ใช้ regex ที่เข้มงวด — ต้องเป็นรูปแบบ {{...}} ที่ปิดด้วยวงเล็บปีกกาคู่จริง ๆ ({{name}} ตรวจพบ แต่ข้อความที่พิมพ์วงเล็บไม่ครบจะไม่ถูกตรวจพบและถูกส่งออกไปตามตัวอักษร) และ backend ตรวจซ้ำอีกชั้นตอนบันทึกจริง ไม่ได้เชื่อผลตรวจจากฝั่งหน้าเว็บอย่างเดียว — ถ้าเนื้อหามี merge tag แต่ตั้ง castType = broadcast จะถูกปฏิเสธด้วย 400 "Merge tags are only supported with multicast campaigns" แม้เรียก API ตรงข้ามหน้าเว็บก็ตาม
  • ตัวเลข "ส่งแล้ว" ในหน้าผลลัพธ์ไม่ได้เก็บเป็นค่านิ่งเสมอ — ถ้ายังไม่มีค่าที่บันทึกไว้ (totalRecipient = 0) backend จะคำนวณสด: สำหรับ Multicast จะไปอ่านค่า totalSuccess จากสรุปผลของกลุ่มเป้าหมายในเมนู Segment audience แล้วเขียนกลับเข้าแคมเปญไว้ใช้ครั้งต่อไป ส่วน Broadcast จะนับจำนวนผู้ใช้ไม่ซ้ำจาก log การติดตามผลโดยตรง — เป็นเหตุผลที่ตัวเลข "ส่งแล้ว" อาจยังเป็น 0 อยู่พักหนึ่งหลังส่งไป ก่อนที่ระบบติดตามผลจะมีข้อมูลเข้ามาให้คำนวณ
  • ข้อมูลการติดตามผลใช้โครงสร้างแบบผสมระหว่างค่าที่คำนวณล่วงหน้ากับข้อมูลสด — ตัวเลขคลิก/การดูของแต่ละข้อความถูกเก็บสรุปไว้เป็นระยะ (มีเคอร์เซอร์วันที่ "คำนวณล่าสุดถึงเมื่อไหร่") แล้วนำมารวมกับ log เหตุการณ์ที่เกิดขึ้น "หลัง" เคอร์เซอร์นั้นแบบสดทุกครั้งที่เปิดหน้าดูผลลัพธ์ ทำให้อ่านได้เร็วแม้แคมเปญมีผู้รับและการคลิกจำนวนมาก
  • การอ้างอิงชื่อซ้ำมีจุดที่ backend เขียนต่างจากพฤติกรรมเดิมโดยเจตนา — ถ้าไม่ได้ส่งชื่อมาเลยตอนแก้ไข (เช่น กดยกเลิกแคมเปญที่ส่งแค่ {status: "cancel"}) ระบบจะไม่ตรวจชื่อซ้ำ เพื่อไม่ให้การแก้ไขที่ไม่ได้แตะชื่อเลยถูกปฏิเสธด้วยข้อความ "Title is already exist" อย่างผิดพลาด

เมนูที่เกี่ยวข้องกันในเส้นทางนี้​

  • Rich message — ต้องสร้างและเปิดใช้งานเนื้อหาไว้ก่อนจึงจะสร้างแคมเปญได้
  • Segment audience — จำเป็นเมื่อส่งแบบ Multicast และเป็นแหล่งของ "totalSuccess" ที่ใช้คำนวณยอด "ส่งแล้ว" ของแคมเปญ Multicast ด้วย ส่วนตัวเลือก "campaign" ที่ใช้เป็นเงื่อนไขสร้างกลุ่มเป้าหมายในเมนูนั้น จะดึงเฉพาะแคมเปญที่ ส่งแล้วและส่งภายใน 3 เดือนล่าสุดเท่านั้น มาให้เลือก (กรองด้วย query parameter พิเศษที่ backend เตรียมไว้ให้)
  • Quick reply — ปุ่มตอบกลับที่ผูกกับ Rich Message จะติดไปกับแคมเปญโดยอัตโนมัติ
  • Campaign Planner — มุมมองปฏิทินของแคมเปญชุดเดียวกันนี้

จุดที่ควรระวัง​

  • การกดส่งสำเร็จหมายถึง "รับงานเข้าคิวแล้ว" ไม่ใช่ "ส่งถึงผู้รับแล้ว" — cms-api ไม่ได้ยิงข้อความไปที่ LINE เอง เพียง publish งานเข้า RabbitMQ (คิว line_broadcast_rich_message หรือ line_multicast_rich_message ตามประเภท) ให้ worker เป็นผู้ส่งจริง
  • ระบบป้องกันการกดส่งซ้ำด้วยกลไก claim ที่กล่าวถึงข้างต้นแล้ว แต่ ไม่มีปุ่ม/สถานะใดในหน้าเว็บที่บอกผู้ใช้ตรง ๆ ว่า "กำลังส่งอยู่" — ถ้าแคมเปญหายไปจากทุกแท็บชั่วครู่หลังกดส่งทันที ให้รอสักครู่แล้วรีเฟรชหน้าอีกครั้ง
  • ความยาวชื่อแคมเปญ (ไม่เกิน 100 ตัวอักษร) เป็นข้อจำกัดฝั่งหน้าเว็บเท่านั้น ไม่มีการตรวจซ้ำที่ backend

สิทธิ์การเข้าถึง (ตรวจสอบจากซอร์สโค้ดจริง)​

  • ฝั่งหน้าเว็บ — ถูกครอบด้วย PermissionGuard ตรวจ action VIEW ของ subject campaign-management
  • ฝั่งเซิร์ฟเวอร์ — ทุกเส้นทางของโมดูลนี้ถูกครอบด้วย ModuleGate("campaign") ที่บังคับใช้จริง (ต่างจากโมดูล Dashboard ที่ policy เป็น no-op) — ถ้าผู้ดูแลระดับแพลตฟอร์มปิดโมดูลแคมเปญให้องค์กรใด ผู้ใช้ในองค์กรนั้นจะได้ 403 ทันทีแม้เรียก API ตรง ไม่ว่าจะผ่านหน้าเว็บหรือไม่
  • policy annotation ระดับการกระทำ (create/update/delete/read/readAll) มีประกาศไว้แต่วิ่งผ่าน middleware ที่เป็น no-op เหมือนโมดูลอื่นส่วนใหญ่ในระบบ — การบังคับจริงจึงอยู่ที่ ModuleGate ระดับโมดูล ไม่ใช่ที่ policy ระดับการกระทำ
  • DELETE /api/campaign/:id เป็น soft delete (เปลี่ยนสถานะเป็น cancel แล้วประทับเวลาลบ) ส่วนการลบจริง (DELETE /api/campaign/:id/hard) ต้องเป็น Super Admin เท่านั้น และต้องส่ง query parameter confirm=true มาด้วย ไม่เช่นนั้นจะถูกปฏิเสธ

ไฟล์อ้างอิงจากซอร์สโค้ด​

Frontend (cms-web-2026)

  • src/app/campaign-management/page.tsx, src/app/campaign-management/form/page.tsx — จุดเข้าหน้ารายการและฟอร์ม
  • src/components/campaign-management/list/ — campaign-management.container.tsx (ตาราง/แท็บ/ตัวกรอง), campaign-management-filter.tsx
  • src/components/campaign-management/form/ — campaign-management-form.container.tsx, campaign-management-form.tsx (กฎ merge tag, กฎเวลา, การ์ดผลลัพธ์), tracking-users-drawer.tsx
  • src/services/campaign-management.service.ts — endpoint ทั้งหมดที่หน้าเว็บเรียกใช้

Backend (line-management-cms-api-go)

  • internal/modules/campaign/controller.go — 8 เส้นทาง, ModuleGate, SuperAdminGuard ของ hard delete
  • internal/modules/campaign/service.go — ตรรกะสร้าง/แก้ไข/ลบ, กลไก claim ก่อนส่ง, cache Redis
  • internal/modules/campaign/dto.go — กฎตรวจสอบข้อมูลขาเข้า
  • internal/modules/campaign/tracking.go — การรวมข้อมูลติดตามผลแบบผสม pre-aggregate + สด

ดูรายละเอียดเชิงเทคนิคเพิ่มเติม (เวอร์ชันก่อนหน้าของทีมเอกสาร) ที่ จัดการแคมเปญ Broadcast