Calendly
Calendly は広く使われているオンライン予約ツールですが、標準の SMS リマインダーは対応国・地域が限られており、アジア太平洋・東南アジア・中東などではカバーが限定的です。Calendly Webhooks と EngageLab SMS を連携すると、予約の作成・キャンセル時やミーティング開始前に、世界中の電話番号へ SMS 通知を送れます。Calendly 標準機能では届きにくい地域を補完できます。
前提条件
始める前に、次の設定が済んでいることを確認してください。
EngageLab 側
- EngageLab SMS が有効になっていること
- テンプレート管理で SMS テンプレートを作成し承認済みであること。テンプレート ID を取得済みであること
- API Keys ページで API キーを作成し、
dev_keyとdev_secretを取得済みであること
Calendly 側
- Calendly Standard 以上のプランであること(Webhook は有料プランが必要です)
- Integrations & apps → API and webhooks で Personal Access Token を作成済みであること
サーバー側
- インターネットから到達可能なサーバーと、有効な HTTPS 証明書が設定されていること
- ローカル開発・デバッグでは ngrok で一時的にローカルポートを公開できます
ステップ 1: SMS テンプレートの準備
API 経由の送信には事前承認済みテンプレートが必要で、自由文をそのまま送ることはできません。
EngageLab コンソールにログインし、SMS → テンプレート管理へ進み、シナリオごとに次の 3 種類のテンプレートを作成します。
| テンプレート用途 | サンプル文言 |
|---|---|
| 予約確定 | {{name}}様、ミーティングは{{time}}に確定しました。お会いできるのを楽しみにしています。 |
| キャンセル通知 | {{name}}様、ご予約のミーティングはキャンセルされました。再調整の際はご連絡ください。 |
| ミーティングリマインダー | {{name}}様、{{advance}}後にミーティングが{{time}}から始まります。事前のご準備をお願いします。 |
テンプレートを提出したら承認を待ち、3 つのテンプレート ID を控えておきます。
ヒント: シナリオごとにテンプレートを分けると意図が明確になり、承認されやすくなります。カスタム変数(例:
{{name}})を含める場合は、API 呼び出し時にparamsで値を渡す必要があります。渡さないと変数名がそのまま送信されます。
ステップ 2: Calendly で Webhook 購読を作成する
Calendly には Webhook 管理のビジュアル UI がないため、API で購読を作成します。
Organization URI の取得
まず、次のエンドポイントを呼び出してアカウントの 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 購読の作成
organization URI を使い、予約の作成・キャンセルイベントを購読する Webhook を作成します。
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 を返すとハンドシェイクが完了します。その後、予約・キャンセルイベントがこの URL にプッシュされます。
注意:
urlは公開 HTTPS で到達可能である必要があります。ローカル開発では ngrok で一時 URL を取得できます:ngrok http 3000の出力にあるhttps://xxxx.ngrok.ioなどを使います。
ステップ 3: Webhook 受信サービスの実装
以下は、Calendly の Webhook イベントを受信し、EngageLab SMS API を呼び出してメッセージを送る Node.js サーバーの完全な例です。
依存関係のインストール
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 のペイロードのうち、SMS 送信に直接関係するフィールドは次のとおりです。
| フィールド | 説明 |
|---|---|
event |
イベント種別: invitee.created(予約作成)または invitee.canceled(予約キャンセル) |
payload.text_reminder_number |
招待された人が入力した電話番号(国番号を含む)。空の場合あり |
payload.invitee.name |
招待された人の名前 |
payload.scheduled_event.start_time |
ミーティング開始時刻(ISO 8601 形式) |
拡張シナリオ
ミーティング後のフォローアップ SMS
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);
}
ホストにも通知する
予約作成時に、招待された人だけでなくミーティングのホストにも SMS を送れます。
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) },
});
}
注意事項
- Webhook は 2 秒以内に
HTTP 200を返す必要があります。 超えると Calendly はプッシュ失敗とみなして再送します。先に 200 を返し、SMS 送信は非同期で実行することを推奨します。EngageLab API の遅延で Calendly が誤判定するのを防げます。 text_reminder_numberは空の場合があります。 コードで null チェックを行い、空の番号を SMS API に渡さないようにしてください。エラーの原因になります。- 電話番号には国番号を含めてください。 例: 中国本土は
+8618701235678。Calendly は電話番号入力時に国際形式を促しますが、サーバー側での形式検証も推奨します。 setTimeoutは本番向きではありません。 サーバー再起動でスケジュールはすべて失われます。本番では BullMQ + Redis などの永続キューやクラウドの定期実行を使い、リマインダー記録を DB に残して復旧できるようにしてください。- テンプレートは利用前に承認が必要です。 審査中や却下のテンプレートを呼ぶと、API は
4001エラーを返します。 - API が HTTP 200 を返しても SMS 配信成功を保証しません。 レスポンス本文の
codeを確認してください。0 以外の値は エラーコードのドキュメント を参照してください。
