AWS
twilio
SNS
lambda

SNS + Lambda + Twilio で音声電話をかける v2

More than 3 years have passed since last update.

アラート発生時に AWS Lambda を使って音声電話をかける では単に電話をかけるだけでしたが、

  • 複数人に同時に電話をかけたい
    • 1人だと、その人が電話に出なかったら終了なので
  • 夜から朝方までの時間帯で電話を鳴らしたい
    • 日中はアラートメールで十分

という変更をしたくなってきたので、いろいろ改変。

npm パッケージを使うことになったので、JSベタ貼りではなくきちんとzipでアップロードすることになります。
となると、いろいろ面倒になってくるので、gulp 対応、CoffeeScript 化もついでに行います。

コード

package.json

使う npm パッケージは以下の通り。

  • moment-timezone: 有名な日時ライブラリの timezone 対応版。UTC-JST 変換、時刻判定に。
  • q: 有名な Deferred-Promise ライブラリ。
  • twilio: せっかく npm 使うので、直接 REST API を叩くのではなく、サクッとライブラリで。

以下、dev 用。

  • coffee-script: gulpfile を coffee で書くために。
  • del: clean task でのファイル削除用。
  • gulp: gulp 本体。
  • gulp-coffee: coffee compile 用。
  • gulp-zip: zip 圧縮用。
  • run-sequence: gulp task の順序制御用。
package.json
{
  "name": "lambda-twilio-alert",
  "version": "1.0.0",
  "description": "",
  "author": "",
  "license": "ISC",
  "dependencies": {
    "moment-timezone": "^0.4.0",
    "q": "^1.4.1",
    "twilio": "^2.2.1"
  },
  "devDependencies": {
    "coffee-script": "^1.9.3",
    "del": "^1.2.0",
    "gulp": "^3.9.0",
    "gulp-coffee": "^2.3.1",
    "gulp-zip": "^3.0.2",
    "run-sequence": "^1.1.1"
  }
}

gulpfile

gulpfile も適当にサクッと作成。

gulpfile.coffee
gulp = require 'gulp'
coffee = require 'gulp-coffee'
zip = require 'gulp-zip'
del = require 'del'
runSequence = require 'run-sequence'

gulp.task 'clean', (cb) ->
  del 'build', cb

gulp.task 'coffee', ->
  gulp.src 'src/*.coffee'
  .pipe coffee bare: true
  .pipe gulp.dest 'build'

gulp.task 'copy', ->
  gulp.src 'node_modules/@(moment-timezone|q|twilio)/**'
  .pipe gulp.dest 'build/node_modules'

gulp.task 'zip', ['coffee', 'copy'], ->
  gulp.src 'build/**'
  .pipe zip 'lambda.zip'
  .pipe gulp.dest 'dist'

gulp.task 'build', ->
  runSequence 'clean', 'zip'

lambda 用コード本体

本体はこんな感じで。
toNumbers には、配列で電話番号を列挙します。

TwiML については S3 Bucket 等の HTTP アクセス可能な場所に設置してもいいのですが、Twilio Labs に Twimlets なるものがあり、これの Echo を使うと QueryString で渡したデータをそのまま XML としてエコーバックしてくれるので、こちらを使っています。
Twimlets を使うことで、任意のメッセージを動的に喋らせることも簡単になりますね。

また、moment-timezone で Asia/Tokyo に変換、10時-19時の間はスキップするようにしています。

twilio-node は Q の Promise を返す仕組みを持っていますので、複数の makeCall の結果を Promise オブジェクトの配列で持ち、Q.allSettled で処理しています。

src/index.coffee
accountSid = 'YOUR_TWILIO_ACCOUNT_SID'
authToken = 'YOUR_TWILIO_AUTH_TOKEN'
fromNumber = '+81XXXXXXXXXX'
toNumbers = [
  '+81XXXXXXXXXX'
  '+81XXXXXXXXXX'
  '+81XXXXXXXXXX'
  '+81XXXXXXXXXX'
  '+81XXXXXXXXXX'
]
twiML = '''
<Response>
<Say language="ja-JP" voice="alice">サービス名 アラート通知です。</Say>
<Pause length="1" />
<Say language="ja-JP" voice="alice">障害が発生した可能性がありますので、アラートメール及びシステムの状態を確認して下さい。</Say>
</Response>
'''

https = require 'https'
queryString = require 'querystring'
moment = require 'moment-timezone'
Q = require 'q'
twilio = require 'twilio'
client = new twilio.RestClient accountSid, authToken

exports.handler = (event, context) ->
  try
    # Skip CloudWatch recovery notification
    if event.Records[0].Sns.Subject.match /^OK: /
      context.done null, 'Skip notification.'
      return
  catch e
    context.done null, 'Invalid event data.'
    return

  d = moment().tz('Asia/Tokyo')
  day = d.day()
  hour = d.hour()
  if day >= 1 and day <= 5 and hour >= 10 and hour <= 18
    # 平日(月〜金)の10時〜19時は通知を抑制する
    context.done null, 'Skip notification.'
    return

  console.log 'Start twilio call'
  promises = []
  for to in toNumbers
    promises.push client.makeCall
      from: fromNumber
      to: to
      url: "http://twimlets.com/echo?Twiml=#{queryString.escape twiML}"

  Q.allSettled(promises).then ->
    args = [].slice.apply arguments
    for arg in args
      if arg.state is 'rejected'
        context.done null, "Call failed! Reason: #{arg.reason}"
        return
    context.done null, 'Call succeeded.'
  return

ビルド、更新

gulp build すると、dist 以下に lambda.zip が生成されます。

あとは、AWS CLI を使って update を行います。

aws --region ap-northeast-1 lambda update-function-code --function-name YOUR_FUNCTION_NAME --zip-file fileb://dist/lambda.zip

テスト実行

AWS CLI の invoke でテストができます。Management Console から実行しても OK です。

aws --region ap-northeast-1 lambda invoke --function-name YOUR_FUNCTION_NAME lambda.log

追加予定のアイデア

  • SNS のメッセージ内容(重要度)によって電話のかけ先を変える
  • TwiML のメッセージ内容を具体的な障害内容にする
  • 休日カレンダーを考慮した業務時間の判定
    • このためだけに Google API 対応するのはちょっと面倒。。

  • 6/26 修正

    • 土日はアラートメールを見過ごす可能性が高いので、時間に関係なく電話をかけるように修正
  • 7/1 修正

    • AWS Lambda が東京リージョンで利用可能になったため、コマンドの region を修正
    • CloudWatch の recovery 通知は無視するよう修正