LoginSignup
7

More than 5 years have passed since last update.

所定のGoogle Driveフォルダにhtmlコンテンツを置いたら半自動でメルマガ配信

Last updated at Posted at 2015-07-14

メルマガ配信の仕組みを刷新した話
今迄はyamlファイルに必要な情報を記入し、それを元にAWS SQSに送信リストを作り定刻になったらAWS SESで送るって仕組みであった。
テスト送信と本送信のAWS SQSのキューが同じだったので誤送信をしたり、定刻に送信するのをhubot上でsetTimeoutを使って送るというやっつけ仕様であったためheroku dynoが再起動すると送れなかったり、休日もその日に送信時刻を仕込まないと駄目だったりと色々と難があった。

そこで、heroku上にpostgresqlで予約テーブルを作りGoogle Drive上の所定フォルダにhtmlコンテンツを置くとテスト送信まで自動で実施->確認を繰り返し、OKとなったらsubjectと送信日時を入れて予約をする仕組みをslack上で完結できるようにした。

以下は主にhubotで動かしているcoffeesriptサンプル

先ずheroku上のpostgresqlに予約テーブルを準備

  • sequelizeを使って以下の様なものを準備
    htmlコンテンツのファイル名 送信日時 subject 状態 などなど
  • stateには initialize review verified preparing available completed などを持たせて状態管理が出来るようにしておいた
    本送信が出来るのは available の状態のときのもののとかにする
    本当はmachinaを使ってみたかった
Sequelize    = require('sequelize')
db = new Sequelize(process.env.DATABASE_URL)
EmagazineSchedule = db.define('EmagazineSchedule', {
  send_at:    {type: Sequelize.DATE},
  file_id:    {type: Sequelize.STRING, allowNull: false},
  filename:   {type: Sequelize.STRING, allowNull: false, unique: true},
  subject:    {type: Sequelize.TEXT},
  state:      {type: Sequelize.STRING,  defaultValue: 'initialize'},
}, {
  paranoid:       true,
  underscored:    true,
  underscoredAll: true,
  deletedAt:      'deleted_at',
  tableName:      'emagazine_schedules',
})

Google Driveの所定フォルダにhtmlファイルを置いたら予約テーブルにレコードが追加されるようにする

  • 所定のフォルダに変更がかかった場合、hubotに通知が来るようにする
    Google Driveの設定はこちらを参照
  • Googleからの通知が受けれるようにhubot上にendpointを作る
    https://***.herokuapp.com/google/drive/notifications/emagazine で受ける
Googleapis   = require('googleapis')
drive        = Googleapis.drive({version: 'v2'})
SERVICE_ACCOUNT_EMAIL = 'xxx@developer.gserviceaccount.com'
SERVICE_ACCOUNT_KEY   = process.env.EMAGAZINE_PEM
SCOPE = [
  "https://www.googleapis.com/auth/drive",
  "https://www.googleapis.com/auth/drive.appdata",
  "https://www.googleapis.com/auth/drive.apps.readonly",
  "https://www.googleapis.com/auth/drive.file",
  "https://www.googleapis.com/auth/drive.metadata.readonly",
  "https://www.googleapis.com/auth/drive.readonly"
]
jwt = new Googleapis.auth.JWT(SERVICE_ACCOUNT_EMAIL, null, SERVICE_ACCOUNT_KEY, SCOPE)
jwt.authorize (err, tokens) ->
  if err
    console.log err
  else
    jwt.credentials = tokens

module.exports = (robot) ->
  robot.router.post "/google/drive/notifications/emagazine", (req, res) ->
    drive.changes.get {auth: jwt, changeId: data.body.id}, (err, resp) ->
      if (not resp.file.labels.trashed and resp.file.mimeType == 'text/html')
        filename = resp.file.title
        db.transaction().then (t) ->
          EmagazineSchedule.findOrCreate({where: {file_id: resp.file.id, filename: filename}, paranoid: false}, {transaction: t, lock: t.LOCK.UPDATE}).spread (schedule, created) ->
            # ここにテスト用の送信リスト作成と送信を実施するコードを入れる
            ...
          .catch (err) ->
            t.rollback()

送信リスト(AWS SQSにemailアドレスリストを作る)と送信はコマンドを準備

  • これは送信サーバ(バッチサーバ)にコマンドを準備した
  • hubotからはsshでこれらのコマンドを呼び出してSTDOUTとSTDERRを受け取って処理を実施するのみ
  • バッチサーバからDrive上のコンテンツを取得するのはgoogle-drive-ocamlfuseを使ってmountして使用
    metadata_cache_time=60 で設定しているのでテストで再送信する場合は60秒の待ちを作ったりする必要あり

テスト送信

  • 送信リストの作成と送信を実施
  • テスト送信用のアドレスリストを予め準備しておいた
    これもhubotにendpointを作り、どこからでもhttp getで取得出来るようにしておいた

本送信用のリスト作成と本送信

  • agendaを使って定期的に予約テーブルを確認して該当するものがあれば実行するようにした
  • 送信時刻が判明しているのでしっかりとスケジューリングが出来るのでcronの様に毎分確認とする仕様でなくても良いが、TODOとしている
  • EventEmitterでタスクを呼び出すようにしている
  • 以下は送信リストを作る箇所のサンプル

agendaで定期的にイベントを呼び出す処理

Agenda       = require('agenda')
agenda       = new Agenda({db: { address: process.env.MONGOLAB_URI}})
module.exports = (robot) ->
  agenda.define 'agenda:emagazine:preparing', (job, done) ->
    robot.logger.info '@agenda:emagazine:preparing'
    robot.emit 'emagazine:preparing', {execute: true}
    done()

  agenda.every('5 minutes', 'agenda:emagazine:preparing')
  agenda.start()

実際に送信リストを作る処理

  • トランザクション分離レベルは状況に合わせて変更
module.exports = (robot) ->
  robot.on 'emagazine:preparing', (params) ->
    db.transaction({isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE}).then (t) ->
      ...

slackからの操作

以下の処理が行えるようにした

  • 予約テーブル一覧表示
  • テスト再送信
  • 送信日時/subjectの設定
  • 承認
  • 予約の取り消し
module.exports = (robot) ->
  robot.respond /\bschedule\s+emagazine\s+(show|resend|update|verify|destroy|...)\s+(.+)$/, (msg) ->
    ...

現在テスト運用中で運用上改修したことなどは今後追記予定

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7