[Twilio+Slack] Slack に SMS 送信コマンドを実装する: SQS+Lambda での非同期処理構成 [2/3]

[Twilio+Slack] Slack に SMS 送信コマンドを実装する: SQS+Lambda での非同期処理構成 [2/3]

Twilio API を使って、SMS を送信できるコマンド /sms を Slack に実装する方法を紹介します。署名検証によるセキュリティ対策を Lambda で実装し、Slack アプリにおける 3 秒のタイムアウト制限を回避するため SQS で非同期的に処理します。第 2 回では、Slack からのリクエストを受け取り、Twilio を使って SMS を送信する処理を非同期で実行するためのバックエンド構成を実装します。

対象読者

  • Slack のスラッシュコマンドを使って外部サービスと連携したい方
  • Twilio を使って SMS を送信したい方
  • AWS Lambda を使ったサーバーレス構成に興味がある方
  • Slack の 3 秒タイムアウト制限を回避する方法を知りたい方
  • SQS を使った非同期処理の実装例を探している方

参考記事

開発環境

バージョン

node: 22.13.1
npm: 10.9.2
node_modules/twilio: 5.5.1 
node_modules/aws-sdk: 2.1692.0

概要

本記事では、Slack のスラッシュコマンド /sms を使って、Twilio 経由で SMS を送信する仕組みを構築する方法を紹介します。ユーザーは Slack 上で以下のようにコマンドを入力するだけで、指定した電話番号に SMS を送信できます。第 2 回では、Slack からのリクエストを受け取り、Twilio を使って SMS を送信する処理を非同期で実行するためのバックエンド構成を実装します。具体的には、Amazon SQS の作成、2 つの AWS Lambda 関数 (リクエスト受信・SMS 送信) の実装、API Gateway の設定を行います。

本記事では、全体構成図のうち、Slack からのリクエストを受け取り、Twilio で SMS を送信するまでのバックエンド処理 (赤枠部分) を実装します。

39

SQS の作成

Slack からのリクエストを即時に受け取り、Twilio での SMS 送信処理を非同期で行うために、Amazon SQS (Simple Queue Service) を使用します。このセクションでは、非同期処理のための SQS キューを作成する手順を説明します。

SQS キューの作成

Amazon SQS コンソール にアクセスし、左メニューの キュー を選択、キューを作成 をクリックします。

21

以下のように設定します。

項目 設定値
キュータイプ 標準
キュー名 slack-sms-queue (任意)

他の項目はデフォルトのままで構いません。 キューの作成 をクリックして完了します。

22

キューの URL を控える

作成したキューの詳細画面に表示される URL をコピーしておきます。この URL は、Slack からのリクエストを受け取る Lambda 関数からメッセージを送信する際に使用します。

23

SQS からのメッセージを受け取り SMS を送信する Lambda 関数の作成

SQS にメッセージが送信されたら、それを受け取って Twilio API を使って SMS を送信する Lambda 関数を作成します。

ローカルでプロジェクトを作成する

ローカル環境で Lambda 関数のコードを作成します。ローカルで作成する理由は、Twilio や AWS SDK などの外部ライブラリを使用するためです。Lambda コンソールのインラインエディタではこれらのライブラリをインストールできないため、ローカルでパッケージをまとめて zip し、アップロードする必要があります。

はじめに、必要なパッケージをインストールします。

mkdir slack-sms-sender
cd slack-sms-sender
npm init -y
npm install twilio

Lambda 関数の実装

次に、index.js を作成し、SMS 送信機能を実装します。以下のコードでは、SQS から受け取ったメッセージをもとに Twilio API を使って SMS を送信し、その結果を Slack に通知する処理を実装しています。

index.js

const twilio = require('twilio');

const client = twilio(
  process.env.TWILIO_ACCOUNT_SID,
  process.env.TWILIO_AUTH_TOKEN
);

const https = require('https');

exports.handler = async (event) => {
  for (const record of event.Records) {
    const body = JSON.parse(record.body);
    const { to, message, response_url } = body;

    try {
      await client.messages.create({
        body: message,
        from: process.env.TWILIO_PHONE_NUMBER,
        to: to
      });

      await postToSlack(response_url, `✅ SMS を ${to} に送信しました。`);
    } catch (err) {
      console.error('Twilio error:', err);
      await postToSlack(response_url, `❌ SMS の送信に失敗しました。\nエラー: ${err.message}`);
    }
  }
};

// Slack の response_url にメッセージを POST
function postToSlack(responseUrl, message) {
  return new Promise((resolve, reject) => {
    const data = JSON.stringify({ text: message });

    const url = new URL(responseUrl);
    const options = {
      hostname: url.hostname,
      path: url.pathname + url.search,
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Content-Length': Buffer.byteLength(data)
      }
    };

    const req = https.request(options, (res) => {
      console.log(`Slack response status: ${res.statusCode}`);
      resolve();
    });

    req.on('error', (e) => {
      console.error(`Slack response error: ${e.message}`);
      reject(e);
    });

    req.write(data);
    req.end();
  });
}

Lambda にデプロイする

Lambda にアップロードするため、プロジェクトを zip にまとめます。

zip -r function.zip .

AWS Lambda コンソール にアクセスし、左メニュー 関数 を選択し 関数を作成 をクリックします。
4

一から作成 を選び、ランタイムに Node.js 22.x を指定します。名前は任意です (今回は slack-sms-sender とします) 。
24

コード タブを開き アップロード元 > .zip ファイル をクリックします。先ほど作成した function.zip をアップロードします。
6

環境変数の設定

コード タブ EXPLORER 内の ENVIRONMENT VARIABLES を開き、 Add environment variables ボタンをクリックして下記の環境変数を設定します。

キー
TWILIO_ACCOUNT_SID Twilio のアカウント SID
TWILIO_AUTH_TOKEN Twilio の認証トークン
TWILIO_PHONE_NUMBER Twilio の送信元電話番号

28

タイムアウト設定

Twilio の SMS API には 3 秒以上かかる場合があるため、タイムアウト設定を 10 秒以上に設定しておくと確実です。

補足: Lambda コンソールでテストを行う

Lambda 関数が正しく動作するかを確認するため、テストイベントを作成して動作確認を行うことができます。Lambda 関数の画面で テスト タブを開き、 テストイベントの作成 を選択します。以下のような JSON を入力して、SQS からのリクエストを模擬します。

{
  "Records": [
    {
      "body": "{\"to\":\"+819012345678\",\"message\":\"テストメッセージ\",\"response_url\":\"https://hooks.slack.com/commands/xxx\"}"
    }
  ]
}

SQS 関連ポリシーをロールにアタッチする

設定 タブを開き アクセス権限 セクションのロール名をクリックし、ロールの設定ページにジャンプします。
25

許可 タブの 許可ポリシー許可を追加 から ポリシーをアタッチ をクリックします。
26

AWSLambdaSQSQueueExecutionRole を追加します。
27

Lambda のページに戻ります。

SQS からのアクセスを許可する

設定 タブの アクセス権限 から リソースベースのポリシーステートメント に以下のアクセス権限を追加します。

項目 設定値
ステートメント ID AllowSQSInvoke
プリンシパル sqs.amazonaws.com
アクション lambda:InvokeFunction

34

SQS の Lambda トリガーにアタッチする

SQS の設定画面に戻り、 Lambda トリガー タブから作成したトリガーを設定します。

33

Slack リクエストを受け取り SQS に送信する Lambda 関数の作成

Slack のスラッシュコマンドから送信されるリクエストを受け取り、Amazon SQS にメッセージを送信する処理を AWS Lambda 上に実装します。この Lambda 関数は、API Gateway を通じて HTTP リクエストを受け取る構成とし、Twilio への SMS 送信は別の Lambda 関数で非同期に処理します。

Slack のスラッシュコマンドには 3 秒以内にレスポンスを返す必要があるため、この Lambda 関数では Twilio への送信は行わず、SQS にメッセージを送信するだけにとどめます。

ローカルでプロジェクトを作成する

まずはローカル環境で Lambda 関数のコードを作成し、必要なパッケージをインストールします。

mkdir slack-sms-request-handler
cd slack-sms-request-handler
npm init -y
npm install aws-sdk

Lambda 関数の実装

次に、index.js を作成し、Slack の署名検証と SQS へのメッセージ送信処理を実装します。以下のコードでは、Slack からのリクエストを受け取り、署名検証を行った上で、SQS にメッセージを送信します。Twilio への SMS 送信は行わず、非同期処理のための準備のみを行います。

index.js

const crypto = require('crypto');
const querystring = require('querystring');
const { SQS } = require('aws-sdk');

const sqs = new SQS();

exports.handler = async (event) => {
    const headers = Object.fromEntries(
        Object.entries(event.headers || {}).map(([k, v]) => [k.toLowerCase(), v])
    );

    const timestamp = headers['x-slack-request-timestamp'];
    const slackSignature = headers['x-slack-signature'];

    if (!timestamp || !slackSignature) {
        return { statusCode: 400, body: '❌ 必要なヘッダーがありません。' };
    }

    // リプレイ攻撃対策
    const now = Math.floor(Date.now() / 1000);
    if (Math.abs(now - timestamp) > 60 * 5) {
        return { statusCode: 200, body: '❌ リクエストが古すぎます。' };
    }

    const rawBody = event.isBase64Encoded
        ? Buffer.from(event.body, 'base64').toString('utf8')
        : event.body;

    const sigBaseString = `v0:${timestamp}:${rawBody}`;
    const mySignature = 'v0=' + crypto
        .createHmac('sha256', process.env.SLACK_SIGNING_SECRET)
        .update(sigBaseString, 'utf8')
        .digest('hex');

    // 署名検証
    if (
        !crypto.timingSafeEqual(
            Buffer.from(mySignature, 'utf8'),
            Buffer.from(slackSignature, 'utf8')
        )
    ) {
        return { statusCode: 200, body: '❌ 署名検証に失敗しました。' };
    }

    const parsedBody = querystring.parse(rawBody);
    const text = parsedBody.text || '';
    const responseUrl = parsedBody.response_url;
    const [to, ...messageParts] = text.trim().split(/\s+/);
    const message = messageParts.join(' ');

    if (!to || !message) {
        return {
            statusCode: 200,
            body: '❌ 入力形式が正しくありません。\n使用例: /sms +81XXXXXXXXXX メッセージ本文'
        };
    }

    const sqsMessage = {
        to,
        message,
        response_url: responseUrl
    };

    try {
        await sqs.sendMessage({
            QueueUrl: process.env.SQS_QUEUE_URL,
            MessageBody: JSON.stringify(sqsMessage)
        }).promise();

        return {
            statusCode: 200,
            body: ''
        };
    } catch (err) {
        console.error('SQS error:', err);
        return {
            statusCode: 500,
            body: '❌ SQS への送信に失敗しました。'
        };
    }
};

Lambda にデプロイする

Lambda にアップロードするため、プロジェクトを zip にまとめます。

zip -r function.zip .

AWS Lambda コンソール にアクセスし、左メニュー 関数 を選択し 関数を作成 をクリックします。
4

一から作成 を選び、ランタイムに Node.js 22.x を指定します。名前は任意です (今回は slack-sms-request-handler とします) 。
29

コード タブを開き アップロード元 > .zip ファイル をクリックします。先ほど作成した function.zip をアップロードします。
6

環境変数の設定

コード タブ EXPLORER 内の ENVIRONMENT VARIABLES を開き、 Add environment variables ボタンをクリックして下記の環境変数を設定します。

キー
SQS_QUEUE_URL 作成した SQS キューの URL
SLACK_SIGNING_SECRET Slack アプリの Signing Secret

30

SQS 関連ポリシーをロールにアタッチする

設定 タブを開き アクセス権限 セクションのロール名をクリックし、ロールの設定ページにジャンプします。
31

許可 タブの 許可ポリシー許可を追加 から インラインポリシーを作成 をクリックします。
32

ポリシーエディタで JSON を選択し、次の内容を入力して作成します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "sqs:SendMessage",
      "Resource": "arn:aws:sqs:****"
    }
  ]
}

Lambda のページに戻ります。

補足: Lambda コンソールでテストを行う

Lambda 関数が正しく動作するかを確認するため、テストイベントを作成して動作確認を行うことができます。Lambda 関数の画面で テスト タブを開き、 テストイベントの作成 を選択します。以下のような JSON を入力して、Slack からのリクエストを模擬します (署名検証をスキップする場合) 。

{
  "headers": {
    "X-Slack-Request-Timestamp": "1234567890",
    "X-Slack-Signature": "v0=fake"
  },
  "body": "text=%2B819012345678+テストメッセージ"
}

API Gateway の設定

Slack のスラッシュコマンドからのリクエストを受け取るために、AWS Lambda 関数に API Gateway をトリガーとして追加します。ここでは、Lambda コンソールから直接 HTTP API を作成する方法を紹介します。 トリガーの追加 ボタンをクリックします。

9

  • トリガータイプ として API Gateway を選択します。
  • API タイプHTTP API を選択します。
  • API の作成方法新しい API を選択します。
  • セキュリティオープン (認証なし) を選択します。

10

トリガーが追加されると、API Gateway のエンドポイント URL が表示されます。この URL をコピーして控えておきます。後ほど Slack のスラッシュコマンドの設定で使用します。

11

API Gateway の設定を開き、左メニュー Integrations > 作成した API の ANY > 統合を管理 を選択します。 ペイロード形式のバージョン を 1.0 に設定します。

まとめ

第 2 回では、Slack からのリクエストを非同期で処理するためのバックエンド構成を実装しました。

  • Amazon SQS を使ってメッセージを一時的に保持し、
  • Lambda 関数で SMS 送信処理を非同期に実行し、
  • Slack の 3 秒制限を回避する構成を実現しました。

次回 (第 3 回) では、Slack アプリの作成とスラッシュコマンドの設定、署名検証のための Signing Secret の取得、そして実際の動作確認を行います。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.