Calendly
Calendly เป็นเครื่องมือนัดหมายออนไลน์ที่ใช้กันอย่างแพร่หลาย แต่ฟีเจอร์แจ้งเตือน SMS ในตัวรองรับเพียงบางประเทศและภูมิภาคเท่านั้น โดยมีความครอบคลุมที่จำกัดในเอเชียแปซิฟิก เอเชียตะวันออกเฉียงใต้ ตะวันออกกลาง และตลาดอื่นๆ การรวม Calendly Webhooks เข้ากับ EngageLab SMS ช่วยให้คุณสามารถส่งการแจ้งเตือน SMS ไปยังหมายเลขโทรศัพท์ทั่วโลกเมื่อมีการสร้างนัดหมาย ยกเลิก หรือก่อนการประชุมจะเริ่มต้น — เติมเต็มช่องว่างทางภูมิศาสตร์ของความสามารถดั้งเดิมของ Calendly
ข้อกำหนดเบื้องต้น
ก่อนเริ่มต้น ตรวจสอบให้แน่ใจว่าการกำหนดค่าต่อไปนี้เสร็จสมบูรณ์:
ฝั่ง EngageLab
- บริการ EngageLab SMS ถูกเปิดใช้งานแล้ว
- สร้างเทมเพลต SMS และได้รับการอนุมัติในหน้าจัดการเทมเพลตแล้ว; ได้รับ ID เทมเพลตแล้ว
- สร้างคีย์ API ในหน้า API Keys แล้ว; ได้รับ
dev_keyและdev_secretแล้ว
ฝั่ง Calendly
- คุณมีแผน Calendly Standard ขึ้นไป (ฟังก์ชัน Webhook ต้องใช้แผนชำระเงิน)
- สร้าง Personal Access Token ในหน้า Integrations & apps → API and webhooks แล้ว
ฝั่งเซิร์ฟเวอร์
- คุณมีเซิร์ฟเวอร์ที่เข้าถึงได้จากอินเทอร์เน็ตสาธารณะและมีใบรับรอง HTTPS ที่ถูกต้อง
- สำหรับการพัฒนาและดีบักในเครื่อง คุณสามารถใช้ ngrok เพื่อเปิดเผยพอร์ตในเครื่องชั่วคราว
ขั้นตอนที่ 1: เตรียมเทมเพลต SMS
การส่ง SMS ผ่าน API ต้องใช้เทมเพลตที่ได้รับการอนุมัติล่วงหน้า; ไม่สามารถส่งข้อความที่กำหนดเองโดยตรงได้
เข้าสู่ระบบคอนโซล EngageLab ไปที่ SMS → จัดการเทมเพลต และสร้างเทมเพลตสามรายการต่อไปนี้สำหรับแต่ละสถานการณ์:
| วัตถุประสงค์เทมเพลต | ตัวอย่างเนื้อหา |
|---|---|
| ยืนยันนัดหมาย | สวัสดี {{name}} การประชุมของคุณได้รับการยืนยันเวลา {{time}} แล้ว รอพบคุณนะ |
| แจ้งยกเลิก | สวัสดี {{name}} นัดหมายการประชุมของคุณถูกยกเลิกแล้ว กรุณาติดต่อเราเพื่อนัดหมายใหม่ |
| เตือนการประชุม | สวัสดี {{name}} คุณมีการประชุมที่จะเริ่มใน {{advance}} กำหนดเวลา {{time}} กรุณาเตรียมตัวล่วงหน้า |
หลังจากส่งเทมเพลต รอการอนุมัติและจดบันทึก ID เทมเพลตทั้งสามรายการ
เคล็ดลับ: สร้างเทมเพลตแยกสำหรับแต่ละสถานการณ์ — ทำให้เจตนาชัดเจนขึ้นและเพิ่มอัตราการอนุมัติ หากเทมเพลตมีตัวแปรที่กำหนดเอง (เช่น
{{name}}) คุณต้องส่งค่าผ่านฟิลด์paramsเมื่อเรียก API มิฉะนั้นตัวแปรจะถูกส่งตามที่เป็น
ขั้นตอนที่ 2: สร้าง Webhook Subscription ใน Calendly
Calendly ไม่มีอินเทอร์เฟซแบบภาพสำหรับจัดการ Webhook; ต้องสร้าง subscription ผ่าน API
รับ Organization URI
ขั้นแรก เรียก endpoint ต่อไปนี้เพื่อรับ organization URI สำหรับบัญชีของคุณ:
curl https://api.calendly.com/users/me \
-H "Authorization: Bearer YOUR_PERSONAL_ACCESS_TOKEN"
ค้นหาฟิลด์ current_organization ในการตอบกลับ ซึ่งมีรูปแบบดังนี้:
https://api.calendly.com/organizations/xxxxxxxxxxxxxxxx
สร้าง Webhook Subscription
ใช้ organization URI เพื่อสร้าง Webhook ที่ subscribe เหตุการณ์การสร้างและยกเลิกนัดหมาย:
curl -X POST https://api.calendly.com/webhook_subscriptions \
-H "Authorization: Bearer YOUR_PERSONAL_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server-address/webhooks/calendly",
"events": [
"invitee.created",
"invitee.canceled"
],
"organization": "https://api.calendly.com/organizations/your-org-id",
"scope": "organization"
}'
เมื่อสร้างเสร็จ Calendly จะส่งคำขอยืนยันไปยัง url ที่ให้ไว้ และเซิร์ฟเวอร์ของคุณต้องตอบกลับด้วย HTTP 200 เพื่อเสร็จสิ้นการ handshake เหตุการณ์นัดหมายและการยกเลิกในภายหลังจะถูกส่งไปยังที่อยู่นี้
หมายเหตุ:
urlต้องเป็นที่อยู่ HTTPS ที่เข้าถึงได้จากสาธารณะ สำหรับการพัฒนาในเครื่อง คุณสามารถใช้ ngrok เพื่อสร้างที่อยู่ชั่วคราว:ngrok http 3000จากนั้นใช้ URLhttps://xxxx.ngrok.ioที่ได้
ขั้นตอนที่ 3: สร้างบริการรับ Webhook
ด้านล่างเป็นตัวอย่างเซิร์ฟเวอร์ Node.js แบบสมบูรณ์ที่รับเหตุการณ์ Calendly Webhook และเรียก EngageLab SMS API เพื่อส่งข้อความ
ติดตั้ง Dependencies
npm install express
โค้ดแบบสมบูรณ์
// server.js
import express from 'express';
const app = express();
app.use(express.json());
// EngageLab authentication: base64(dev_key:dev_secret)
const ENGAGELAB_AUTH = Buffer.from(
`${process.env.ENGAGELAB_DEV_KEY}:${process.env.ENGAGELAB_DEV_SECRET}`
).toString('base64');
// Call EngageLab SMS API to send a message
async function sendSMS({ to, templateId, params }) {
const res = await fetch('https://smsapi.engagelab.com/v1/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Basic ${ENGAGELAB_AUTH}`,
},
body: JSON.stringify({
to: [to],
template: {
id: templateId,
params,
},
}),
});
const data = await res.json();
// HTTP 200 does not guarantee delivery; check the code field
if (data.code && data.code !== 0) {
console.error(`SMS sending failed: code=${data.code}, message=${data.message}`);
} else {
console.log(`SMS sent successfully: plan_id=${data.plan_id}, message_id=${data.message_id}`);
}
}
// Convert an ISO time string to local time (Asia/Shanghai)
function formatTime(isoString) {
return new Date(isoString).toLocaleString('en-US', {
timeZone: 'Asia/Shanghai',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
});
}
// Schedule pre-meeting reminders (24h and 1h before)
function scheduleReminders({ phone, name, startTime }) {
const meetingTime = new Date(startTime).getTime();
const now = Date.now();
const reminders = [
{ advance: '24 hours', triggerAt: meetingTime - 24 * 60 * 60 * 1000 },
{ advance: '1 hour', triggerAt: meetingTime - 60 * 60 * 1000 },
];
for (const { advance, triggerAt } of reminders) {
const delay = triggerAt - now;
if (delay <= 0) continue; // Trigger time has passed, skip
setTimeout(async () => {
await sendSMS({
to: phone,
templateId: process.env.TEMPLATE_ID_REMINDER,
params: { name, time: formatTime(startTime), advance },
});
}, delay);
}
}
// Webhook receiver endpoint
app.post('/webhooks/calendly', async (req, res) => {
const { event, payload } = req.body;
const phone = payload.text_reminder_number; // Phone number provided by the invitee
const name = payload.invitee?.name ?? 'User';
const startTime = payload.scheduled_event?.start_time;
// Skip SMS if no phone number is provided
if (!phone) {
console.log('No phone number provided for this appointment, skipping SMS notification');
return res.sendStatus(200);
}
if (event === 'invitee.created') {
// Send appointment confirmation SMS
await sendSMS({
to: phone,
templateId: process.env.TEMPLATE_ID_CONFIRM,
params: { name, time: formatTime(startTime) },
});
// Schedule pre-meeting reminders
scheduleReminders({ phone, name, startTime });
} else if (event === 'invitee.canceled') {
// Send cancellation notice SMS
await sendSMS({
to: phone,
templateId: process.env.TEMPLATE_ID_CANCEL,
params: { name },
});
}
// Must return 200 within 2 seconds, otherwise Calendly will consider the push failed and retry
res.sendStatus(200);
});
app.listen(3000, () => console.log('Server running on port 3000'));
การกำหนดค่าตัวแปรสภาพแวดล้อม
สร้างไฟล์ .env ในไดเรกทอรีรากของโปรเจกต์ด้วยตัวแปรต่อไปนี้:
ENGAGELAB_DEV_KEY=your_dev_key
ENGAGELAB_DEV_SECRET=your_dev_secret
TEMPLATE_ID_CONFIRM=appointment_confirmation_template_id
TEMPLATE_ID_CANCEL=cancellation_notice_template_id
TEMPLATE_ID_REMINDER=meeting_reminder_template_id
ข้อมูลอ้างอิงฟิลด์สำคัญ
ฟิลด์ต่อไปนี้ใน Calendly Webhook payload เกี่ยวข้องโดยตรงกับการส่ง SMS:
| ฟิลด์ | คำอธิบาย |
|---|---|
event |
ประเภทเหตุการณ์: invitee.created (สร้างนัดหมาย) หรือ invitee.canceled (ยกเลิกนัดหมาย) |
payload.text_reminder_number |
หมายเลขโทรศัพท์ที่ผู้ได้รับเชิญให้ไว้ รวมรหัสประเทศ; อาจว่างเปล่า |
payload.invitee.name |
ชื่อผู้ได้รับเชิญ |
payload.scheduled_event.start_time |
เวลาเริ่มการประชุมในรูปแบบ ISO 8601 |
สถานการณ์ขยาย
ส่ง SMS ติดตามหลังการประชุม
เพิ่ม trigger หลังการประชุมใน scheduleReminders เพื่อส่งแบบสำรวจความพึงพอใจหรือคำเชิญนัดหมายติดตาม:
// Append to the scheduleReminders function
const followUpAt = meetingTime + 30 * 60 * 1000; // 30 minutes after the meeting ends
const followUpDelay = followUpAt - now;
if (followUpDelay > 0) {
setTimeout(async () => {
await sendSMS({
to: phone,
templateId: process.env.TEMPLATE_ID_FOLLOWUP,
params: { name },
});
}, followUpDelay);
}
แจ้งเตือนผู้จัดประชุมด้วย
เมื่อสร้างนัดหมาย นอกจากแจ้งเตือนผู้ได้รับเชิญแล้ว คุณยังสามารถส่งการเตือนไปยังผู้จัดประชุมได้:
if (event === 'invitee.created') {
// Notify the invitee
await sendSMS({
to: phone,
templateId: process.env.TEMPLATE_ID_CONFIRM,
params: { name, time: formatTime(startTime) },
});
// Notify the host
await sendSMS({
to: process.env.HOST_PHONE,
templateId: process.env.TEMPLATE_ID_HOST_NOTIFY,
params: { name, time: formatTime(startTime) },
});
}
หมายเหตุสำคัญ
- Webhooks ต้องตอบกลับด้วย
HTTP 200ภายใน 2 วินาที มิฉะนั้น Calendly จะถือว่าการส่งล้มเหลวและลองใหม่ แนะนำให้ตอบกลับ 200 ก่อน จากนั้นดำเนินการส่ง SMS แบบอะซิงโครนัสเพื่อหลีกเลี่ยงการตัดสินผิดพลาดของ Calendly เนื่องจากความล่าช้าของ EngageLab API - ฟิลด์
text_reminder_numberอาจว่างเปล่า — เพิ่มการตรวจสอบค่า null ในโค้ดของคุณเพื่อหลีกเลี่ยงการส่งหมายเลขว่างไปยัง SMS API ซึ่งจะทำให้เกิดข้อผิดพลาด - หมายเลขโทรศัพท์ต้องมีรหัสประเทศ เช่น
+8618701235678สำหรับหมายเลขจีนแผ่นดินใหญ่ Calendly จะแนะนำผู้ใช้ให้กรอกรูปแบบสากลเมื่อเก็บหมายเลขโทรศัพท์ แต่แนะนำให้ตรวจสอบรูปแบบฝั่งเซิร์ฟเวอร์ด้วย setTimeoutไม่เหมาะสำหรับสภาพแวดล้อมการผลิต — งานที่กำหนดเวลาทั้งหมดจะสูญหายเมื่อเซิร์ฟเวอร์รีสตาร์ท สำหรับการผลิต ใช้คิวงานแบบถาวร (เช่น BullMQ + Redis) หรืองานที่กำหนดเวลาบนคลาวด์ และเก็บบันทึกการเตือนในฐานข้อมูลเพื่อกู้คืนหลังจากรีสตาร์ท- เทมเพลตต้องได้รับการอนุมัติก่อนใช้งาน — หากเทมเพลตอยู่ระหว่างการตรวจสอบหรือถูกปฏิเสธเมื่อเรียกใช้ API จะคืนค่าข้อผิดพลาด
4001 - การตอบกลับ HTTP 200 จาก API ไม่รับประกันการส่ง SMS สำเร็จ — ตรวจสอบฟิลด์
codeในเนื้อหาการตอบกลับ ดูเอกสารรหัสข้อผิดพลาด สำหรับค่าที่ไม่ใช่ศูนย์
