วิธีส่ง SMS ผ่าน ThaiBulkSMS API ใน Laravel

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

เผยแพร่
เวลาอ่าน
4 นาที
ในโพสต์นี้ 6 หัวข้อ
  1. ส่ง SMS หนึ่งข้อความคือ HTTP request เดียว
  2. ชื่อผู้ส่งที่ยังไม่ได้ลงทะเบียนจะไม่ถึงปลายทาง
  3. เครดิตแยกตามประเภท SMS
  4. เบอร์ที่ส่งไม่ได้กลับมาในคำตอบที่ดูเหมือนสำเร็จ
  5. ตัวส่งแบบเขียน log สำหรับเครื่องที่ไม่ใช่ production
  6. กรณีที่อาจเจอในงานจริง

SMS ยังเป็นช่องทางที่ร้านและองค์กรในไทยใช้กันมาก ทั้งส่งรหัส OTP ตอนสมัครหรือ login แจ้งสถานะคำสั่งซื้อ และเตือนวันนัด ลูกค้าไม่ต้องติดตั้งอะไร มีเบอร์โทรก็ได้รับ

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

ส่ง SMS หนึ่งข้อความคือ HTTP request เดียว

API ส่งข้อความของ ThaiBulkSMS เป็น POST ธรรมดา ใช้ API key กับ secret เป็น Basic auth และส่งข้อมูลสี่ช่อง

ช่อง ใส่อะไร
msisdn เบอร์ปลายทาง ใส่ได้ทั้ง 0812345678 และ 66812345678 คั่นด้วย comma เพื่อส่งหลายเบอร์ได้
message ข้อความ
sender ชื่อผู้ส่งที่ลงทะเบียนไว้
force ประเภทของ SMS คือ standard หรือ corporate ซึ่งใช้เครดิตคนละก้อน

ฝั่ง Laravel ใช้ HTTP client ที่มีอยู่แล้วได้เลย ไม่ต้องติดตั้งแพ็กเกจเพิ่ม ส่วนรหัส OTP ThaiBulkSMS มี API แยกอีกตัว ใช้ key คนละชุด ซึ่งเป็นหนึ่งในทางเลือกที่เทียบไว้ในโพสต์เรื่อง OTP ของซีรีส์นี้

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

ชื่อผู้ส่งที่ยังไม่ได้ลงทะเบียนจะไม่ถึงปลายทาง

ชื่อผู้ส่ง (sender name) คือชื่อที่ขึ้นแทนเบอร์โทรบนมือถือของผู้รับ ในไทยชื่อนี้ต้องลงทะเบียนกับผู้ให้บริการก่อนใช้ และใช้เวลาหลายวันกว่าจะผ่าน

จุดที่อันตรายคือ ถ้าส่งด้วยชื่อที่ยังไม่ผ่าน API ไม่ได้ตอบ error ข้อความแค่ไม่ถึงผู้รับ ระบบที่ไม่ได้คิดเรื่องนี้จะบันทึกว่าส่งแล้ว และกว่าจะรู้ก็ตอนลูกค้าโทรมาบอกว่าไม่ได้รับรหัส OTP

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

เครดิตแยกตามประเภท SMS

บัญชีของ ThaiBulkSMS มีเครดิตสองก้อน คือ standard กับ corporate การส่งแต่ละครั้งหักจากก้อนที่ตรงกับ force ที่ส่งไป

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

ทางแก้มีสองส่วน ตั้งประเภทที่ใช้ส่งเป็นค่าเดียวทั้งระบบ ให้ตรงกับก้อนที่ร้านซื้อ และให้หน้าตั้งค่าในระบบแสดงเครดิตที่เหลือแยกทั้งสองก้อน (API เช็กเครดิตเรียกได้ฟรี) พอมีคนบอกว่าไม่ได้รับ SMS ก็เริ่มดูจากตัวเลขจริงได้เลย

เบอร์ที่ส่งไม่ได้กลับมาในคำตอบที่ดูเหมือนสำเร็จ

เวลาเบอร์ปลายทางผิดรูปแบบหรือส่งไม่ได้ ThaiBulkSMS ยังตอบ status 201 เหมือนส่งสำเร็จ แต่ใส่เบอร์นั้นไว้ในรายการ bad_phone_number_list ของคำตอบ ส่วนเบอร์ที่ส่งได้อยู่ใน phone_number_list พร้อมเครดิตที่ใช้ และบาง error ของทั้ง request ก็มาเป็นก้อน error ในเนื้อคำตอบ

ระบบที่เช็กแค่ว่า status เป็น 2xx จะบันทึกว่าส่งแล้วทั้งที่ข้อความไม่ได้ออกไป ตัวส่งจึงต้องเปิดเนื้อคำตอบทุกครั้ง

$response = Http::withBasicAuth($key, $secret)->asForm()->post('https://api-v2.thaibulksms.com/sms', [...]);

// status 2xx ยังไม่พอ เบอร์ที่ส่งไม่ได้อยู่ในเนื้อคำตอบ
if ($response->json('error') || $response->json('bad_phone_number_list')) {
    throw new SmsNotSent($response->body());
}

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

ตัวส่งแบบเขียน log สำหรับเครื่องที่ไม่ใช่ production

SMS ทุกข้อความเสียเงิน และส่งถึงคนจริง เครื่องของนักพัฒนา เครื่องทดสอบ และ CI จึงไม่ควรส่งจริง เราแยกการส่ง SMS ไว้หลัง interface ที่มี method เดียว คือส่งข้อความหนึ่งข้อความไปที่เบอร์หนึ่งเบอร์ แล้วมีตัวส่งสองแบบ

  • ตัวส่งจริง เรียก API ของผู้ให้บริการ ใช้บน production
  • ตัวส่งแบบ log เขียนข้อความลง log แทน ใช้บนเครื่องอื่นทั้งหมด นักพัฒนาอ่านรหัส OTP จาก log ได้เลย

เลือกตัวไหนด้วยค่าใน .env ส่วน test ก็ใช้ fake แทนการส่ง ตามแบบใน กัน test ใน Laravel ไม่ให้ส่งอีเมล ยิง API หรือเข้าคิวจริง

log ของ SMS มีข้อมูลส่วนบุคคลและรหัส OTP อยู่ ถ้าระบบเปิดให้พนักงานหลายคนอ่าน log ได้ ตัวส่งแบบ log บนเครื่องที่ไม่ใช่ของนักพัฒนาควรปิดเลขกลางของเบอร์ (08xxxx5678) และไม่เขียนเนื้อข้อความ เปิดเนื้อข้อความได้เฉพาะเครื่องทดสอบที่ใช้ข้อมูลสมมติ

กรณีที่อาจเจอในงานจริง

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

ตอนถัดไปของซีรีส์ ติดต่อและยืนยันตัวตนลูกค้า

อ่านต่อ

ถูกอ้างถึงใน

ล่าสุด

ดูทั้งหมด →