AWS Lambda + SES + Node.js で、Connpass の新着情報を定期的にメールで取得する


※ AWS Lambda と SES の勉強がてら、作ってみました。前記事の続き となります。

IT系勉強会を探すときに、Connpass をよく使っています。

Connpass では、所属するグループが新しい勉強会を登録すると、メールで通知を行ってくれます。

しかし、特定のキーワードや地域名での通知機能は存在しません。このため、自分が所属するグループ外で、面白そうな勉強会が開かれていても、情報を見過ごすことが何度かありました。

そこで、AWSを利用して

  • 自分が興味あるキーワード、地域の勉強会の登録情報を定期的に確認
  • 新しい勉強会が登録されたら、メールで通知する

ようにしてみました。

やること

Connpass で、自分が興味あるキーワードの勉強会が新規登録・更新されたら、メールで通知する。キーワードは「AWS」「東京都」の2つを含むものとする。

仕様

AWS Lambda で、一日一回「AWS」「東京都」に関する勉強会の更新・登録情報を検索する.
新着情報があったら、その勉強会のURLを AWS SES に渡し、自分宛てにメールで送信する.

以下

  • AWS SESの設定
  • IAMロールの設定
  • Lambda 関数の作成
  • コードの記述
  • テスト
  • CloudWatch によるスケジュール設定

の順番で記述します。

AWS SESの設定

SES (Simple Mail service) を利用するにはドメインもしくはメールアドレスを登録してベリファイを行う必要があります。今回はメールアドレスを登録することにしました。

2018年2月時点ではSESはアジア・パシフィック(東京)リージョンでは使えない。このため以下の3リージョンいずれかで設定する。

EU (アイルランド)
米国東部 (バージニア北部)
米国西部 (オレゴン)

今回はバージニア北部(us-east-1)で設定しました。リージョン情報は後で必要となるので控えておきます。

SESのコンソールから登録したいメールアドレスを入力します。

登録したメールアドレス宛に、確認用のメールが届くので、URLをクリックすると、ベリファイが終了。登録したメールアドレスをFromとしてメールの送信が可能となります。

IAMロールの設定

IAMロールの設定を行います。Lambda では 関数を登録する際に、任意の権限を持つロールを付与する必要があります。

Lambda関数の実行のため 「AWSLambdaExecute」、SESの操作のため「AmazonSESFullAccess」の
2つのポリシーをアタッチしたIAMロールを作成する。名前は「Lambda_test」としました。

Lambda 関数の作成

AWSのコンソールからLambda を選び、新規関数を作成。

「一から作成」を選び、名前、ランタイム、ロールを設定する。ランタイムはNode.js 6.10、ロールは先ほど作成したロールを選択します。

コードの記述

Node.js で、Connpass のAPIを検索=>SESでメール送信するまでのコードを登録します。

AWS Lambda でNode.jsのパッケージを利用する場合、予めzipファイルで圧縮して、Lambda 上に転送する必要があるそうです。

Azure Functions で実装したときは、APIとの通信に「request」モジュールを利用しました が、パッケージをまとめてZIPを作成するのが少々手間に思えたため、Node.js の標準機能として利用可能な「https」モジュールを利用する形に書き換えました。

exports.handler = (event, context) => {

  "use strict";

// APIとの通信のためhttpsモジュールを利用

  const https = require('https');

// API検索に利用する変数を定義 

  const DOMAIN = 'connpass.com';
  const PATH = '/api/v1/event/?keyword=aws&keyword=%E6%9D%B1%E4%BA%AC%E9%83%BD';

// AWS SESでメール送信するための各種変数を定義

  const mailTo = ['メールの送信先アドレス'];
  const mailFrom = 'SESで認証したメールアドレス';
  const AWS = require('aws-sdk');
  AWS.config = new AWS.Config();
  AWS.config.region = "us-east-1";
  const ses = new AWS.SES({
    apiVersion: '2010-12-01'
  });

// 通信結果を受け取る変数「response」と、メール文面を作るための変数「list」を定義

  let response = '';
  let list = '';

// 関数「ConnpassSearch」を利用して新着勉強会の情報を取得
// 過去24時間内にアップデートがあった勉強会のURLを変数に格納

  connpassSearch(DOMAIN, PATH).then(function(json) {
    const events = json.events;
    const dt = new Date();
    const yesterday = dt - 86400000;
    list += '「AWS」「東京都」に合致する勉強会の更新がありました。\n';
    for (let i in events) {
      let updated_at = new Date(events[i].updated_at);
      if (updated_at > yesterday) {
        list += events[i].event_url + "\n";
      }
    }

    // AWS SES へ更新・新着勉強会のURLを渡す

      postData(list);

  });


// 関数「ConnpassSearch」を定義
// Connpass のAPIを叩き、取得したデータをPromise で返す

  function connpassSearch(DOMAIN, PATH) {
    const options = {
      host: DOMAIN,
      path: PATH,
      method: 'get',
      headers: {
        'Content-Type': 'application/json',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36',
      }
    };
    return new Promise((resolve, reject) => {
      const req = https.request(options, (res) => {
        res.on('data', (chunk) => {
          response += chunk;
        });
        res.on('end', (res) => {
          res = JSON.parse(response);
          resolve(res);
        });
      })
      req.on('error', (e) => {
        console.error(`problem with request: ${e.message}`);
      });
      req.end();
    });
  }


// AWS SESでメール送信を行う関数「postData」を定義

  function postData(list) {
    ses.sendEmail({
      Source: mailFrom,
      Destination: {
        ToAddresses: mailTo
      },
      Message: {
        Subject: {
          Data: 'Connpassで勉強会の更新がありました。'
        },
        Body: {
          Text: {
            Data: list,
          }
        }
      }
    }, function(err, data) {
      if (err) throw err
      console.log('Email sent:');
      console.log(data);
    });
  }
};



記述したコードを、Lambda のエディタ画面に貼り付けます。

実行時間を設定します。今回のコードは、REST APIの通信結果を待ってから処理を行うため、30秒に設定してみました。

テスト

コードの貼り付けと設定が終わったら、テストを行います。
今回のコードは何かデータを受け取ってから処理を行うわけではなく、コード単体で完結してるため、初期状態で用意されている「Hello World」のテストデータをそのまま使います。

設定が終わったら、テストを実行。正常終了のログが出力され、データがメールで飛んできたらテスト完了です。

CloudWatch によるスケジュール設定

最後に、CloudWatch を設定して、毎日朝8時にコードを起動するようにします。

今回はcron式で記述しました。Lambda のCron式設定は、このドキュメントに書いている ので、書式を確認しながら設定します。日本時間のAM8時は、UTCの23時となります。

定時実行の設定が終わったら終了です。

以後、毎日朝8時に、Connpassに新規登録・もしくは更新があった勉強会のURLがメールで送信されます。

参考情報

以下のドキュメントを参考にしました。

https://dev.classmethod.jp/cloud/aws/lambda-to-ses/
https://qiita.com/ysti/items/9eeca4179aab5ea847b5
https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/ses-examples.html
https://nodejs.org/api/http.html#http_http_request_options_callback
https://qiita.com/n0bisuke/items/33a6a6da1d6f455105f9
https://qiita.com/yoshidasts/items/2d7117ef0f31754a06ab

Lambda 全般については この書籍がとても詳しく、参考になりました。

ちなみに

Movable Type に関する Connpass のエンドポイントは

https://connpass.com/api/v1/event/?keyword=Movable%20Type

となります。(ダイマ)