概要
- CPaaS NOWをGAS(Google Apps Script)で接続してみようと思い生成AI(Github copilot Agent)を利用して作ってみた
このアプリの仕様
- spreadsheetにある連絡先に対してSMSを一括で送ることができる
- CPaaS NOWの「SMS配信登録」と「SMS配信結果取得」のAPIを利用
- 送信に必要な情報は相手先の電話番号のみ
- SMS送信するメッセージは固定文言
- 送信結果は別シートで確認することができる
やり方
CPaaS NOWのAPIドキュメントからopenapi.yamlファイルをダウンロードする

VS Codeでダウンロードしたyamlファイルを開きAIエージェント(Github copilot Agent)に依頼する
Google Apps ScriptでCPaaS NOWに接続するツールを作成してみたい、どう始めればいい?
- 依頼したらあとはひたすら待つだけ
出来上がったファイルでやり方を確認する
- sms送信以外の部分はカットしています
readme.md
# Google Apps Script用 CPaaS NOW サンプル
このディレクトリには、Google Apps ScriptでCPaaS NOWのAPIを利用するためのサンプルコードとドキュメントが含まれています。
## 開始方法
### 1. Google Apps Scriptプロジェクトの作成
1. [Google Apps Script](https://script.google.com/) にアクセス
2. 「新しいプロジェクト」をクリック
3. プロジェクト名を設定(例:「CPaaS NOW連携ツール」)
### 2. 基本設定
Google Apps ScriptでCPaaS NOWを利用するには、以下の手順で設定を行います:
1. **APIトークンの設定**
- CPaaS NOWの設定画面からAPIトークンを取得
- Google Apps Scriptの「プロジェクトの設定」→「スクリプト プロパティ」でAPIトークンを設定
2. **HTTPSリクエストの有効化**
- Google Apps Scriptでは外部APIへのHTTPSリクエストがデフォルトで利用可能
### 3. サンプルファイル
- `sms_basic.gs` - SMS配信の基本例
- `spreadsheet_integration.gs` - Googleスプレッドシートとの連携例
- `cpaas_client.gs` - CPaaS NOW APIクライアントクラス
~~~省略
実際にやってみる
1.スプレッドシートでGASを開く

2.GASのスクリプトに下記ファイルを追加する

- cpaas_client.gs
- spreadsheet.gs
cpaas_client.gs
/**
* CPaaS NOW API クライアントクラス
* Google Apps Scriptで利用するためのCPaaS NOW APIラッパー
*/
class CPaaSClient {
constructor(apiToken, baseUrl = 'https://sandbox.cpaasnow.com') {
this.apiToken = apiToken;
this.baseUrl = baseUrl;
}
/**
* 共通のHTTPリクエスト処理
* @param {string} endpoint - APIエンドポイント
* @param {string} method - HTTPメソッド
* @param {Object} payload - リクエストボディ
* @returns {Object} APIレスポンス
*/
makeRequest(endpoint, method = 'GET', payload = null) {
const options = {
method: method,
headers: {
'Authorization': `Bearer ${this.apiToken}`,
'Content-Type': 'application/json'
}
};
if (payload && method !== 'GET') {
options.payload = JSON.stringify(payload);
}
try {
const response = UrlFetchApp.fetch(`${this.baseUrl}${endpoint}`, options);
const responseCode = response.getResponseCode();
const responseText = response.getContentText();
// レスポンスコードチェック
if (responseCode >= 200 && responseCode < 300) {
return {
success: true,
data: JSON.parse(responseText),
status: responseCode
};
} else {
const errorData = JSON.parse(responseText);
return {
success: false,
error: errorData,
status: responseCode
};
}
} catch (error) {
console.error('API Request Error:', error);
return {
success: false,
error: { message: error.toString() },
status: 0
};
}
}
/**
* SMS配信登録
* @param {string} phoneNumber - 宛先電話番号
* @param {string} message - SMS本文
* @param {boolean} clickTracking - クリックトラッキング有効/無効
* @param {string} userReference - ユーザー参照情報
* @param {string} billSplitCode - 請求分割コード
* @returns {Object} API レスポンス
*/
sendSMS(phoneNumber, message, clickTracking = false, userReference = null, billSplitCode = null) {
const payload = {
to: phoneNumber,
text: message,
click_tracking: clickTracking
};
if (userReference) payload.user_reference = userReference;
if (billSplitCode) payload.bill_split_code = billSplitCode;
return this.makeRequest('/api/v1/short_messages', 'POST', payload);
}
/**
* SMS配信結果取得
* @param {string} deliveryOrderIds - 配信オーダーID(カンマ区切り)
* @param {string} acceptedAtFrom - 受付日時開始
* @param {string} acceptedAtTo - 受付日時終了
* @param {number} limit - 取得件数制限
* @param {number} offset - オフセット
* @returns {Object} API レスポンス
*/
getSMSResults(deliveryOrderIds = null, acceptedAtFrom = null, acceptedAtTo = null, limit = 100, offset = 0) {
let params = [`limit=${limit}`, `offset=${offset}`];
if (deliveryOrderIds) params.push(`delivery_order_ids=${deliveryOrderIds}`);
if (acceptedAtFrom) params.push(`accepted_at_from=${acceptedAtFrom}`);
if (acceptedAtTo) params.push(`accepted_at_to=${acceptedAtTo}`);
const queryString = params.join('&');
return this.makeRequest(`/api/v1/short_messages?${queryString}`, 'GET');
}
spreadsheet.gs
/**
* Googleスプレッドシートとの連携例
*
* 機能:
* - スプレッドシートから連絡先を読み込み
* - 一括配信の実行
* - 配信結果の記録
* - 配信ログの管理
*
* 前提条件:
* 1. スクリプト プロパティに「CPAAS_API_TOKEN」を設定
* 2. cpaas_client.gs ファイルを同じプロジェクトに追加
*/
/**
* メイン処理:スプレッドシートから連絡先を読み込んでSMS一括配信
*/
function sendSMSFromSpreadsheet() {
try {
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
const contactsSheet = getOrCreateSheet(spreadsheet, '連絡先');
const logsSheet = getOrCreateSheet(spreadsheet, '配信ログ');
// 初回実行時にヘッダーを作成
setupContactsSheet(contactsSheet);
setupLogsSheet(logsSheet);
// 連絡先データを読み込み
const contacts = readContactsFromSheet(contactsSheet);
if (contacts.length === 0) {
console.log('配信対象の連絡先がありません');
return;
}
console.log(`${contacts.length}件の連絡先を読み込みました`);
// CPaaS NOW クライアントを初期化
const apiToken = PropertiesService.getScriptProperties().getProperty('CPAAS_API_TOKEN');
if (!apiToken) {
throw new Error('CPAAS_API_TOKEN が設定されていません。setup_guide.gs の setupAPIToken() を実行してください。');
}
const client = new CPaaSClient(apiToken);
// メッセージ内容(設定シートから読み込むことも可能)
const message = getMessageTemplate();
// 一括配信実行
const results = [];
for (let i = 0; i < contacts.length; i++) {
const contact = contacts[i];
console.log(`配信中: ${contact.name} (${i + 1}/${contacts.length})`);
// 送信前の最終チェック
if (!contact.phoneNumber || contact.phoneNumber === '') {
console.error(`エラー: ${contact.name}の電話番号が空です`);
const logEntry = {
timestamp: new Date(),
name: contact.name,
phoneNumber: contact.phoneNumber || '',
success: false,
deliveryOrderId: '',
errorCode: 'MISSING_PHONE_NUMBER',
errorMessage: '電話番号が入力されていません'
};
results.push(logEntry);
writeLogToSheet(logsSheet, logEntry);
continue;
}
console.log(`→ 送信先: ${contact.phoneNumber}`);
const result = client.sendSMS(
contact.phoneNumber,
message,
false, // クリックトラッキング
`batch_${new Date().getTime()}_${i + 1}` // ユーザー参照
);
console.log(`配信結果: ${result.success ? '成功' : '失敗'}`);
if (!result.success) {
console.error(`エラー詳細: ${result.error.code} - ${result.error.message}`);
}
// 結果を記録
const logEntry = {
timestamp: new Date(),
name: contact.name,
phoneNumber: contact.phoneNumber,
success: result.success,
deliveryOrderId: result.success ? result.data.delivery_order_id : '',
errorCode: result.success ? '' : result.error.code,
errorMessage: result.success ? '' : result.error.message
};
results.push(logEntry);
// ログシートに記録
writeLogToSheet(logsSheet, logEntry);
// レート制限対策
Utilities.sleep(1000);
}
// 結果サマリーを表示
showResultSummary(results);
} catch (error) {
console.error('エラーが発生しました:', error);
throw error;
}
}
// ===== ヘルパー関数 =====
/**
* シートを取得または作成
*/
function getOrCreateSheet(spreadsheet, sheetName) {
console.log(`getOrCreateSheet: シート "${sheetName}" を取得または作成中...`);
if (!spreadsheet) {
console.error('エラー: spreadsheet が null または undefined です');
throw new Error('spreadsheet パラメータが無効です');
}
console.log(`スプレッドシート名: ${spreadsheet.getName()}`);
let sheet;
try {
sheet = spreadsheet.getSheetByName(sheetName);
if (sheet) {
console.log(`✓ 既存のシート "${sheetName}" を取得しました`);
} else {
console.log(`シート "${sheetName}" が存在しません。新規作成します...`);
sheet = spreadsheet.insertSheet(sheetName);
console.log(`✓ 新しいシート "${sheetName}" を作成しました`);
}
} catch (error) {
console.error(`シート操作中にエラーが発生しました: ${error.toString()}`);
throw error;
}
if (!sheet) {
throw new Error(`シート "${sheetName}" の作成に失敗しました`);
}
return sheet;
}
/**
* 連絡先シートのセットアップ
*/
function setupContactsSheet(sheet) {
console.log('setupContactsSheet: 連絡先シートをセットアップ中...');
if (!sheet) {
console.error('エラー: sheet パラメータが null または undefined です');
throw new Error('sheet パラメータが無効です');
}
console.log(`シート名: ${sheet.getName()}`);
console.log(`現在の行数: ${sheet.getLastRow()}`);
if (sheet.getLastRow() === 0) {
console.log('空のシートです。ヘッダーとサンプルデータを追加します...');
const headers = ['名前', '電話番号', 'メールアドレス', '備考'];
sheet.getRange(1, 1, 1, headers.length).setValues([headers]);
sheet.getRange(1, 1, 1, headers.length).setFontWeight('bold');
// 電話番号列を文字列フォーマットに設定
const phoneNumberColumn = sheet.getRange('B:B'); // B列(電話番号列)
phoneNumberColumn.setNumberFormat('@'); // @ は文字列フォーマット
console.log('✓ 電話番号列を文字列フォーマットに設定しました');
// サンプルデータを追加(文字列として明示的に設定)
const sampleData = [
['田中太郎', '09001111101', 'success@example.com', 'テスト用'],
['佐藤花子', '09001111102', 'success@example.com', 'テスト用'],
['鈴木次郎', '09001111103', 'success@example.com', 'テスト用']
];
// データを1行ずつ設定して文字列として保存
for (let i = 0; i < sampleData.length; i++) {
const row = sampleData[i];
sheet.getRange(i + 2, 1).setValue(row[0]); // 名前
sheet.getRange(i + 2, 2).setValue(row[1]); // 電話番号(文字列として)
sheet.getRange(i + 2, 3).setValue(row[2]); // メール
sheet.getRange(i + 2, 4).setValue(row[3]); // 備考
}
console.log('✓ 連絡先シートを初期化しました');
console.log('注意: テスト用電話番号を使用しています。実際の配信前に適切な番号に変更してください。');
console.log('📝 電話番号は文字列フォーマットで入力してください(先頭の0が保持されます)');
} else {
console.log('✓ 連絡先シートは既に初期化済みです');
// 既存シートでも電話番号列のフォーマットを設定
const phoneNumberColumn = sheet.getRange('B:B');
phoneNumberColumn.setNumberFormat('@');
console.log('✓ 既存の電話番号列を文字列フォーマットに設定しました');
}
}
/**
* ログシートのセットアップ
*/
function setupLogsSheet(sheet) {
console.log('setupLogsSheet: ログシートをセットアップ中...');
if (!sheet) {
console.error('エラー: sheet パラメータが null または undefined です');
throw new Error('sheet パラメータが無効です');
}
console.log(`シート名: ${sheet.getName()}`);
console.log(`現在の行数: ${sheet.getLastRow()}`);
if (sheet.getLastRow() === 0) {
console.log('空のシートです。ヘッダーを追加します...');
const headers = ['配信日時', '名前', '電話番号', 'メールアドレス', '配信オーダーID', '成功/失敗', 'エラーコード', 'エラーメッセージ'];
sheet.getRange(1, 1, 1, headers.length).setValues([headers]);
sheet.getRange(1, 1, 1, headers.length).setFontWeight('bold');
console.log('✓ ログシートを初期化しました');
} else {
console.log('✓ ログシートは既に初期化済みです');
}
}
/**
* 連絡先データの読み込み
*/
function readContactsFromSheet(sheet) {
const data = sheet.getDataRange().getValues();
const contacts = [];
console.log(`シートから${data.length - 1}行のデータを読み込み中...`);
for (let i = 1; i < data.length; i++) { // ヘッダー行をスキップ
const name = data[i][0];
const phoneNumber = data[i][1];
const email = data[i][2];
const note = data[i][3];
// デバッグ用ログ
console.log(`行${i + 1}: 名前="${name}", 電話番号="${phoneNumber}", メール="${email}"`);
if (name) { // 名前が入力されている行のみ
// 電話番号の検証と正規化
let normalizedPhoneNumber = '';
if (phoneNumber !== null && phoneNumber !== undefined && phoneNumber !== '') {
// 数値として読み込まれた場合の対応(先頭0を復元)
let phoneStr = phoneNumber.toString().trim();
// 数値で読み込まれて先頭の0が消えた場合を検出・修正
if (/^[7-9][0-9]{9}$/.test(phoneStr)) {
// 携帯電話番号の場合(70~99で始まる10桁)
phoneStr = '0' + phoneStr;
console.log(`先頭0を復元: ${phoneNumber} -> ${phoneStr}`);
} else if (/^20[0-9]{8,11}$/.test(phoneStr)) {
// 020番号の場合
phoneStr = '0' + phoneStr;
console.log(`先頭0を復元: ${phoneNumber} -> ${phoneStr}`);
} else if (/^[1-6][0-9]{8,9}$/.test(phoneStr)) {
// 固定電話番号の場合(01~06で始まる)
phoneStr = '0' + phoneStr;
console.log(`先頭0を復元: ${phoneNumber} -> ${phoneStr}`);
}
// ハイフンやスペースを除去
normalizedPhoneNumber = phoneStr.replace(/[-\s]/g, '');
// 電話番号の形式チェック
if (!/^(0[1-9][0-9]{8,9}|0[7-9]0[0-9]{8})$/.test(normalizedPhoneNumber)) {
console.warn(`警告: ${name}の電話番号が無効です: "${phoneNumber}" -> "${normalizedPhoneNumber}"`);
console.warn(`有効な形式: 携帯電話(070/080/090-XXXX-XXXX)、固定電話(0X-XXXX-XXXX)`);
// 完全に無効な場合はスキップ
if (normalizedPhoneNumber.length < 10) {
console.warn(`${name}をスキップします(電話番号が短すぎる)`);
continue;
}
}
} else {
console.warn(`警告: ${name}の電話番号が未入力です`);
continue;
}
const contact = {
name: name.toString().trim(),
phoneNumber: normalizedPhoneNumber,
email: email ? email.toString().trim() : '',
note: note ? note.toString().trim() : ''
};
contacts.push(contact);
console.log(`✓ 有効な連絡先: ${contact.name} (${contact.phoneNumber})`);
}
}
console.log(`合計${contacts.length}件の有効な連絡先を読み込みました`);
return contacts;
}
/**
* ログエントリをシートに書き込み
*/
function writeLogToSheet(sheet, logEntry) {
const row = [
logEntry.timestamp,
logEntry.name,
logEntry.phoneNumber,
logEntry.email || '',
logEntry.deliveryOrderId,
logEntry.success ? '成功' : '失敗',
logEntry.errorCode,
logEntry.errorMessage
];
sheet.appendRow(row);
}
/**
* 配信結果詳細をシートに書き込み
*/
function writeDeliveryResults(sheet, type, deliveryOrders) {
// ヘッダー設定
if (sheet.getLastRow() === 0) {
const headers = ['タイプ', '配信オーダーID', 'ステータス', '受付日時', '完了日時', '配信ID', '宛先', '配信ステータス', '配信日時', '配信停止希望', 'エラーコード'];
sheet.getRange(1, 1, 1, headers.length).setValues([headers]);
sheet.getRange(1, 1, 1, headers.length).setFontWeight('bold');
}
deliveryOrders.forEach(order => {
order.deliveries.forEach(delivery => {
const row = [
type,
order.id,
order.status,
order.accepted_at,
order.end_at || '',
delivery.id,
delivery.to,
delivery.status,
delivery.delivered_at || '',
delivery.opted_out,
delivery.error ? delivery.error.code : ''
];
sheet.appendRow(row);
});
});
}
/**
* メッセージテンプレートを取得
*/
function getMessageTemplate() {
// 設定シートから読み込むか、固定メッセージを返す
return `【重要なお知らせ】
いつもご利用いただきありがとうございます。
システムメンテナンスのお知らせです。
メンテナンス日時:${Utilities.formatDate(new Date(), 'JST', 'yyyy年MM月dd日 HH:mm')}
詳細は弊社ウェブサイトをご確認ください。
https://example.com/maintenance
配信停止を希望される場合は {{配信停止URL}} をクリックしてください。`;
}
/**
* 結果サマリーを表示
*/
function showResultSummary(results) {
const successCount = results.filter(r => r.success).length;
const failureCount = results.length - successCount;
console.log('\n=== 配信結果サマリー ===');
console.log(`総配信数: ${results.length}`);
console.log(`成功: ${successCount}`);
console.log(`失敗: ${failureCount}`);
if (failureCount > 0) {
console.log('\n失敗詳細:');
results.filter(r => !r.success).forEach(result => {
console.log(`- ${result.name}: ${result.errorCode} - ${result.errorMessage}`);
});
}
}
/**
* 既存の連絡先シートの電話番号フォーマットを修正
*/
function fixPhoneNumberFormat() {
console.log('=== 電話番号フォーマット修正 ===');
try {
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
const contactsSheet = getOrCreateSheet(spreadsheet, '連絡先');
// 電話番号列を文字列フォーマットに設定
const phoneNumberColumn = contactsSheet.getRange('B:B');
phoneNumberColumn.setNumberFormat('@');
console.log('✓ 電話番号列を文字列フォーマットに設定しました');
// 既存データの電話番号を修正
const data = contactsSheet.getDataRange().getValues();
let fixedCount = 0;
for (let i = 1; i < data.length; i++) { // ヘッダー行をスキップ
const name = data[i][0];
const phoneNumber = data[i][1];
if (name && phoneNumber) {
let phoneStr = phoneNumber.toString().trim();
let originalPhone = phoneStr;
let fixed = false;
// 先頭0が消えた場合の修正
if (/^[7-9][0-9]{9}$/.test(phoneStr)) {
phoneStr = '0' + phoneStr;
fixed = true;
} else if (/^20[0-9]{8,11}$/.test(phoneStr)) {
phoneStr = '0' + phoneStr;
fixed = true;
} else if (/^[1-6][0-9]{8,9}$/.test(phoneStr)) {
phoneStr = '0' + phoneStr;
fixed = true;
}
if (fixed) {
contactsSheet.getRange(i + 1, 2).setValue(phoneStr);
console.log(`修正: ${name} の電話番号 ${originalPhone} -> ${phoneStr}`);
fixedCount++;
}
}
}
console.log(`✓ ${fixedCount}件の電話番号を修正しました`);
console.log('修正完了: 今後は電話番号を入力する際に先頭の0が保持されます');
} catch (error) {
console.error('電話番号フォーマット修正中にエラーが発生しました:', error);
}
}
3.Google Apps Scriptの「プロジェクトの設定」→「スクリプト プロパティ」でAPIトークンを設定

4.実際に送信してみる
- 連絡先ファイルを作成する
-
sendSMSFromSpreadsheet
を実行すると下記エラーが発生
API Request Error: { [Exception: Request failed for https://sandbox.cpaasnow.com returned code 400. Truncated server response: {"code":"InvalidParameter","message":"パラメータに誤りがあります","details":[{"parameter":"to","message":"toは070,080,090から始まる11桁の番号か、020から始まる11桁または14桁の番号にしてください... (use muteHttpExceptions option to examine full response)] name: 'Exception' }
- 先頭の0が消えているのが原因なのでAIエージェントに修正依頼
連絡先シートの電話番号が文字列ではないので先頭の0が認識できない
-
fixPhoneNumberFormat
関数を用意してくれたので実行する -
sendSMSFromSpreadsheet
を再度実行し送信した -
成功した
感想
- 100%AIエージェントにお任せして実装はできなかったが、適宜指摘していったら簡単に作成することができた
- スクリプトでもコード量は多いと感じた、必要ないコードは自分で適宜メンテナンスする必要がありそう
- スプレッドシートに登録した連絡先に対して送信するアプリはできたので、実際に利用シーンがあるといいなと思う
もっと他の記事も読んでみたい方
当社に興味がある方はこちら👀