このチュートリアルでは、一般的な予約ワークフローを設定します:
- ユーザーが予約を完了した後に予約確認メールを送信します。
- ユーザーが予約を開始したが時間内に完了しなかった場合に回復メールを送信します。
最終的に、以下を持つことになります:
- 2つのカスタムイベント(
booking_started、booking_complete)
- 完了対放棄で分岐する1つのJourney
- 確認詳細用の予約Data Feed
- 回復インセンティブ用のオプションのクーポンData Feed
このガイドはOneSignalの設定に焦点を当てています。予約システムとバックエンドは任意の言語またはフレームワークで実装できます。
セットアップフロー
- アプリが
booking_started カスタムイベントを追跡します。
- これによりユーザーがJourneyに入ります。
- Journeyは
booking_completeイベントを待ち、時間内に受信されない場合、フォローアップリマインダーを送信します。
- 予約が完了すると、OneSignalは送信時に予約Data Feedを呼び出し、最新の予約詳細を含む確認メールを送信します。
- 予約が待機ウィンドウ内で完了しない場合、Journeyは有効期限パスに従い、回復メールを送信します。
セットアップ
前提条件
開始する前に、以下を確認してください:
- メール チャネルが有効化されたOneSignalアプリ
- 予約および/またはクーポンデータをJSONで返すことができるバックエンドエンドポイント
- アプリ、バックエンド、OneSignalのExternal ID間で共有される安定したユーザー識別子
- カスタムイベントへのアクセス
1. 予約イベントの追跡
以下のカスタムイベントを追跡します。これらはアプリ(SDKを使用)またはバックエンド(REST APIを使用)から送信できます。
イベント名:
booking_started — ユーザーが予約フローを開始したとき
booking_complete — 予約が正常に完了したとき
モバイルSDKおよび/またはWeb SDKのtrackEvent()メソッドを使用して、アプリ/ウェブサイトから直接カスタムイベントを送信します。OneSignal.User.trackEvent("booking_started");
OneSignal.User.trackEvent("booking_complete");
バックエンドからイベントを追跡している場合は、カスタムイベント作成APIを使用してOneSignalにイベントを送信します。curl --request POST \
--url https://api.onesignal.com/apps/{app_id}/custom_events \
--header 'Authorization: Key YOUR_APP_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"events": [
{
"name": "booking_started",
"external_id": "user123",
"properties": {}
}
]
}'
イベントの追跡時とバックエンドからデータを返すときに同じユーザー識別を使用してください。不一致のIDは、パーソナライゼーションが欠落する最も一般的な原因です。
2. Data Feedエイリアスの作成
OneSignalで、設定 > Data Feedsに移動し、以下のエイリアスを作成します。
予約Data Feed:
このフィードを使用して、送信時に最新の予約詳細を取得します。
- エイリアス:
booking_data
- メソッド: GET
- URL:
https://your-domain.com/datafeed/booking?user_id={{subscription.external_id}}
レスポンス例:
{
"first_name": "Sam",
"last_booking": {
"service_type": "相談",
"booking_date": "2026年1月22日",
"booking_time": "14:00",
"price": 45
}
}
クーポンData Feed(オプション):
回復メールにクーポンコードを含めたい場合は、このオプションのフィードを使用します。
- エイリアス:
coupon
- メソッド: GET
- URL:
https://your-domain.com/datafeed/coupon?user_id={{subscription.external_id}}
レスポンス例:
{
"first_name": "Sam",
"code": "PROMO8F3K2",
"discount_text": "10%",
"expires_in_hours": 2,
"deep_link": "https://your-domain.com/checkout?coupon=PROMO8F3K2"
}
Data Feedエンドポイントを保護してください。本番環境では、リクエストヘッダーでAPIキー(例:x-api-key)を送信し、URLにシークレットを埋め込む代わりに設定 > Data Feedsでそのヘッダーを設定してください。
3. メールテンプレートの作成
予約確認メール:
件名:
本文:
{{ data_feed.booking_data.first_name | default: "お客様" }}さん、こんにちは
ご予約ありがとうございます!ご予約の詳細は以下の通りです:
サービス:{{ data_feed.booking_data.last_booking.service_type }}
日付:{{ data_feed.booking_data.last_booking.booking_date }}
時間:{{ data_feed.booking_data.last_booking.booking_time }}
料金:{{ data_feed.booking_data.last_booking.price }}円
お会いできるのを楽しみにしています!
予約回復メール
件名:
本文:
クーポンData Feedを使用
クーポンData Feedを使用しない
{{ data_feed.coupon.first_name | default: "お客様" }}さん、こんにちは
今後{{ data_feed.coupon.expires_in_hours }}時間以内にご予約を完了すると、
このコードで{{ data_feed.coupon.discount_text }}オフになります:
{{ data_feed.coupon.code }}
こちらでご利用ください:
{{ data_feed.coupon.deep_link }}
お客様へ
ご予約がまだ完了していません!
今すぐ完了して、次回のご予約でお得になりましょう。
このリンクを使用してご予約を完了してください:
[ここにディープリンクを挿入]
Data Feedフィールドが欠落している場合に空白コンテンツを防ぐため、Liquidには常にdefaultフィルターを含めてください。
4. Journeyの構築
-
OneSignalで、メッセージ > Journeys > Journeyを作成に移動
-
エントリートリガーを以下に設定:
- カスタムイベント:
booking_started
-
Wait Untilステップを追加:
- 条件: カスタムイベントが発生
- イベント名:
booking_complete
- 最大待機時間: 10分
- 有効期限パスを有効化
-
ブランチを設定:
- 完了: 予約確認メールを送信
- 期限切れ: 回復メールを送信
有効期限ブランチにより、アプリで追加のロジックなしに放棄を処理できます。詳細は以下を参照:
5. テストと検証
イベントの確認
アプリまたはバックエンドからカスタムイベントをトリガーして確認します。
OneSignalで、Analytics > カスタムイベントに移動し、以下が表示されることを確認:
- External IDに対して
booking_startedイベントが表示される
- External IDに対して
booking_completeイベントが表示される
Data Feedの確認
既知のユーザーIDを使用してData Feedエンドポイントを手動で呼び出し、以下を確認:
- 200レスポンスが返される
- すべての予期されるフィールドが存在する
メールの確認
Journeyエディターからテストメッセージを送信し、以下を確認:
- 予約メールに実際の予約詳細が含まれている
- 回復メールに有効なクーポンが含まれている
- Liquid変数が空でレンダリングされていない
パーソナライゼーションが欠落している場合は、Data FeedリクエストのユーザーIDがJourneyをトリガーしたユーザーと一致することを確認してください。
例:Data Feed実装
Node.js Data Feedエンドポイントの例
この例では、予約確認および回復Data Feed用の最小限のExpress実装を示します。JSONレスポンス形式がメールテンプレートと一致する限り、バックエンド言語、フレームワーク、データソースは異なっても構いません。予約Data Feedの例
import express from "express";
const app = express();
function dataFeedAuth(req, res, next) {
if (req.headers["x-api-key"] !== process.env.DATAFEED_API_KEY) {
return res.status(401).json({ error: "Unauthorized" });
}
next();
}
app.get("/datafeed/booking", dataFeedAuth, async (req, res) => {
const { user_id } = req.query;
if (!user_id) {
return res.status(400).json({ error: "Missing user_id" });
}
const booking = await getLatestBookingForUser(user_id);
if (!booking) {
return res.status(404).json({ error: "No booking found" });
}
res.json({
first_name: booking.first_name,
last_booking: {
service_type: booking.service_type,
booking_date: booking.booking_date,
booking_time: booking.booking_time,
price: booking.price
}
});
});
クーポンData Feedの例
app.get("/datafeed/coupon", dataFeedAuth, async (req, res) => {
const { user_id } = req.query;
if (!user_id) {
return res.status(400).json({ error: "Missing user_id" });
}
const coupon = await generateCouponForUser(user_id);
res.json({
first_name: coupon.first_name,
code: coupon.code,
discount_text: coupon.discount_text,
expires_in_hours: coupon.expires_in_hours,
deep_link: coupon.deep_link
});
});
実装ガイドライン
- レスポンスを高速に保つ(Data Feedは送信時に呼び出される)
- 常に予測可能なJSON構造を返す
- データが存在しない場合は404を使用
- リクエストヘッダー経由で送信されるAPIキーでエンドポイントを保護
よくある問題
メールに空の値が表示される
- Data Feedが404を返した
- JSONレスポンスでフィールド名が変更された
- ユーザー識別の不一致
Journeyが分岐しない
booking_completeイベントが追跡されていない
- イベント名の不一致(大文字小文字を区別)
- 待機ウィンドウの外でイベントが発生
Data Feedが401または403を返す
- APIキーが欠落または無効
- Data Feed設定でヘッダーが設定されていない
次のステップ
- より高度なJourney条件のためにイベントプロパティ(サービスタイプ、価格)を追加
- プッシュまたはSMSリマインダーなど、追加の回復ステップを追加
- 重複する回復メッセージを防ぐためにJourney出口ルールを使用