Boltを使ってSlackワークフローからDynamoDBにデータを挿入する
こんにちは👋26新卒の大山です。
Slackワークフローで受け取ったデータをGoogleスプレッドシートに保存している仕組みを、DynamoDBへ移行する計画があり、その一つの方法として「Slackの入口部分はワークフローのままで、保存先だけDynamoDBにする構成」を検証したので、その内容をブログにしようと思います。
Slack ワークフローの種類
Slackでワークフローを作成する方法としてSlackアプリケーション内にあるワークフロービルダーから作成する方法・Custom Functionsを使う方法・Boltを使う3種類あります。
| 機能 | ワークフロービルダー | Custom Functions | Bolt |
|---|---|---|---|
| プログラミング | 不要 | 必要(Deno + TS) | 必要(JS・Python・Java) |
| インフラ・デプロイ | Slackが管理 | Slackが管理 | AWS Lambdaなど |
| トリガー | メッセージ送信、Webhook、リアクションなど限定的 | 自由にカスタム可能 | 自由にカスタム可能 |
| 外部連携 | コネクターで対応しているもの | 自由に実装可能 | 自由に実装可能 |
| メッセージのカスタマイズ | 限定的 | Block KitでUIを手軽にカスタマイズ可能 | Block KitでUIを手軽にカスタマイズ可能 |
| コスト | Proプラン以上で利用可能 | Proプラン以上で利用可能 | フリープランから無料で利用可能。別途サーバー代 |
| 向いている用途 | 定型的なフロー | インフラ管理はしたくない・独自の外部サービス連携が必要 | 既存のサーバーやAWSなどに統合したい・複雑なロジックが必要 |
今回は統合予定のシステムで使われているAWSサービスがあるので、Boltを使ってLambda経由でDynamoDBに保存する仕組みで作っていきます。
Slackアプリの作成
まずSlack APIにアクセスして、アプリの作成を行います。

アプリの作り方としてUIで作るScratchと、JSON/YAML形式でアプリの基本情報を構築するManifestの2つの方法があります。
今回はManifestの方法で作成していきます。

インストール先のワークスペースの選択
アプリをインストールするワークスペースを選択します。
この選択は後で変更できないため注意してください。

Manifestをアップロード
今回はYAML形式で作成します。
urlとrequest_urlの部分は後で書き換えるのでとりあえず仮で入れています。
display_information:
name: SlackTestApp
description: フォームからDynamoDBにデータを送信するBot
background_color: "#2c2d30"
features:
bot_user:
display_name: SlackTestApp
always_online: true
slash_commands:
- command: /submit
url: https://example.com
description: 申請フォームを開く
usage_hint: /submit
should_escape: false
oauth_config:
scopes:
bot:
- commands
- chat:write
- users:read
- users:read.email
pkce_enabled: false
settings:
interactivity:
is_enabled: true
request_url: https://example.com
org_deploy_enabled: false
socket_mode_enabled: false
token_rotation_enabled: false
is_mcp_enabled: false

ワークスペースにインストール
作ったアプリを導入するワークスペースにインストールします。
-
Features > OAuth & Permission > OAuth Tokens内にある、
Install to SlackAPP[ワークスペース名]を選択。

-
権限を確認して許可するを選択。

これでアプリの作成は完了です!
プロジェクトの作成
下記の構成で構築していきます。
- フレームワーク: Bolt
- IaC: AWS CDK
- 言語: TypeScript
- パッケージマネージャー:pnpm
アーキテクチャはシンプルに、API Gatewayで受けたリクエストをLambdaに渡し、Lambda内でSlackのリクエスト検証を行った上でDynamoDBに保存します。SlackのSigning SecretとBot Tokenは、Parameter Storeにて管理します。

CDK
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as lambdaNode from 'aws-cdk-lib/aws-lambda-nodejs';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as path from 'path';
export class SlackAppStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// ── DynamoDB ──────────────────────────────────────────────────────────────
const table = new dynamodb.Table(this, 'SlackAppTest', {
tableName: 'SlackAppTest',
partitionKey: {
name: 'id',
type: dynamodb.AttributeType.STRING,
},
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
// ── Lambda ────────────────────────────────────────────────────────────────
const handler = new lambdaNode.NodejsFunction(this, 'SlackHandler', {
functionName: 'slackapp-handler',
entry: path.join(__dirname, '../../src/index.ts'),
handler: 'handler',
runtime: lambda.Runtime.NODEJS_22_X,
timeout: cdk.Duration.seconds(10),
memorySize: 128,
environment: {
TABLE_NAME: table.tableName,
SLACK_SIGNING_SECRET_PARAM: '/slackapp/signing-secret',
SLACK_BOT_TOKEN_PARAM: '/slackapp/bot-token',
},
});
// ── IAM: DynamoDB 書き込み権限 ─────────────────────────────────────────────
table.grantWriteData(handler);
// ── IAM: SSM SecureString 読み取り権限 ────────────────────────────────────
handler.addToRolePolicy(
new iam.PolicyStatement({
actions: ['ssm:GetParameter'],
resources: [
`arn:aws:ssm:${this.region}:${this.account}:parameter/slackapp/*`,
],
}),
);
// ── API Gateway ───────────────────────────────────────────────────────────
const api = new apigateway.RestApi(this, 'SlackApi', {
restApiName: 'slackapp-api',
deployOptions: { stageName: 'dev' },
});
const integration = new apigateway.LambdaIntegration(handler, {
proxy: true,
});
const slackResource = api.root.addResource('slack');
slackResource.addMethod('POST', integration);
// ── Outputs ───────────────────────────────────────────────────────────────
new cdk.CfnOutput(this, 'SlackEndpointUrl', {
value: `${api.url}slack`,
description: 'Slack のスラッシュコマンド URL・Interactivity Request URL の両方に設定してください',
});
}
}
const app = new cdk.App();
new SlackAppStack(app, 'SlackAppStack');
フォームUIの作成
フォームUIの作成にはBlock Kit Builderを使って作成します。

左側のツールバーからブロックを追加したり、ドラッグで並び順を変更したりと、GUIベースで直感的にフォームUIを作成できます。作成できたら、右側の欄に表示されているJSONをコピーし、以下のように views.open の view プロパティに貼り付けます。
await client.views.open({
view: {
//ここに入れる
}
})
Lambda
import { App, AwsLambdaReceiver } from '@slack/bolt';
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, PutCommand } from '@aws-sdk/lib-dynamodb';
import { SSMClient, GetParameterCommand } from '@aws-sdk/client-ssm';
import { v4 as uuidv4 } from 'uuid';
// ── AWS クライアント ───────────────────────────────────────────────────────────
const dynamo = DynamoDBDocumentClient.from(new DynamoDBClient({}));
const ssmClient = new SSMClient({});
const TABLE_NAME = process.env.TABLE_NAME!;
async function getParameter(name: string): Promise<string> {
const result = await ssmClient.send(
new GetParameterCommand({ Name: name, WithDecryption: true }),
);
return result.Parameter!.Value!;
}
// ── Bolt 初期化 ────────────────────
let lambdaHandler: ((...args: any[]) => Promise<unknown>) | undefined;
async function initApp(): Promise<NonNullable<typeof lambdaHandler>> {
if (lambdaHandler) return lambdaHandler;
const [signingSecret, botToken] = await Promise.all([
getParameter(process.env.SLACK_SIGNING_SECRET_PARAM!),
getParameter(process.env.SLACK_BOT_TOKEN_PARAM!),
]);
const receiver = new AwsLambdaReceiver({ signingSecret });
const app = new App({ token: botToken, receiver });
// ── /submit スラッシュコマンド:モーダルを開く ────────────────────────────
app.command('/submit', async ({ ack, command, client, logger }) => {
await ack();
try {
await client.views.open({
trigger_id: command.trigger_id,
view: {
"callback_id": "submit_modal",
"type": "modal",
"title": {
"type": "plain_text",
"text": "申請ワークフロー",
"emoji": true
},
"submit": {
"type": "plain_text",
"text": "送信",
"emoji": true
},
"close": {
"type": "plain_text",
"text": "キャンセル",
"emoji": true
},
"blocks": [
{
"type": "input",
"block_id": "text_block",
"label": {
"type": "plain_text",
"text": "申請内容"
},
"element": {
"type": "plain_text_input",
"action_id": "text_input",
"placeholder": {
"type": "plain_text",
"text": "申請内容を入力してください"
}
}
}
]
}
});
} catch (error) {
logger.error('views.open に失敗しました:', error);
}
});
// ── モーダル送信:DynamoDB に保存 ─────────────────────────────────────────
app.view('submit_modal', async ({ ack, body, logger }) => {
await ack();
const userId = body.user.id;
const text = body.view.state.values['text_block']['text_input'].value ?? '';
try {
await dynamo.send(
new PutCommand({
TableName: TABLE_NAME,
Item: {
id: uuidv4(),
userId,
text,
submittedAt: new Date().toISOString(),
},
}),
);
logger.info(`申請を保存しました: userId=${userId}`);
} catch (error) {
logger.error('DynamoDB への保存に失敗しました:', error);
}
});
lambdaHandler = await receiver.start();
return lambdaHandler;
}
// ── Lambda エントリーポイント ──────────────────────────────────────────────────
export const handler = async (...args: any[]) => {
const fn = await initApp();
return fn(...args);
};
Slack APIから環境変数を取得
Slack APIのダッシュボードから、Client SecretとOAuth Tokensを取得します。
- Settings > Basic Information > App CredentialsからClient Secretを取得。

- Features > OAuth & PermissionからOAuth Tokensを取得。

パラメータストアに環境変数を追加
-
AWS マネジメントコンソールにログインし、AWS Systems Manager > アプリケーションツール > パラメータストアに遷移します。
-
パラメーターの作成を選択

-
名前に
/slackapp/signing-secret・/slackapp/bot-tokenを、それぞれ【利用枠:標準】【タイプ:安全な文字列】【KMSキーソース:現在のアカウント】で該当の値を入力してパラメーターを作成。

デプロイ
- CDKデプロイ
コマンドはお使いのパッケージマネージャーやAWS CLIのプロファイル名に合わせて変更してください。
pnpm exec cdk deploy --profile work
-
CloudFormation > スタックにあるステータスがCREATE_COMPLETEになっていればOKです。

-
デプロイ後、ターミナルに出力された Outputs の SlackEndpointUrl、もしくは CloudFormation > スタック > (該当スタック) > 出力タブ から SlackEndpointUrl の値をコピーします。
-
Slack APIのFeatures > App Manifestから2箇所に3でコピーしたエンドポイントURLを入力します。
slash_commands:
- command: /submit
url: 👉ここにエンドポイントURLをペースト
description: 申請フォームを開く
settings:
interactivity:
is_enabled: true
request_url: 👉ここにエンドポイントURLをペースト
org_deploy_enabled: false

試してみる
-
アプリをインストールしたSlackワークスペースを開き、テキストボックスから
/submitと入力し送信。

-
モーダルが開くので、入力して
送信ボタンを選択。

-
AWSマネジメントコンソールから DynamoDB > 項目を探索 > SlackAppTest を開くと、入力した内容が無事に保存されていることが確認できました🎉

まとめ
Slackのワークフローには複数の作り方があり、「コネクタ非対応の外部ツールと連携したい」「既存システムと統合したい」など、要件に合わせて柔軟に選択できるのが魅力だと感じました。今回のようにBolt + AWS構成にすることで、Slackを入口にした業務フローをそのままクラウド側の資産として活かせるので、既存システムとの統合を検討している方はぜひ試してみてください!







