はじめに:「通話後30分間の作業」を自動化したい
営業担当者が1件の商談を終えた後、デスクではどのような作業を行っているでしょうか。一例として、以下のようなケースが考えられます。
- 記憶が新しいうちに商談内容をメモし、CRM (Salesforce等) に入力する (20分)
- 海外のチームやマネジメント層向けに英語等のサマリーを作成・メール送信する (10分)
- ネクストアクション(宿題や次アポ)をタスク管理ツールに登録する (5分)
合計で35分。もし週10件の商談があれば、月に約23時間もの時間が、この「通話後処理」というドキュメンテーション作業に消えている計算になります。
今年2026年にリリースされた「Zoom AI Services」は、Zoomが提供する高品質なAIモデルを「シンプルなREST API」として利用できるPaaSです。
今回は、この中からScribe (文字起こし)、Summarizer (要約)、Translator (翻訳) の3つのAPIを1本のパイプラインとして接続し、 「録音ファイルを1発投げるだけで、CRM更新と多言語共有メールが20秒で完了する仕組み」 を構築しました。
手作業によるタイムラグや記入漏れをなくし、商談直後の新鮮なコンテキストを即座に組織へ共有する方法をご紹介します!
本記事がご参考になりましたら 「いいね」や「ストック」 いただけますと大変励みになります!
全体アーキテクチャと処理フロー
本システムは、音声データを起点として、各サービスがバケツリレー式にデータを処理し、最終的に外部の業務ツールへと書き込みを行います。
各APIの役割とデータ(In/Out)
| API名 | 役割 | 入力データ | 出力データ |
|---|---|---|---|
| Scribe API | 音声のテキスト化 | 音声ファイルのURL | 全文文字起こしテキスト |
| Summarizer API | 構造化された要約の抽出 | 文字起こしテキスト | 決定事項・アクション (JSON) |
| Translator API | テキストの翻訳 | 日本語の要約テキスト | 英語・中国語等の多言語テキスト |
環境準備
Node.js + TypeScript環境で構築します。今回環境構築するにあたり外部依存ライブラリは最小限に抑えています。
mkdir zoom-sales-pipeline && cd zoom-sales-pipeline
npm init -y
npm install typescript tsx dotenv
npm install @aws-sdk/client-ses
npx tsc --init
.env ファイルは以下のように設定します
# Build Platform の JWT
ZOOM_AI_TOKEN=<ZOOM_TOKEN>
# 音声ファイルのパブリックURL(S3等のクラウドストレージサービスのURL)
AUDIO_URL=https://your-bucket.s3.amazonaws.com/call-2026-06-29.mp3
# 連携先ツールの認証情報
SF_ACCESS_TOKEN=<SF_TOKEN>
SF_INSTANCE_URL=https://your-domain.my.salesforce.com
SF_CONTACT_ID=XXXXXXX
SES_FROM_ADDRESS=ai-pipeline@your-domain.com
実装
1.共通APIクライアント
堅牢性を高めるため、HTTPステータスエラーが発生した際にレスポンスのボディ(エラー詳細)をログに出力する共通クライアントを定義します。
// src/zoomClient.ts
import 'dotenv/config';
export async function zoomPost<T>(path: string, body: object): Promise<T> {
const res = await fetch(`https://api.zoom.us/v2${path}`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.ZOOM_AI_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
if (!res.ok) {
const errorDetails = await res.text();
throw new Error(`[Zoom API Error ${res.status}] URL: ${path} - Details: ${errorDetails}`);
}
return res.json();
}
2. Scribe API:音声からテキストへの変換
音声データを高精度にテキスト化します。 商談の会話は機密情報や個人情報が含まれやすい ため、実務運用ではAPI送信前にローカルで匿名化(Redaction)を挟むか、API側の安全な処理方針を策定することを推奨します。今回は単に一例のため省略しています。
// src/steps/transcribe.ts
import { zoomPost } from '../zoomClient';
export async function transcribe(audioUrl: string): Promise<string> {
console.log('🎙 Scribe API: 文字起こし処理を開始します...');
const result = await zoomPost<{ transcript: string }>(
'/aiservices/scribe/transcribe',
{
file: audioUrl,
config: {
language: 'ja-JP',
word_time_offsets: false,
},
}
);
console.log(`✅ Scribe API: 文字起こし完了 (${result.transcript.length}文字)`);
return result.transcript;
}
3. Summarizer API:要約 & アクションアイテムの構造化
task: "full_summary" を指定することで、会話の全体サマリーと、「タスク・担当者・期限」の構造化データを1回のリクエストでまとめて取得します。
さらに実務上の有用性を高めるため、会話内のキーワードから顧客の熱量を簡易分析するロジックも追加しています。あくまで簡易なので、LLMに投げたりなど良い方法が他にもあると思います。
// src/steps/summarize.ts
import { zoomPost } from '../zoomClient';
export interface SalesCallSummary {
summaryText: string;
actionItems: Array<{
owner: string;
task: string;
due?: string;
}>;
customerSentiment: string;
}
export async function summarize(transcript: string): Promise<SalesCallSummary> {
console.log('📝 Summarizer API: 要約およびアクションアイテムの抽出中...');
const result = await zoomPost<{
result: {
text: string;
action_items: Array<{ owner: string; task: string; due_date?: string }>
}
}>(
'/aiservices/summarizer/summarize',
{
input: { text: transcript },
config: {
summary_type: 'conversation',
task: 'full_summary',
language: 'ja-JP',
},
reference_id: `sales-${Date.now()}`,
}
);
const sentiment = detectSentiment(transcript);
return {
summaryText: result.result.text,
actionItems: result.result.action_items.map(i => ({
owner: i.owner,
task: i.task,
due: i.due_date,
})),
customerSentiment: sentiment,
};
}
// 商談テキストから商談相手のトーンを簡易判定
function detectSentiment(text: string): string {
const positive = ['ありがとう', '検討します', '進めましょう', '良いと思います', '導入'];
const negative = ['難しい', '懸念', '予算が', '見送り', '失注'];
const posCount = positive.filter(w => text.includes(w)).length;
const negCount = negative.filter(w => text.includes(w)).length;
if (posCount > negCount) return '🟢 良好(前向き・進捗あり)';
if (negCount > posCount) return '🔴 要フォロー(懸念・障害あり)';
return '🟡 ニュートラル';
}
4. Translator API:多言語への翻訳
海外拠点のマネジメントやチームへ共有するため、生成された日本語の要約を英語 (en-US) や中国語 (zh-CN)へ翻訳します。
// src/steps/translate.ts
import { zoomPost } from '../zoomClient';
export async function translateSummary(
japaneseText: string,
targetLanguages: string[] = ['en-US']
): Promise<Record<string, string>> {
console.log(`🌐 Translator API: 多言語翻訳中... [対象: ${targetLanguages.join(', ')}]`);
const result = await zoomPost<{
translations: Array<{ language: string; text: string }>
}>(
'/aiservices/translator/translate',
{
text: japaneseText.slice(0, 4000),
config: {
source_language: 'ja-JP',
target_languages: targetLanguages,
},
reference_id: `trans-${Date.now()}`,
}
);
return Object.fromEntries(
result.translations.map(t => [t.language, t.text])
);
}
5. Salesforce CRMへの自動書き込み
要約されたテキストとアクションアイテムを、Salesforceの「Task」オブジェクトへ自動起票します。これにより、商談直後に顧客カルテが最新状態に更新されます。
サクッと検証したい方へ:
Salesforceのアカウントをお持ちでない場合は、APIリクエスト(fetch)の部分をコメントアウトし、リクエストボディを出力するだけのモック処理に差し替えても、パイプライン全体の連動を問題なく検証可能です。
// src/steps/updateCRM.ts
import type { SalesCallSummary } from './summarize';
export async function updateCRM(contactId: string, summary: SalesCallSummary): Promise<void> {
console.log('💼 CRM連携: Salesforceへ活動履歴を書き込み中...');
const sfToken = process.env.SF_ACCESS_TOKEN!;
const sfInstance = process.env.SF_INSTANCE_URL!;
const description = [
`【AI自動生成】商談要約サマリー`,
`----------------────────────────`,
`■ 顧客対応トーン: ${summary.customerSentiment}`,
`\n■ AI要約:\n${summary.summaryText}`,
`\n■ 抽出されたネクストアクション:`,
summary.actionItems.map(a => `・[${a.owner}] ${a.task} (期日: ${a.due ?? '未設定'})`).join('\n')
].join('\n');
// アカウントがない場合は以下をコメントアウトし、console.log(description) でモック化可能です
const res = await fetch(`${sfInstance}/services/data/v59.0/sobjects/Task`, {
method: 'POST',
headers: {
Authorization: `Bearer ${sfToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
WhoId: contactId,
Subject: `[AI自動記録] 商談サマリー (${new Date().toLocaleDateString('ja-JP')})`,
Description: description,
Status: 'Completed',
Priority: 'Normal',
ActivityDate: new Date().toISOString().split('T')[0],
}),
});
if (!res.ok) throw new Error(`[Salesforce Error] ${await res.text()}`);
console.log('✅ CRM連携: Salesforceへの書き込みが完了しました');
}
6. AWS SES:海外チームへの共有メール自動送信
翻訳された英語の要約と、英語表記にマッピングしたアクションアイテムを、海外チームのメーリングリストへ自動配信します。
// src/steps/sendEmail.ts
import { SESClient, SendEmailCommand } from "@aws-sdk/client-ses";
import type { SalesCallSummary } from './summarize';
// SESクライアントの初期化
const sesClient = new SESClient({
region: process.env.AWS_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
},
});
export async function sendOverseasEmail(
toEmail: string,
summary: SalesCallSummary,
translations: Record<string, string>
): Promise<void> {
console.log(`📧 メール連携: AWS SES経由で海外チーム (${toEmail}) へ送信中...`);
const englishSummary = translations['en-US'] || '(Translation failed)';
const emailBody = `
[Auto-generated by Zoom AI Services Pipeline]
Dear Global Team,
A sales call has been completed. Below is the automatically generated summary and action items.
=== 1. Executive Summary (Translated) ===
${englishSummary}
=== 2. Action Items ===
${summary.actionItems.map(a => `• [${a.owner}]: ${a.task} (Due: ${a.due ?? 'TBD'})`).join('\n')}
=== 3. Customer Engagement Sentiment ===
${summary.customerSentiment}
---
This email was automatically dispatched by the Zoom AI Sales Pipeline.
Please update the CRM directly if any modifications are required.
`.trim();
// SESのコマンド構築
const command = new SendEmailCommand({
Source: process.env.SES_FROM_ADDRESS,
Destination: {
ToAddresses: [toEmail],
},
Message: {
Subject: {
Data: `[Global Sync] Sales Call Summary - ${new Date().toLocaleDateString('en-US')}`,
Charset: 'UTF-8',
},
Body: {
Text: {
Data: emailBody,
Charset: 'UTF-8',
},
},
},
});
try {
await sesClient.send(command);
console.log('✅ メール連携: AWS SES経由での送信が完了しました');
} catch (error) {
console.error('❌ SES送信エラー:', error);
throw error;
}
}
7. パイプラインの一元実行
すべてのステップを結合します。後半の「CRMへの書き込み」と「メール送信」は、互いに依存関係がないためPromise.allを用いて並列処理し、実行速度をさらに最適化します。
// src/pipeline.ts
import 'dotenv/config';
import { transcribe } from './steps/transcribe';
import { summarize } from './steps/summarize';
import { translateSummary } from './steps/translate';
import { updateCRM } from './steps/updateCRM';
import { sendOverseasEmail } from './steps/sendEmail';
async function main() {
const audioUrl = process.env.AUDIO_URL;
const contactId = process.env.SF_CONTACT_ID;
const toEmail = process.env.OVERSEAS_EMAIL;
if (!audioUrl || !contactId || !toEmail) {
console.error('❌ エラー: 必要な環境変数が設定されていません。.envファイルを確認してください。');
process.exit(1);
}
console.log('\n🚀 === Zoom AI Services 自動化パイプラインを開始します ===\n');
const startTime = Date.now();
try {
// 1. 音声の文字起こし
const transcript = await transcribe(audioUrl);
// 2. テキストの要約とタスク抽出
const summary = await summarize(transcript);
// 3. 要約結果を多言語翻訳(英語・中国語)
const translations = await translateSummary(summary.summaryText, ['en-US', 'zh-CN']);
// 4 & 5. 外部システムへの配信
await Promise.all([
updateCRM(contactId, summary),
sendOverseasEmail(toEmail, summary, translations)
]);
const duration = ((Date.now() - startTime) / 1000).toFixed(1);
console.log(`\n🎉 === すべての自動化プロセスが正常に完了しました! (${duration}秒) ===\n`);
} catch (error: any) {
console.error('\n❌ パイプライン実行中に致命的なエラーが発生しました:');
console.error(error.message);
process.exit(1);
}
}
main();
ソフトウェアコンポーネントの依存関係
この自動化パイプラインは、単一のオーケストレーターが各処理を呼び出し、外部サービス(Zoom AI Services/Salesforce/AWS SES)と連携するモジュール構成をとっています。
実行結果
今まで35分かかっていた営業のドキュメンテーションと共有の作業が、Zoom AI Servicesを使用したパイプラインによってわずか20.5秒で完了しました。
🚀 === Zoom AI Services 自動化パイプラインを開始します ===
🎙 Scribe API: 文字起こし処理を開始します...
✅ Scribe API: 文字起こし完了 (3,733文字)
📝 Summarizer API: 要約およびアクションアイテムの抽出中...
🌐 Translator API: 多言語翻訳中... [対象: en-US, zh-CN]
💼 CRM連携: Salesforceへ活動履歴を書き込み中...
📧 メール連携: 海外チーム (global-team@example.com) へ多言語サマリーを送信中...
✅ CRM連携: Salesforceへの書き込みが完了しました
✅ メール連携: 海外チームへのメール送信が完了しました
🎉 === すべての自動化プロセスが正常に完了しました! (20.5秒) ===
アカウントを作ると特典でもらえる20ドル分の無料クレジットですが、今回のパイプライン(Scribe → Summarizer → Translator)を一通り組んで、テストで何度か動かしてみたものの、クレジットはほとんど減りませんでした。
細かい単価計算は省きますが(データ量でも変わるので)、個人で触るレベルや、ちょっとしたチームでの検証運用くらいなら、この無料枠だけで結構試すことができると思います。「すぐ枠を使い切ったら嫌だな」という心配をせずに、ガシガシ動かせるのはかなり嬉しいポイントです。
実際に触ってわかった運用のTips
-
Scribe APIの使用感
Scribe APIに渡すファイルは、Zoomのインフラ側からインターネット経由で直接アクセス (HTTP GET)できるパブリックなURLである必要があります。
そのため、検証する場合はS3のパブリック読み取りバケットに一時アップロードすることなどを検討してください。 -
本番運用でのアーキテクチャ検討
今回のサンプルでは、即時レスポンスが返る「Fast mode」を前提とした同期処理で実装しています。Fast mode は同期処理のため、長時間録音や大量データではレスポンス待機時間が長くなる可能性があります。大規模処理や運用上の信頼性を重視する場合は、Batch modeとWebhookを組み合わせた非同期アーキテクチャを推奨します。
まとめ
Zoom AI Servicesの魅力は、高性能なAIモデルそのものだけでなく、それらをシンプルなREST APIとして組み合わせられることにあります。
Scribeで文字起こしし、Summarizerで要約し、Translatorで多言語展開する。そんな一連の流れを簡単なコードで自動化できます。
20ドル分の無料クレジットも用意されているので、ぜひ実際に触りながら、自分たちの業務フローに合わせたAI活用を試してみてください!
参考リンク