ITエンジニアによるITエンジニアのためのブログ

TwilioとRailsによる自動架電

ビジネスにおいて、本人確認、予約リマインダーなど、定型的なアナウンスを電話で行う場面は少なくありません。これらの業務をシステムで自動化することで、大幅なコスト削減が可能です。

この記事では、TwilioとRuby on Railsを用いて自動架電システムを構築する方法を解説します。

目次

  1. アーキテクチャ
  2. 事前設定
  3. Railsアプリケーションでの自動架電ジョブのスケジュール
  4. 自動架電ジョブの実行とTwilio FunctionへのAPIリクエスト
  5. Twilio Functionによるユーザーへの架電
  6. 架電結果の処理と再架電
  7. まとめ

アーキテクチャ

システム全体のアーキテクチャは以下の通りです。

  1. Railsアプリケーションで自動架電ジョブをスケジュールします。
  2. 指定時刻に自動架電ジョブが実行され、Twilio FunctionへAPIリクエストを送信します。
  3. Twilio Functionがユーザーへ架電します。
  4. Twilioから架電ステータスに関するウェブフックがRailsアプリケーションへ送信されます。
  5. ユーザーが応答しない場合は再架電します。

図は以下の通りです。

事前設定

事前設定は以下が必要となります。

  • Twilioアカウントの作成
  • Twilio電話番号の取得
  • twilio-ruby gemのインストール(Gemfileにgem 'twilio-ruby'を追記し、bundle installを実行)
# Gemfile
gem 'twilio-ruby'
bundle install

Railsアプリケーションでの自動架電ジョブのスケジュール

RailsのActiveJobを使用して、架電ジョブをスケジュールします。以下の例では、病院予約システムで予約時刻の10分前に患者へ電話し、診察の順番が来たことを通知します。

# Railsのコントローラーなどでジョブをスケジュール
AutoCallJob.set(wait_until: reservation.start_at - 10.minutes).perform_later(reservation.id)

自動架電ジョブの実行とTwilio FunctionへのAPIリクエスト

# app/jobs/auto_call_job.rb
class AutoCallJob < ApplicationJob
  queue_as :default

  def perform(reservation_id)
    reservation = Reservation.find_by reservation_id
    return if reservation.nil?

    account_sid = ENV['TWILIO_ACCOUNT_SID']
    auth_token = ENV['TWILIO_AUTH_TOKEN']
    twilio_client = Twilio::REST::Client.new(account_sid, auth_token)

    twilio_client.calls.create(
      url: "#{ENV['TWILIO_RESERVATION_CALL_FUNCTION_URL']}?&room_id=#{ERB::Util.url_encode(reservation.room_id)}",
      to: "+81#{reservation.phone_number.delete_prefix('0')}",
      from: '+81312345678',
      status_callback: "#{ENV['TWILIO_CALL_STATUS_CALLBACK_URL']}/reservations/#{reservation.id}/reschedule_call",
      status_callback_method: 'POST'
    )
  end
end

ジョブでは以下の処理を行います。

  • Twilio FunctionのURLを指定(Twilio Functionについては後述)。
  • 架電先の電話番号を設定。
  • 架電元の電話番号を設定(Twilioで取得した番号を使用)。
  • ステータスコールバックURLとHTTPメソッドを指定。これにより、架電ステータスをRailsアプリケーションで受け取り、ステータスに応じた処理が可能になります。

Twilio Functionによるユーザーへの架電

Twilio Functionは、Twilioのサーバーレス環境で実行されるNode.jsの関数です。TwiML(Twilio Markup Language)というXMLベースのマークアップ言語を用いて架電内容を記述します。

XMLベースと言ってもNode.jsライブラリーで記述可能です。

# Node.jsでのTwiML例
const VoiceResponse = require('twilio').twiml.VoiceResponse;

const response = new VoiceResponse();
response.say('Hello!');

console.log(response.toString());
# XMLの出力
<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Say>Hello!</Say>
</Response>

Twilio FunctionはTwilio管理画面の「Functions and Assets」から作成できます。

以下の例では、ActiveJob実行時に渡された部屋番号(room_id)を、Twilio Function内で取得し、アナウンスに含めています。

exports.handler = async function(context, event, callback) {
  let twiml = new Twilio.twiml.VoiceResponse();

  twiml.pause({length: 3});

  const say = twiml.say({
    voice: 'Polly.Mizuki'
  }, `診療予約のお知らせです。${event.room_id}番受付窓口までお越しください。`);

  return callback(null, twiml);
};

このFunctionが実行されると、Twilioがユーザーの電話に架電し、指定された音声を再生します。

架電結果の処理と再架電

Twilio Functionの実行後、ユーザーの応答に関わらず、ActiveJobで指定したコールバックURL(status_callback)へ架電ステータスが送信されます。これにより、ユーザーが電話に出なかった場合に再架電するなどの処理が可能です。

# config/routes.rb
post 'reservations/:id/reschedule_call', to: 'reservations#reschedule_call'

# app/controllers/reservations_controller.rb
class ReservationsController < ActionController::API
  def reschedule_call
    validator = Twilio::Security::RequestValidator.new(ENV['TWILIO_AUTH_TOKEN'])

    return unless validator.validate(
      request.original_url,
      URI.decode_www_form(request.raw_post).to_h,
      request.headers['X-Twilio-Signature']
    )

    if %w[busy failed no-answer].include?(params['CallStatus'])
      AutoCallJob.set(wait_until: Time.now + 10.minutes).perform_later(params[:id])
    end
  end
end

*ActionController::Baseを継承すると、CSRF対策によりActionController::InvalidAuthenticityTokenエラーが発生するため、ActionController::APIを継承してください。また、AuthenticityTokenの代わりに、Twilio::Security::RequestValidatorを用いてリクエストの正当性を検証します。

まとめ

この記事では、TwilioとRailsを使って自動架電システムを構築する方法を解説しました。定型的な電話業務を手動で行っている場合は、自動化による時間とコストの削減を検討してみてはいかがでしょうか。