見出し画像

チャネルトークのスニペットを使ったら管理画面の開発から開放された話

画像7

それは、チャネルトークと出会うずっと前の夏の日、僕は、受信トレイに溜まっていく問い合わせに応じて、is_deletedフラグをただひたすらに折っていた。開発しなければいけないプロダクトはたくさんあった。夕があり、朝があった。

次の夏は、新たに非エンジニアのオペレータと共に迎えた。開発に専念したかった。その期待に応えるように、彼はテキパキと問い合わせに対応していった。そして毎日、僕にこう言った。

「XX番の削除対応お願いします」

その夏、僕はslackの#req-deleteチャンネルに溜まっていく削除依頼に応じて、is_deletedフラグを折っていた。勿論、開発しなければいけないプロダクトはたくさんあった。夕があり、朝があった。

はじめに

チャネルトークさんからお話をいただき、チャネルトークのスニペットに関する記事を書かせていただくことになりました。

いきなり始まった訳のわからない序文でしたが、私はこのような状況が多くの企業で起こっているのではないかと想像しています。

すなわち

・ 顧客管理システムがあり顧客は様々なステータスを持っている
・ 既存の顧客から相談や問い合わせがあると顧客や顧客に紐付いたデータのフラグやステータスを変える 

といった、どこにでもあるようなシステムを抱える中で

【問題】
たかが1つのフラグを折るのに非エンジニアが触れるUIを作りこむ必要があるのか、そんなことを開発している暇があったら適当なコマンドを作るなり、Sequel Pro使うなり、暴力的なまでにSQLを叩くなりしたほうが早いんじゃないかという結論に至り、結局CS対応から完全にエンジニアを剥がせない

という問題が往々にして生じていると想像されるのです。特に認証周りなどアクセスコントロールも意識し始めると、ますます腰が重くなります。そして、果てしなく月日を重ねてステータスの数は膨れ上がり、いよいよ面倒になっていくでしょう。

今回は、この【問題】に対するアプローチとして、チャネルトークのスニペットを使うと良いのではないかという話をさせていただきます。

すなわち、チャネルトークのスニペットを使うと、APIだけ作っておけば管理画面とか作り込む必要なくなってCS対応のエンジニア離れが進むし、オペレータもいちいちエンジニアに頼らなくても業務回せて健康的だし、顧客からみても対応速度上がって満足度上がるし、みんな幸せになるよ、という話です。謎に長文の記事になっているのですが、読むのが面倒な人はここだけでも覚えていってください。

技術的に込み入った話をやり始めると終わらなくなるので、今回は実践というよりは概略、ストーリーをメインにします。でも心配しないでください。ここでは、以下のことが理解できていれば十分です。

・ あるURLにPOSTでアクセスされたときにリクエストのデータを読める
・ あるURLにPOSTでアクセスすると任意のjsonを返せる

スニペットとは何か

公式のドキュメントには以下のように説明されています。

Snippet is customizable interactive component in Channel. Snippet allows you to integrate your app into Channel seamlessly.
Customizable widget that help you to improve when responding to users!

すなわちスニペットとは、自前のシステムとチャネルトークの対応画面を統合するために役立つコンポーネント、ウィジェット、なのです。

チャネルトークのスニペットには、次のようなコンポーネントが用意されています。

【出力系】
- 画像
- テキスト
- キーバリューリスト
- ボタン(ウェブリンク)
- リスト
- スペーサー(見た目の調整)
- 水平線(Divider)
【入力系】
- テキスト入力
- ドロップダウン
- ボタン(サブミット)

より具体的に踏み込むと、チャネルトーク上の顧客IDや電話番号を(チャネルトークの顧客リストとは別に存在する)既存の顧客DB等その他のシステムと上手く紐づけることができたら、既存のシステム上の情報をめちゃんこ簡単に表示することができるのです!

さらに、単に表示するだけではなく、既存のシステム上の値も操作できるため、トーク画面上と既存システムの行き来をする必要がなくなったり、もっと言えば、いちいちステータスやフラグを変更するだけの管理画面を作る必要がなくなります

具体的にどういうことが起きるのか、ここでは不動産会社を例に説明していきます。ちなみに私は不動産関係の会社で一度も働いたことはありません。

実例 - あらすじ

あなたは住宅仲介をする不動産会社に勤めています。あなたの会社では、潜在顧客から問い合わせがあると、名前や電話番号を顧客DBに新規に保存し、営業が個別にヒアリングを行い、顧客顧客管理システム上の希望エリアや予算、家族構成など顧客の属性を更新していきます。

いままではサイト上のWEBフォームをトリガーにしていましたが、この度、時代はチャットというボスの一声で、サイト上にチャネルトークを導入しました。導入にあたり、チャネルトークのフォーム機能を使って、顧客には名前と電話番号を回収することにしました。

画像5

こうして、チャットを使ったCS対応が可能となりましたが、営業の人は、既存の顧客管理システムとチャネルトークのトーク画面を並べて表示して画面を行ったり来たりしているようで、すこし大変そうです。

営業曰く『毎回、電話番号で名寄せしてデータを参照してる。顧客増えてきてマジ卍。一瞬で参照したい

実例 - 顧客データ表示

 スニペットは、トーク画面が開かれるたびに予め指定していたURLにチャネルトークがリクエストを投げて、返されるjsonをウィジェットとして表示してくれる機能です。

ですので、まずはスニペットのjsonを返してくれるURLを指定してあげる必要があるのですが、これは設定画面の【連携の管理】→【スニペット設定】より可能です。思う存分、任意のAPI URLを指定してあげましょう。

画像1

 そうすると、チャネルトークは応対者がトーク画面を開くたびにスニペットごとの一意のtokenをクエリパラメータに乗っけたAPI URLにデータをPOSTしてくれます。

システム統合にあたって、特に重要な情報はchannel内にあるuserに入ってあります。今回は、さらにその中にあるprofileのmobileNumberをキーに引っ掛けます。

// ↓ postdata.channel.user
{
//...省略...
  "user":{
     "id":"5exxxxxxxxxxxxxx",
     "channelId":"00000",
     "memberId":"5dxxxxxxxxxxxxxx",
     "veilId":"5dxxxxxxxxxxxxxx",
     "name":"SEKIGUCHI",
     "profile":{
        "name":"SEKIGUCHI",
        "referrer":"https://xxxxxxxxx/",
        "mobileNumber":"+819012345678"
     },
     "tags":[/* ... */],
     "alert":0,
     "unread":0,
     "blocked":false,
     "unsubscribed":false,
     "hasChat":true,
     "hasPushToken":false,
     "language":"ja",
     "country":"JP",
     "city":"Tokyo",
     "latitude":35.6558,
     "longitude":139.7013,
     "web":{ /* ... */},
     "sessionsCount":3,
     "lastSeenAt":1593149582282,
     "createdAt":1593146851834,
     "updatedAt":1593149582283,
     "version":14,
     "member":false,
     "avatarUrl":"https://cf.channel.io/avatar/emoji/tiger.d5dcf9.png",
     "mobileNumber":"+819012345678",
     "contact":true,
     "systemLanguage":"ja"
  }
//...省略....
}

既存のシステムは+81を除いて0を加えた形の電話番号をデータとして持っていたので、電話番号の正規化を行った後に、電話番号に紐づけてデータを取得してあげました。

続いて、レスポンスのJSONを考えるにあたり、スニペットのビルダーを駆使ししてlayout以下のjsonのテンプレートを作りました。

const json = {
 "snippet": {
   "version": "v0",
     "layout":[
     {
   "id": "title",
   "type": "text",
   "text": "顧客管理システム上の情報",
   "style": "h1"
 },
 {
   "id": "order-property", 
   "type": "key-value",
   "items": [
     {
       "key": "名前", 
       "value": customer.name
     },
     {
       "key": "電話",
       "value": customer.phone
     },
     {
       "key": "希望エリア",
       "value": customer.area
     },
     {
       "key":"予算",
       "value" customer.budget
     }
      ]
   }
  ]
  }
}

そして、既存のシステムとつなぎ込んでJSONを返してあげます。今回はtypescirptで実装しています。

//[スニペットのAPI側の処理]

//customerを既存APIから取得
API.getCutomerByPhone(
    trim81add0(postdata.channel.user.mobileNumber)
).then( customer => {
    const json = {
    /* ... 上記例のjson ... */
    };
    res.writeHead(200,{'content-type':'application/json'});
    res.write(JSON.stringify(json));
});

すると、トーク画面の右側にこんなにイケてるウィジェットが表示され、無事、既存のシステムとの統合が行えました。※実際は別途tokenが正しいか等で改ざんされたリクエストではないかの検証を必ず行うべきです

画像2

(あ、これはスニペット名が表示されるからヘッダのテキストあえて書かなくてもよかったやつや……)

ここまでのお話

スニペット使うだけで、既存のシステムの情報を簡単にトーク画面に出せた。(電話番号という共通データをキーにして)

実例 - リンクを貼るだけのお手軽統合

問い合わせに対して迅速に既知の情報を参照しながら対応できるようになったことにより、営業の業務効率は改善されました。結果、営業成績は増加し、営業の給与は上がっていくのに、あなたの給与は据え置かれています。

業務改善を正しく評価してくれる会社への転職しようという意思を改めて固めていると、営業は次のようなことを言いはじめます。

「常識的に考えて、チャットをしていく中で、お客様の希望が変わるじゃん。結局そのとき、既存のシステムでいちいち名前から参照していくの面倒なんだけど」

そこで、あなたは次のように考えます。既存システムの情報を引っ張ってこれてるので、編集画面のURL作れるぞ。幸い、編集画面は次の簡単なURLで表現できる。

https://xxxxx.com/admin/edit/[customerId]

あとは、スニペットに編集画面へのリンクを貼るだけです。

チャネルトークのスニペットのコンポーネントには、ボタンがあり、ボタンにはsubmitとurlの2種類のアクションがあります。urlを利用すると、任意のURLへのリンクボタンを作成できます。

そこで、既存のスニペットjsonのlayoutに次のようなボタンを追加しました。

  {
   "id": "link-to-edit",
   "type": "button",
   "label": "編集画面へ",
   "action": {
     "type":"url",
     "url": `https:/xxxxxxx.com/admin/edit/${customer.id}`
   }
 }

たったこれだけで、トーク画面上に編集画面へのリンクが現れました。そしてまたひとつ、営業の給与向上に寄与できました。

画像3

ここまでのお話

スニペット使うだけで、既存のシステムの情報を簡単にトーク画面に出せたし、既存のシステムの編集画面へのリンクも簡単に出せた。

実例 - トーク画面上での更新

参照はトーク画面に統合され、更新もワンクリックで画面遷移できるようになったため、一旦は営業さんも大満足。しかし、さらなる利便性を求めた営業は、あなたにこう言ってのけます。

「画面遷移すら嫌だ」

あなたは震える拳を抑えながら、スニペットでの解決を志します。営業が言うには、よく変更がある、希望エリアと予算だけで一旦は大丈夫とのことです。

チャネルトークのスニペットにはドロップダウンがありましたし、希望エリアと予算の取りうる値は既に定義済みのため、今回はドロップダウンを利用することにしました。

まずはドロップダウンと更新を確定させるコンポーネントを配置します。

定義済みの値やラベルを駆使して、次のようなjsonを追加します。(勿論、dropdownのvalueは動的に、各itemのid、labelは定義済みの値リストをもとにそれぞれ生成され、結果として次のようなjsonが出力されました)

"layout": [
// .....
  {
     "id":"edit-header",
     "type":"text",
     "text":"データを更新する",
     "style":"h1"
  },
  {
     "id":"edit-header",
     "type":"text",
     "text":"希望エリア",
     "style":"h2"
  },
  {
     "type":"dropdown",
     "id":"area-dropdown",
     "label":"エリアを選択",
     "value":"area-dropdown-jonan",
     "items":[
        {
           "id":"area-dropdown-jonan",
           "label":"都内・城南"
        },
        {
           "id":"area-dropdown-johoku",
           "label":"都内・城北"
        }
     ]
  },
  {
     "id":"edit-header",
     "type":"text",
     "text":"予算",
     "style":"h2"
  },
  {
     "type":"dropdown",
     "id":"budget-dropdown",
     "label":"予算を選択",
     "value":"budget-dropdown-5000",
     "items":[
        {
           "id":"budget-dropdown-4000",
           "label":"4000万円台"
        },
        {
           "id":"budget-dropdown-5000",
           "label":"5000万円台"
        }
     ]
  },
  {
     "id":"submit-edit",
     "type":"button",
     "label":"更新",
     "style":"secondary",
     "action":{
        "type":"submit"
     }
  }
]
//.....

こうしてスニペットは下のようになりました。

画像5

最後に、更新ボタンを押した際の挙動を確認します。

typeがsubmitのボタンを押すと、最初に設定したAPI URLに同じようにPOSTリクエストが送られます。POSTされるデータには、トークを開いたときに送られるデータに加えて、新たにcomponentIdとsubmitというデータが含まれています。

今回の場合は、この通り。

  {
  // ....
   "componentId":"submit-edit",
   "submit":{
     "area-dropdown":"area-dropdown-johoku",
     "budget-dropdown":"budget-dropdown-4000"
  }

そこで、POSTされたデータの中にcomponentIdが含まれるかどうかをチェックすると同時に、含まれていた場合には(そしてsubmit-editであった場合には)area-dropdownおよびbudget-dropdownの値をそれぞれ既存のシステムの更新APIに渡すという処理を書けば、すべての作業は完了です。

//[スニペットのAPI側の処理]

// ... 省略 ...

//customerを既存APIから取得
   if(postdata.componentId !== undefined && postdata.componentId === "submit-edit") {
     API.editCustomer(customer.id, {
        "area" : postdata.submit['area-dropdown'],
        "budget" : postdata.submit['budget-dropdown']
     }).then(res => {
        //更新が終われば、再度顧客データを読んでスニペットを生成する\
        //(更新後のデータでスニペットを作り直す)
     });
   }

submitされると該当スニペットのみがレスポンスに応じて再描画されるため、最新の顧客情報を取得してスニペットのjsonを生成して返してあげれば、画面遷移なしに最新の情報が表示されます。

ここまでのお話

スニペット使うだけで、既存のシステムの情報を簡単にトーク画面に出せたし、既存のシステムの編集画面へのリンクも簡単に出せたし、そもそも編集画面に飛ばさなくても更新APIがあればトーク画面上で既存システムのデータを更新できるようになった。

実例 - その後

ある日、自宅でリモートワークをしていると、営業がslackでこんなことを言いました。「これからは、リモートワークかどうかも住宅選びの大きなポイントになる。すぐにでも属性に追加してほしい」

もう以前のように管理画面や更新画面の心配することはありません。既存システムのDBのスキーマを変更して、更新APIをすこしだけ修正してあげます。あとは、チャネルトークにスニペットを追加するだけです。

フラグのようにトグルする値の場合は、ドロップダウンでも良いですが、ステータスに応じてトグルするボタンでも簡単ですよ。その場合は、押したボタンのcomponentIdに応じて処理を分岐させていくと良いでしょう。

さいごに

以上、非常に長くなりましたが、以上、チャネルトークでスニペットを使うとめちゃくちゃ便利だよ、という物語でした。

ただ顧客のフラグを折りたかっただけなのに、複雑なアクセスコントロールを有し、イケてる更新画面を作る必要があるのか……と落ち込んでいるエンジニアの皆様、ぜひ、チャネルトークのスニペットで完結させましょう。

エンジニアも幸せ、CS対応も幸せ、顧客も幸せというトリプルWINな仕組みが出来上がります。

なお、自分は普段はもっぱらPHPを触るため、記事中のtypescript(js)のコードの正確性は保証されていません。申し訳ありません。

余談

今回は既存のシステムが存在する前提で話を進めましたが、全くの新規の場合は、そもそもチャネルトークの顧客リストをそのまま使ってしまうというのも方法の一つとしてあると思います。もちろんチャネルトークが用意している画面で編集するのも良いですが、ポチポチ操作したいという場合は、やっぱりスニペットで、アクションごとにチャネルトークのuser/upsertを叩いてあげる等の方法が考えられます。

その上で、チャネルトーク上の顧客情報が追加(変更)されたらというwebhookがあればさらに既存のアプリケーションとの統合がより簡単になる気がしますので、チャネルトークさんにおいては、ご検討のほどよろしくお願い申し上げます。

最後に、チャネルトークはもちろん、LINEや既存のシステムを活かしながらいかに業務改善を行うかということを最近はもっぱら考えています。お悩みがある方はぜひ一度、お気軽にご相談ください。

こうして顧客DBと管理画面と、その万象とが完成した。エンジニアは第七日にその作業を終えられた。すなわち、そのすべての作業を終って第七日に休まれた。

この記事が気に入ったらサポートをしてみませんか?