Amazon Connect エージェントワークスペースで、発信時にキューを選択できるサードパーティアプリケーションを作ってみた
はじめに
Amazon Connect でエージェントがアウトバウンドコールを行う際、用途や顧客に応じて発信時に利用する発信元電話番号を、キュー単位で切り替えたいことがあります。
たとえば、
- サービスごとに発信番号を分けたい
- 窓口ごとに異なるキューを使い分けたい
- 顧客対応の種類に応じて発信元を切り替えたい
といったケースです。
標準のエージェントワークスペースでも発信自体は可能ですが、通常の発信では明示的にキューを指定することができません。エージェントワークスペース上でキュー選択用の専用 UI を用意したい場合は、Amazon Connect のサードパーティアプリケーションを使うと実現できます。
なお、サードパーティアプリケーションは デフォルトエージェントワークスペースでのみサポートされており、カスタム CCP ではサポートされません。
今回は、Amazon Connect エージェントワークスペース に表示できるサードパーティアプリケーションとして、以下のようなシンプルな外線発信アプリを作成してみました。
- エージェントの ルーティングプロファイル からキュー一覧を取得
- 画面上で発信キューを選択
- E.164 形式の電話番号を入力して発信
最終的な画面イメージは以下のようになります。

通常の発信元電話番号はどこの設定か
今回のアプリの挙動を理解するうえで、通常の発信とキューを指定した発信の違いを整理しておきます。
Amazon Connect の通常のアウトバウンド発信では、ユーザーに紐づく ルーティングプロファイル の デフォルトのアウトバウンドキューで指定したキューにおいて、そのキューで設定しているアウトバウンド発信者 ID 番号が使われます。

ルーティングプロファイルのデフォルトのアウトバウンドキューでは、cm-hiraiキューが指定されている

cm-hiraiキューのアウトバウンド発信者 ID 番号で電話番号が指定されている
一方で、今回のアプリのようにキューを明示的に指定して発信する場合は、そのキューの設定が使われます。
つまり、整理すると以下です。
- 通常の標準発信(明示的にキューを指定しない)
→ ルーティングプロファイルのデフォルトのアウトバウンドキューの発信者 ID が使われる。 - 今回のアプリのようにキューを選んで発信
→ 選択したキューの発信者 ID が使われる。
実装の流れ
今回の実装の流れは以下です。
- Amazon Connect SDK を使ったサードパーティアプリケーション用の HTML / JavaScript を作成
- S3 バケットを作成してアプリファイルを配置
- CloudFront で配信
- Amazon Connect のサードパーティアプリケーションに登録
- Amazon Connect インスタンスに関連付け
- セキュリティプロファイル にアプリ利用権限を付与
- エージェントワークスペースで動作確認
サードパーティアプリケーション用ファイルの作成
今回は CloudShell を使って作成しました。
CloudShell 上では Node.js / npm が利用できるため、そのまま bundle 作成まで進められます。
作業ディレクトリを作成します。
mkdir -p ~/connect-3p-outbound
cd ~/connect-3p-outbound
index.html を作成
まず、アプリ本体となる index.html を作成します。
コードは以下の通りです。
cat > index.html <<'EOF'
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>外線発信アプリ</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
padding: 20px;
background-color: #f2f3f3;
margin: 0;
}
.container {
background-color: #ffffff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
font-size: 14px;
color: #333;
}
select, input {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
font-size: 14px;
}
button {
width: 100%;
padding: 10px;
background-color: #0073bb;
color: white;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
margin-top: 10px;
}
button:hover {
background-color: #005a93;
}
button:disabled {
background-color: #9aa5b1;
cursor: not-allowed;
}
#statusMessage {
margin-top: 15px;
font-size: 14px;
text-align: center;
font-weight: bold;
white-space: pre-wrap;
}
.hint {
margin-top: 8px;
font-size: 12px;
color: #666;
}
</style>
</head>
<body>
<div class="container">
<h3 style="margin-top: 0; color: #333;">外線発信</h3>
<div class="form-group">
<label for="queueSelect">発信キュー</label>
<select id="queueSelect" disabled>
<option value="">読み込み中...</option>
</select>
<div class="hint">Amazon Connect のエージェントワークスペース内で利用してください。</div>
</div>
<div class="form-group">
<label for="phoneNumber">発信先電話番号 (E.164形式)</label>
<input type="text" id="phoneNumber" placeholder="例: +819012345678" />
</div>
<button id="callButton" disabled>発信</button>
<div id="statusMessage"></div>
</div>
<script src="./connect-sdk-app.bundle.js"></script>
<script>
const queueSelect = document.getElementById('queueSelect');
const phoneNumberInput = document.getElementById('phoneNumber');
const callButton = document.getElementById('callButton');
const statusMessage = document.getElementById('statusMessage');
let voiceClient = null;
let appCreated = false;
function setStatus(message, color = '#333') {
statusMessage.textContent = message;
statusMessage.style.color = color;
}
function isE164(phone) {
return /^\+[1-9]\d{1,14}$/.test(phone);
}
function addQueueOption(queue, suffix = '') {
if (!queue?.queueARN) return;
if (!queue?.name || String(queue.name).trim() === '') return;
const option = document.createElement('option');
option.value = queue.queueARN;
option.textContent = `${queue.name}${suffix}`;
queueSelect.appendChild(option);
}
async function initApp() {
try {
setStatus('初期化中...');
if (!window.AmazonConnectSDK) {
throw new Error('AmazonConnectSDK が見つかりません。connect-sdk-app.bundle.js の配置を確認してください。');
}
const { provider } = window.AmazonConnectSDK.AmazonConnectApp.init({
onCreate: async (event) => {
try {
appCreated = true;
console.log('App created:', event);
const agentClient = new window.AmazonConnectSDK.AgentClient({ provider });
voiceClient = new window.AmazonConnectSDK.VoiceClient({ provider });
const routingProfile = await agentClient.getRoutingProfile();
console.log('routingProfile =', routingProfile);
console.log('queues =', routingProfile?.queues);
console.log('defaultOutboundQueue =', routingProfile?.defaultOutboundQueue);
queueSelect.innerHTML = '<option value="">キューを選択してください</option>';
const queues = Array.isArray(routingProfile?.queues) ? routingProfile.queues : [];
const defaultOutboundQueue = routingProfile?.defaultOutboundQueue;
queues
.filter(queue => queue?.name && String(queue.name).trim() !== '')
.forEach(queue => addQueueOption(queue));
if (
defaultOutboundQueue &&
defaultOutboundQueue.name &&
String(defaultOutboundQueue.name).trim() !== '' &&
!queues.some(q => q.queueARN === defaultOutboundQueue.queueARN)
) {
addQueueOption(defaultOutboundQueue, '(デフォルト発信キュー)');
}
queueSelect.disabled = false;
callButton.disabled = false;
if (queueSelect.options.length <= 1) {
setStatus('キューが見つかりません。ルーティングプロファイル または権限設定を確認してください。', 'red');
} else {
setStatus('キューを読み込みました。', 'green');
}
} catch (err) {
console.error('Initialization after onCreate failed:', err);
queueSelect.disabled = true;
callButton.disabled = true;
setStatus('Amazon Connect 内で開いてください。', 'red');
}
},
onDestroy: (event) => {
console.log('App destroyed:', event);
}
});
setTimeout(() => {
if (!appCreated) {
queueSelect.disabled = true;
callButton.disabled = true;
setStatus('Amazon Connect 内で開いてください。', 'red');
}
}, 5000);
} catch (err) {
console.error('Initialization error:', err);
queueSelect.disabled = true;
callButton.disabled = true;
setStatus('Amazon Connect 内で開いてください。', 'red');
}
}
callButton.addEventListener('click', async () => {
try {
if (!voiceClient) {
setStatus('初期化が完了していません。', 'red');
return;
}
const phoneNumber = phoneNumberInput.value.trim();
const selectedQueueArn = queueSelect.value;
if (!phoneNumber) {
setStatus('電話番号を入力してください。', 'red');
return;
}
if (!isE164(phoneNumber)) {
setStatus('電話番号は E.164 形式で入力してください。', 'red');
return;
}
callButton.disabled = true;
setStatus('発信中...');
let result;
if (selectedQueueArn) {
result = await voiceClient.createOutboundCall(phoneNumber, {
queueARN: selectedQueueArn
});
} else {
result = await voiceClient.createOutboundCall(phoneNumber);
}
console.log('createOutboundCall result =', result);
setStatus('発信処理を開始しました。', 'green');
} catch (err) {
console.error('Outbound call failed:', err);
setStatus(
'発信に失敗しました。選択したキューに outbound caller ID number が設定されているか確認してください。',
'red'
);
} finally {
callButton.disabled = false;
}
});
initApp();
</script>
</body>
</html>
EOF
このアプリの処理内容におけるポイントは以下の4点です。
1. Amazon Connect SDK を利用
今回のアプリは、Amazon Connect エージェントワークスペースのサードパーティアプリケーションとして動作させています。
アプリ初期化時に AmazonConnectApp.init() を呼び出し、返却される provider を使って各種クライアントを生成する実装にしています。AWS のエージェントワークスペース開発者ガイドでも、アプリ初期化には AmazonConnectApp.init() を使い、返却される provider を保持してクライアント生成に利用するよう案内されています。
2. キュー一覧の取得
本アプリで表示するキュー一覧は、Amazon Connect 全体のキュー一覧ではありません。
AgentClient.getRoutingProfile() で取得した、現在ログインしているエージェントのルーティングプロファイルに含まれるキューを表示しています。
Connect 上に多数のキューが存在していても、対象ユーザーのルーティングプロファイルに紐づいたキューだけが候補になります。
なお、AgentClient.getRoutingProfile() のレスポンスには、システム内部で利用される特殊なキューや、名前情報を持たないデータが含まれる場合があります。
そのため、今回はアプリ側で name が設定されているキューだけを表示するようにし、エージェントにとって不要な選択肢が出ないようにしています。
3. キューの発信者 ID番号が未設定だと発信は失敗
AWSドキュメントには、キューの 発信者 ID番号 が未設定の場合について、次のように明記されています。
If no number is set for the outbound caller ID number for the queue, the call attempt will fail.
https://docs.aws.amazon.com/connect/latest/adminguide/queues-callerid.html
そのため、今回のようなキュー選択式のアプリを運用する場合は、発信候補にするキューに 発信者 ID番号 を設定しておくことが前提になります。
4. 発信時のキュー指定について
エージェントワークスペースの VoiceClient.createOutboundCall() は queueARN を任意で指定でき、未指定の場合はルーティングプロファイルのデフォルトのアウトバウンドキューが使われます。
今回のアプリでは、プルダウンで選択されたキューの ARN を渡すことで、発信元の切り替えを実現しています。
entry-app.js を作成
次に、bundle を作るためのエントリファイルを作成します。
今回は、index.html を S3 / CloudFront で配信するシンプルな構成にしたかったため、Amazon Connect SDK を事前に bundle 化して connect-sdk-app.bundle.js として配置しています。AWS のドキュメントでも、SDK は npm パッケージとして配布されており、ブラウザの <script> タグで直接読み込むことはできず、ブラウザ互換形式の prebuilt script file を作成する必要があると説明されています。
cat > entry-app.js <<'EOF'
import { AmazonConnectApp } from "@amazon-connect/app";
import { AgentClient } from "@amazon-connect/contact";
import { VoiceClient } from "@amazon-connect/voice";
window.AmazonConnectSDK = {
AmazonConnectApp,
AgentClient,
VoiceClient,
};
EOF
bundle を作成
npm パッケージをインストールして、connect-sdk-app.bundle.js を生成します。
npm init -y
npm install @amazon-connect/app @amazon-connect/contact @amazon-connect/voice
npm install --save-dev esbuild
npx esbuild entry-app.js \
--bundle \
--minify \
--format=iife \
--target=es2020 \
--outfile=connect-sdk-app.bundle.js
これで、配置する 2 ファイルができました。
index.htmlconnect-sdk-app.bundle.js
S3 バケットを作成
S3 バケットは以下で作成しました。
aws s3api create-bucket \
--bucket バケット名 \
--region ap-northeast-1 \
--create-bucket-configuration LocationConstraint=ap-northeast-1
その後、2 ファイルをアップロードします。
aws s3 cp index.html s3://バケット名/index.html
aws s3 cp connect-sdk-app.bundle.js s3://バケット名/connect-sdk-app.bundle.js
CloudFront を作成
S3 をオリジンにして CloudFront を作成します。今回はコンソールから作成しました。
無料プランを選択しました。WAFが自動適用されますが、ランニングコストは無料です。


S3バケットをオリジンにします。オリジンパスは設定せず、検証中で、コードの変更を即座に反映させるため、キャッシュは無効化します。

モニターモードは設定せず、作成完了です。

なお、コンソールで OAC を使って作成する場合、CloudFront から S3 へのアクセスに必要な バケットポリシー は自動で設定されます。
Amazon Connect にサードパーティアプリケーションを登録・関連付け
次に、Amazon Connect のサードパーティアプリケーションからアプリを追加します。
今回は以下のように設定しました。
- 表示名:
OutboundCallApp - 説明:
Outbound Call Application - Integration type: 標準アプリケーション
- コンタクト範囲: クロスコンタクト
- 初期化タイムアウト:
5000 - Access URL:
https://<cloudfront-domain>/index.html
許可とiframe設定は、デフォルトで設定しました。
{
"Permissions": [
"*"
]
}
{
"IframeConfig": {
"Allow": [],
"Sandbox": [
"allow-forms",
"allow-popups",
"allow-same-origin",
"allow-scripts"
]
}
}

セキュリティプロファイルにアプリ権限を付与
Amazon Connect 側でアプリを作成しただけでは、エージェントの画面には表示されません。対象のセキュリティプロファイルに対して、アプリの利用権限を付与します。
エージェントがアプリランチャーからサードパーティアプリケーションを利用するためには、該当アプリへのアクセス権の設定が必須となります。

エージェントワークスペースで確認
エージェントワークスペースにログインし、アプリランチャーから「OutboundCallApp」を開きます。
このアプリはタブとしてピン留めすることも可能です。

アプリが開くと、ルーティングプロファイルに紐づいたキューがプルダウンに一覧表示されます。任意のキューを選択し、電話番号を入力して外線発信を行います。

発信中の画面挙動についてですが、アプリをピン留めしている場合は通話中も表示され続けます。一方、ピン留めしていない場合は、以下のように通話コントロール画面に切り替わり、「OutboundCallApp」の画面は消えます。

なお、プルダウンで「キューを選択してください」(未選択状態)のまま発信した場合は、ユーザーのルーティングプロファイルに設定されている「デフォルトのアウトバウンドキュー」を使用して発信されます。
セキュリティ対策
CloudFront などの基本的な設定に加えて、今回アプリ側の実装としてお伝えしておきたいセキュリティ対策が 1 点あります。
ワークスペース外アクセス時は利用不可メッセージを表示
このアプリは AmazonConnectApp.init() によってエージェントワークスペースから provider を受け取って初期化する前提の作りになっています。AWS のドキュメントでも、アプリをワークスペース外で直接読み込むと、エージェントワークスペースに接続できなかった旨のエラーが開発者ツールに表示されると説明されています。
そのため今回は、onCreate イベントが一定時間内に発生しなかった場合も含めて、ワークスペース外から直接アクセスされたときは 「Amazon Connect 内で開いてください。」 と画面に表示する実装にしています。これは直接アクセス自体を禁止するものではなく、直接アクセスされても利用できないことを明示するための実装です。
実際にブラウザから直接 URL へアクセスすると、以下のように表示されます。

ただし、この状態では URL を知っていれば誰でもページ自体は開けてしまいますので、注意ください。
次回の記事では、このサードパーティアプリケーションを本番運用する際に検討すべき本格的なセキュリティ対策について解説できればと思います。
まとめ
Amazon Connect のエージェントワークスペースにサードパーティアプリケーションを追加することで、エージェントが外線発信時にキューを選べる UI を実装できました。
標準機能だけでは難しい「発信用途ごとのキュー選択」も、Amazon Connect SDK を活用したサードパーティアプリケーションを使えば比較的シンプルに実現できます。S3 と CloudFront を使ったサーバーレスな構成で手軽にホスティングできる点も魅力です。
エージェント業務に合わせた専用 UI をワークスペース内に組み込めるのは非常に便利なので、要件に合わせてぜひ活用してみてください。
参考







