![[Twilio+Slack] Slack に SMS 送信コマンドを実装する: SQS+Lambda での非同期処理構成 [2/3]](https://images.ctfassets.net/ct0aopd36mqt/wp-thumbnail-3f316c20ff5f4f46d1a005e2ad976882/c71162912c717bb0ca7c9b4196a40df5/twilio.png)
[Twilio+Slack] Slack に SMS 送信コマンドを実装する: SQS+Lambda での非同期処理構成 [2/3]
- 第 1 回: 構成と Twilio 初期設定
- 第 3 回: Slack アプリの設定と動作確認
対象読者
- 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 を送信するまでのバックエンド処理 (赤枠部分) を実装します。
SQS の作成
Slack からのリクエストを即時に受け取り、Twilio での SMS 送信処理を非同期で行うために、Amazon SQS (Simple Queue Service) を使用します。このセクションでは、非同期処理のための SQS キューを作成する手順を説明します。
SQS キューの作成
Amazon SQS コンソール にアクセスし、左メニューの キュー
を選択、キューを作成
をクリックします。
以下のように設定します。
項目 | 設定値 |
---|---|
キュータイプ | 標準 |
キュー名 | slack-sms-queue (任意) |
他の項目はデフォルトのままで構いません。 キューの作成
をクリックして完了します。
キューの URL を控える
作成したキューの詳細画面に表示される URL
をコピーしておきます。この URL は、Slack からのリクエストを受け取る Lambda 関数からメッセージを送信する際に使用します。
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 コンソール にアクセスし、左メニュー 関数
を選択し 関数を作成
をクリックします。
一から作成
を選び、ランタイムに Node.js 22.x を指定します。名前は任意です (今回は slack-sms-sender とします) 。
コード
タブを開き アップロード元 > .zip ファイル
をクリックします。先ほど作成した function.zip をアップロードします。
環境変数の設定
コード
タブ EXPLORER
内の ENVIRONMENT VARIABLES
を開き、 Add environment variables
ボタンをクリックして下記の環境変数を設定します。
キー | 値 |
---|---|
TWILIO_ACCOUNT_SID | Twilio のアカウント SID |
TWILIO_AUTH_TOKEN | Twilio の認証トークン |
TWILIO_PHONE_NUMBER | Twilio の送信元電話番号 |
タイムアウト設定
Twilio の SMS API には 3 秒以上かかる場合があるため、タイムアウト設定を 10 秒以上に設定しておくと確実です。
補足: Lambda コンソールでテストを行う
Lambda 関数が正しく動作するかを確認するため、テストイベントを作成して動作確認を行うことができます。Lambda 関数の画面で テスト
タブを開き、 テストイベントの作成
を選択します。以下のような JSON を入力して、SQS からのリクエストを模擬します。
{
"Records": [
{
"body": "{\"to\":\"+819012345678\",\"message\":\"テストメッセージ\",\"response_url\":\"https://hooks.slack.com/commands/xxx\"}"
}
]
}
SQS 関連ポリシーをロールにアタッチする
設定
タブを開き アクセス権限
セクションのロール名をクリックし、ロールの設定ページにジャンプします。
許可
タブの 許可ポリシー
の 許可を追加
から ポリシーをアタッチ
をクリックします。
AWSLambdaSQSQueueExecutionRole
を追加します。
Lambda のページに戻ります。
SQS からのアクセスを許可する
設定
タブの アクセス権限
から リソースベースのポリシーステートメント
に以下のアクセス権限を追加します。
項目 | 設定値 |
---|---|
ステートメント ID | AllowSQSInvoke |
プリンシパル | sqs.amazonaws.com |
アクション | lambda:InvokeFunction |
SQS の Lambda トリガーにアタッチする
SQS の設定画面に戻り、 Lambda トリガー
タブから作成したトリガーを設定します。
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 コンソール にアクセスし、左メニュー 関数
を選択し 関数を作成
をクリックします。
一から作成
を選び、ランタイムに Node.js 22.x を指定します。名前は任意です (今回は slack-sms-request-handler とします) 。
コード
タブを開き アップロード元 > .zip ファイル
をクリックします。先ほど作成した function.zip をアップロードします。
環境変数の設定
コード
タブ EXPLORER
内の ENVIRONMENT VARIABLES
を開き、 Add environment variables
ボタンをクリックして下記の環境変数を設定します。
キー | 値 |
---|---|
SQS_QUEUE_URL | 作成した SQS キューの URL |
SLACK_SIGNING_SECRET | Slack アプリの Signing Secret |
SQS 関連ポリシーをロールにアタッチする
設定
タブを開き アクセス権限
セクションのロール名をクリックし、ロールの設定ページにジャンプします。
許可
タブの 許可ポリシー
の 許可を追加
から インラインポリシーを作成
をクリックします。
ポリシーエディタで 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 を作成する方法を紹介します。 トリガーの追加
ボタンをクリックします。
トリガータイプ
としてAPI Gateway
を選択します。API タイプ
はHTTP API
を選択します。API の作成方法
は新しい API
を選択します。セキュリティ
はオープン
(認証なし) を選択します。
トリガーが追加されると、API Gateway のエンドポイント URL が表示されます。この URL をコピーして控えておきます。後ほど Slack のスラッシュコマンドの設定で使用します。
API Gateway の設定を開き、左メニュー Integrations > 作成した API の ANY > 統合を管理
を選択します。 ペイロード形式のバージョン
を 1.0 に設定します。
まとめ
第 2 回では、Slack からのリクエストを非同期で処理するためのバックエンド構成を実装しました。
- Amazon SQS を使ってメッセージを一時的に保持し、
- Lambda 関数で SMS 送信処理を非同期に実行し、
- Slack の 3 秒制限を回避する構成を実現しました。
次回 (第 3 回) では、Slack アプリの作成とスラッシュコマンドの設定、署名検証のための Signing Secret の取得、そして実際の動作確認を行います。
- 第 1 回: 構成と Twilio 初期設定
- 第 3 回: Slack アプリの設定と動作確認