2026年10月25日
DMと公式問い合わせのメッセージシステム
DMと問い合わせを統合するメッセージングシステムを導入。
導入
コミュニケーションと通知
#プラットフォーム
eric

公式アカウントを拡充する中で、ゲームセンターから、Musecat内で問い合わせを受け付けて返信したいという要望が生まれた。利用者が質問を残し、担当者が管理画面から答えられるだけでなく、担当が交代した後もやり取りを引き継げる必要があった。

現在のPreviewでは、個人DM、店舗への問い合わせ、Musecatチームへの問い合わせに共通のメッセージエンジンを使っている。エンジンとは、保存、順序付け、途切れた会話の復旧を担う内部処理である。参加できる人や返信の名義、問い合わせの進捗は、会話の種類に応じて扱う。

個人メッセージと公式問い合わせ

個人DMは二人の利用者による非公開の会話である。新たに送信するときは、相手の受信設定と双方のブロックを確認する。運営者にもすべてのDMを閲覧する権限は与えていない。参加済みの会話を読む条件と、新しいメッセージを受け取る条件は個別に適用する。

公式問い合わせは、特定の担当者個人ではなく窓口に届く質問として扱う。例えば店舗の担当アカウントが交代しても、交代前に受け付けた質問への対応は続けられる必要がある。このため過去の担当者を永久の参加者としてコピーせず、閲覧と送信のたびに現在の公式アカウントの関係と運営上の役割を確認する。チーム宛ての問い合わせにも、その窓口の権限を適用する。

Mermaid
flowchart TD
    accTitle: 共通のメッセージ基盤と異なる会話権限
    accDescr: 個人DMと公式問い合わせは異なるアクセス方針を持ち、保存と取得と更新を共通の基盤で処理する。
    D[個人DM] --> P[二人の参加者と受信およびブロック方針]
    I[店舗とチームへの問い合わせ] --> Q[依頼者と現在の担当権限]
    P --> M[共通メッセージ基盤]
    Q --> M
    M --> H[順序を持つ会話履歴]
    M --> A[権限を確認する添付取得]
    M --> E[変更イベントと通知]

店舗はサイト内で受け付けるほか、外部の連絡先へ案内したり、新規受付を閉じたりできる。受付設定によって既存の問い合わせの閲覧資格まで変更するわけではない。保存処理を共用することで再送や添付を重複実装せず、窓口ごとの条件を組み込める。

会話とメッセージの保存構造

会話を中心に、その配下へメッセージを保存する。会話には種類と最新のメッセージ順序を持たせ、サーバーが各メッセージに与えるsequenceで並び順を決める。既読位置は利用者ごとに保存するため、ある担当者が読んでも別の担当者まで既読にはならない。

問い合わせには依頼者、窓口、対応状態の情報を追加する。個人DMに不要な業務情報は問い合わせにだけ持たせる構成である。店舗管理ダッシュボードも別のメッセージのコピーを作る場所ではなく、同じ会話を開く入口となる。

Mermaid
erDiagram
    accTitle: 会話とメッセージおよび問い合わせ状態の関係
    accDescr: 共通の会話にメッセージと主体と個人別の既読位置を接続し、問い合わせだけに業務状態を追加する。
    CONVERSATION ||--o{ MESSAGE : "メッセージを持つ"
    CONVERSATION ||--o{ PARTY : "主体を接続する"
    CONVERSATION ||--o{ READ_CURSOR : "利用者別の既読"
    CONVERSATION ||--o| INQUIRY : "問い合わせに追加"
    CONVERSATION {
        string id
        string kind
        int last_sequence
    }
    MESSAGE {
        int sequence
        string sender_user
        string acting_type
        string acting_id
        string client_message_id
    }
    PARTY {
        string party_type
        string party_id
    }
    READ_CURSOR {
        string user
        int last_read_sequence
    }
    INQUIRY {
        string requester
        string inbox_type
        string inbox_id
        string status
    }

ダッシュボードでは受付設定と対応待ちの問い合わせを確認できる。お知らせ管理や営業状態と同じ画面にあっても権限はそれぞれ検査する。会話の既読、問い合わせの進捗、店舗の公開・閉店状態は、表している対象が異なるためである。

公式返信の作成者と名義

店舗名で表示される返信にも、実際に入力したログイン利用者がいる。担当交代後に過去の応対を確認するには、その人と返信の名義の両方が必要になる。サーバーは実際のアカウントをsender_user、応対する主体をactingAsとして保存し、当時の名前と役割も残す。

DMで使えるのは本人名義だけである。問い合わせでは依頼者と公式の応対担当を区別し、両方の資格を持つ人には名義を選んでもらう。画面はサーバーから選択可能な名義と状態変更を受け取り、送信時にはサーバーが改めて資格を確認する。

添付写真にも会話の閲覧権限を適用する。同一オリジンのAPIからブラウザーのセッションをサーバー認証につなぎ、会話、メッセージ、ファイルの所属を確認して保護された保存先から提供する。URLやファイル名を知っているだけでは閲覧できない。実際のファイル形式と容量もサーバーで検証する。

メッセージの順序と重複送信

送信後に通信が切れると、保存できたか利用者には分からない。例えばサーバーでの保存直後にレスポンスだけが失われた場合、再送を新しい投稿として扱うと同じ文章が二度表示される。画面は作成した項目にclientMessageIdを付け、再試行でもIDと内容を維持する。

サーバーは会話、実際の作成者、クライアントIDの組で既存の要求を探す。本文と写真のハッシュが一致すれば保存済みのメッセージを返し、同じIDで内容や名義を変えた要求は競合として拒否する。新規送信では順序の更新、メッセージ、最終メッセージ情報、問い合わせ状態、受信者の通知一覧と送信待ちを一つのトランザクションで保存する。失敗すればまとめて取り消し、外部プッシュと変更イベントはコミット成功後に処理する。

Mermaid
sequenceDiagram
    accTitle: 再送を識別し関連する保存を一緒に確定する
    accDescr: 同じクライアントIDの再送は既存メッセージを返し、新規要求は順序と問い合わせ状態および通知処理を一緒に保存する。
    participant C as 会話画面
    participant A as メッセージAPI
    participant D as データベース
    participant E as イベント接続
    C->>A: クライアントIDと本文と写真
    A->>D: 現在の権限と既存要求を確認
    alt 同じ内容の保存済み要求
        D-->>A: 既存メッセージ
    else 新しい要求
        A->>D: 順序とメッセージと問い合わせ状態を保存
        A->>D: 対象の通知一覧と配信待機列を保存
        D-->>A: 一緒に確定
        A-->>E: 会話の変更通知
    end
    A-->>C: 保存済みメッセージ

送信中は仮の項目を表示し、レスポンスを得た後も取得した履歴に同じクライアントIDが現れるまで保持して統合する。失敗した項目は再送または削除できる。ただし、この送信待ちは画面のメモリー上にあり、ブラウザーを閉じた後まで残る永続的なオフライン送信箱ではない。

リアルタイム更新と会話の復旧

更新の通知にはSSEを使う。SSEはサーバーからブラウザーへ変更の合図を送る接続である。ページ共通のProviderがログインアカウントの接続を一つ維持し、メッセージ一覧と通知一覧が共有する。

conversation.changedには会話IDと最新の順序が入る。画面は手元の最終順序より後のメッセージをAPIで取得し、IDで統合してsequence順に並べる。最初の取得中にイベントが来た場合も、続けて不足分を取得する。履歴はサーバーにあるため、タブの休止や切断があっても問い合わせによって欠けた区間を復旧できる。

Mermaid
flowchart TD
    accTitle: 共有イベント接続から会話履歴を復旧する
    accDescr: 初期履歴を読み、SSEの変更に応じて後続の順序を取得する。切断や復帰では再同期し権限を失った場合は記録を消す。
    S[最初の会話履歴を取得] --> H[最後の順序を保持]
    E[共有SSEの変更通知] --> F[最後の順序より後を取得]
    H --> F
    F --> M[メッセージIDで統合して順序で整列]
    M --> H
    O[接続の切断またはタブの復帰] --> R[再接続と再同期]
    R --> F
    P[補完の取得] --> F
    F -->|アクセス不可| C[会話履歴と仮の送信項目を除去]

再接続と再同期に加え、通常の接続中は30秒ごと、画面が表示されていて切断中なら5秒ごとに履歴を確認する。一度の追従処理量は制限し、残りは次の更新へ引き継ぐ。セッションが切れたり閲覧資格が失われたりした場合は、その会話の履歴と仮の送信項目を消し、以前受信した非公開内容を表示し続けない。

既読処理と問い合わせの状態管理

問い合わせに返信しても、現場の問題が直ちに解決するとは限らない。例えば担当者が筐体の状態を確認すると答えた場合、利用者はその返信を読んだ後も修理の結果を待つことになる。誰がどこまで読んだかに加え、対応がどの段階にあるかを表す必要がある。

既読位置は、その人が確認した最後のメッセージの順番である。サーバーはlastReadSequenceとして保存し、過去の順番へ戻さない。会話がアクティブに表示され、最新メッセージまで見えているときに更新する。イベントの受信やバックグラウンド取得だけでは既読にしない。

Mermaid
stateDiagram-v2
    accTitle: メッセージに連動する問い合わせの業務状態
    accDescr: 新規問い合わせは待機から始まり公式返答で確認済みへ移る。完了後も依頼者の新しい質問で待機へ戻る。
    state "待機" as Waiting
    state "確認済み" as Recognised
    state "完了" as Solved
    [*] --> Waiting: 新規問い合わせ
    Waiting --> Recognised: 公式名義の返答
    Recognised --> Solved: 完了を選択
    Waiting --> Solved: 完了を選択
    Solved --> Waiting: 依頼者の追加メッセージ
    Recognised --> Waiting: 依頼者の追加メッセージ
    Solved --> Recognised: 公式名義の返答

問い合わせには待機、確認、完了の状態を設ける。依頼者の本人名義による投稿は待機へ、公式名義の返信は確認へ変更する。依頼者は待機と完了を、現在の応対担当者は三つすべてを選べる。完了後でも依頼者が追加の質問を書けば、再び待機として対応対象に戻る。

通知先も役割に応じて決める。店舗への問い合わせでは依頼者の通常メッセージを現在の公式担当者へ、公式返信を依頼者へ知らせる。Musecatチームへの明示的なメンションがあればプラットフォーム担当者にも届く。共通の送受信と復旧を使いながら、閲覧資格、通知先、問い合わせの進捗をそれぞれ管理できる構成である。

関連する記事と資料:通知一覧と端末プッシュ

Musecat
ゲームセンターのすべて