> ## Documentation Index
> Fetch the complete documentation index at: https://documentation.onesignal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 予約確認と放棄予約の回復

> カスタムイベント、Journeys、Data Feedsを使用して、リアルタイムの予約ステータスに基づいて予約確認または回復メールを送信します。

## 概要

このチュートリアルでは、一般的な予約ワークフローを設定します：

* ユーザーが予約を完了した後に予約確認メールを送信します。
* ユーザーが予約を開始したが時間内に完了しなかった場合に回復メールを送信します。

最終的に、以下を持つことになります：

* 2つのカスタムイベント（`booking_started`、`booking_complete`）
* 完了対放棄で分岐する1つのJourney
* 確認詳細用の予約Data Feed
* 回復インセンティブ用のオプションのクーポンData Feed

このガイドはOneSignalの設定に焦点を当てています。予約システムとバックエンドは任意の言語またはフレームワークで実装できます。

### セットアップフロー

1. アプリが`booking_started` [カスタムイベント](./custom-events)を追跡します。
2. これによりユーザーがJourneyに入ります。
3. Journeyは`booking_complete`イベントを待ち、時間内に受信されない場合、フォローアップリマインダーを送信します。
4. 予約が完了すると、OneSignalは送信時に予約Data Feedを呼び出し、最新の予約詳細を含む確認メールを送信します。
5. 予約が待機ウィンドウ内で完了しない場合、Journeyは有効期限パスに従い、回復メールを送信します。

***

## セットアップ

### 前提条件

開始する前に、以下を確認してください：

* [**メール**](./email-setup) チャネルが有効化されたOneSignalアプリ
* 予約および/またはクーポンデータをJSONで返すことができるバックエンドエンドポイント
* アプリ、バックエンド、OneSignalの[External ID](./users#external-id)間で共有される安定したユーザー識別子
* [カスタムイベント](./custom-events)へのアクセス

### 1. 予約イベントの追跡

以下のカスタムイベントを追跡します。これらはアプリ（SDKを使用）またはバックエンド（REST APIを使用）から送信できます。

**イベント名:**

* `booking_started` — ユーザーが予約フローを開始したとき
* `booking_complete` — 予約が正常に完了したとき

<Tabs>
  <Tab title="アプリ - モバイル・Web">
    [モバイルSDK](./mobile-sdk-reference)および/または[Web SDK](./web-sdk-reference)の`trackEvent()`メソッドを使用して、アプリ/ウェブサイトから直接カスタムイベントを送信します。

    ```js 例 theme={null}
    OneSignal.User.trackEvent("booking_started");
    OneSignal.User.trackEvent("booking_complete");
    ```
  </Tab>

  <Tab title="サーバーサイド">
    バックエンドからイベントを追跡している場合は、[カスタムイベント作成API](/reference/create-custom-events)を使用してOneSignalにイベントを送信します。

    ```bash theme={null}
    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": {}
          }
        ]
      }'
    ```
  </Tab>
</Tabs>

<Note>
  イベントの追跡時とバックエンドからデータを返すときに同じユーザー識別を使用してください。不一致のIDは、パーソナライゼーションが欠落する最も一般的な原因です。
</Note>

### 2. Data Feedエイリアスの作成

OneSignalで、**設定 > Data Feeds**に移動し、以下のエイリアスを作成します。

**予約Data Feed:**
このフィードを使用して、送信時に最新の予約詳細を取得します。

* **エイリアス:** `booking_data`
* **メソッド:** GET
* **URL:**

```liquid エンドポイント例 theme={null}
https://your-domain.com/datafeed/booking?user_id={{subscription.external_id}}
```

レスポンス例：

```json JSON theme={null}
{
  "first_name": "Sam",
  "last_booking": {
    "service_type": "相談",
    "booking_date": "2026年1月22日",
    "booking_time": "14:00",
    "price": 45
  }
}
```

**クーポンData Feed（オプション）:**

回復メールにクーポンコードを含めたい場合は、このオプションのフィードを使用します。

* **エイリアス:** `coupon`
* **メソッド:** GET
* **URL:**

```liquid エンドポイント例 theme={null}
https://your-domain.com/datafeed/coupon?user_id={{subscription.external_id}}
```

レスポンス例：

```json JSON theme={null}
{
  "first_name": "Sam",
  "code": "PROMO8F3K2",
  "discount_text": "10%",
  "expires_in_hours": 2,
  "deep_link": "https://your-domain.com/checkout?coupon=PROMO8F3K2"
}
```

<Warning>
  Data Feedエンドポイントを保護してください。本番環境では、リクエストヘッダーでAPIキー（例：`x-api-key`）を送信し、URLにシークレットを埋め込む代わりに**設定 > Data Feeds**でそのヘッダーを設定してください。
</Warning>

### 3. メールテンプレートの作成

#### 予約確認メール：

**件名：**

```text theme={null}
ご予約の詳細
```

**本文：**

```liquid theme={null}
{{ 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 }}円

お会いできるのを楽しみにしています！
```

#### 予約回復メール

**件名：**

```text theme={null}
ご予約を完了してお得に
```

**本文：**

<Tabs>
  <Tab title="クーポンData Feedを使用">
    ```liquid theme={null}
    {{ 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 }}
    ```
  </Tab>

  <Tab title="クーポンData Feedを使用しない">
    ```text theme={null}
    お客様へ 

    ご予約がまだ完了していません！ 

    今すぐ完了して、次回のご予約でお得になりましょう。

    このリンクを使用してご予約を完了してください：
    [ここにディープリンクを挿入]
    ```
  </Tab>
</Tabs>

<Note>
  Data Feedフィールドが欠落している場合に空白コンテンツを防ぐため、Liquidには常に`default`フィルターを含めてください。
</Note>

### 4. Journeyの構築

1. OneSignalで、**メッセージ > Journeys > Journeyを作成**に移動

2. **エントリートリガー**を以下に設定：
   * **カスタムイベント:** `booking_started`

3. **Wait Until**ステップを追加：
   * **条件:** カスタムイベントが発生
   * **イベント名:** `booking_complete`
   * **最大待機時間:** 10分
   * 有効期限パスを有効化

4. ブランチを設定：
   * **完了:** 予約確認メールを送信
     * **Data Feed:** `booking_data`
   * **期限切れ:** 回復メールを送信
     * **Data Feed:** `coupon`

<Info>
  有効期限ブランチにより、アプリで追加のロジックなしに放棄を処理できます。詳細は以下を参照：

  * [Journey設定](./journeys-settings) - カスタムイベントのエントリーおよび出口ルールの詳細
  * [Journeys アクション](./journeys-actions) - Wait Untilステップと有効期限ブランチの詳細
</Info>

### 5. テストと検証

#### イベントの確認

アプリまたはバックエンドからカスタムイベントをトリガーして確認します。

OneSignalで、**Analytics > カスタムイベント**に移動し、以下が表示されることを確認：

* External IDに対して`booking_started`イベントが表示される
* External IDに対して`booking_complete`イベントが表示される

#### Data Feedの確認

既知のユーザーIDを使用してData Feedエンドポイントを手動で呼び出し、以下を確認：

* 200レスポンスが返される
* すべての予期されるフィールドが存在する

#### メールの確認

Journeyエディターからテストメッセージを送信し、以下を確認：

* 予約メールに実際の予約詳細が含まれている
* 回復メールに有効なクーポンが含まれている
* Liquid変数が空でレンダリングされていない

<Check>
  パーソナライゼーションが欠落している場合は、Data FeedリクエストのユーザーIDがJourneyをトリガーしたユーザーと一致することを確認してください。
</Check>

## 例：Data Feed実装

<Accordion title="Node.js Data Feedエンドポイントの例">
  この例では、予約確認および回復Data Feed用の最小限のExpress実装を示します。JSONレスポンス形式がメールテンプレートと一致する限り、バックエンド言語、フレームワーク、データソースは異なっても構いません。

  ### 予約Data Feedの例

  ```js theme={null}
  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の例

  ```js theme={null}
  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キーでエンドポイントを保護
</Accordion>

## よくある問題

### メールに空の値が表示される

* Data Feedが404を返した
* JSONレスポンスでフィールド名が変更された
* ユーザー識別の不一致

### Journeyが分岐しない

* `booking_complete`イベントが追跡されていない
* イベント名の不一致（大文字小文字を区別）
* 待機ウィンドウの外でイベントが発生

### Data Feedが401または403を返す

* APIキーが欠落または無効
* Data Feed設定でヘッダーが設定されていない

## 次のステップ

* より高度なJourney条件のためにイベントプロパティ（サービスタイプ、価格）を追加
* プッシュまたはSMSリマインダーなど、追加の回復ステップを追加
* 重複する回復メッセージを防ぐためにJourney出口ルールを使用

***
