Googleフォーム連携・社外向けメール自動生成システム
SHOGAKU
19 days ago
Googleフォーム連携・社外向けメール自動生成システム(汎用版)
📋 システム概要
社内フォーム入力により、社外向けの定型メールを自動生成し、入力者に下書きとして送信するシステム。部品失効通知、契約更新案内、定期レポートなど様々な定型業務に適用可能。
🚨 重要:技術制約とその解決策
❌ 技術的制約
- GAS実行権限: スクリプトは管理者権限でのみ実行される
- Gmail下書き制限: 他人のGmailアカウントに直接下書きを作成することは不可能
- 権限の壁: フォーム入力者 ≠ 下書き作成可能者
💡 巧妙な解決策
【不可能】
フォーム入力者のGmailに直接下書き作成 ❌
【実装された解決策】
フォーム入力者
↓
GAS(管理者権限で実行)
↓
完成した下書き内容をHTMLメールで入力者に送信 ✅
↓
入力者が受信メールから下書き内容を取得・活用
つまり:「下書きを直接作成できない」技術制約を「下書き内容の配信」で実用的に解決
🔄 ワークフロー
社員がフォーム入力(誰でも可能)
↓
GASスクリプト自動実行(管理者権限)
↓
入力データ検証・処理
↓
社外向け定型メール生成
↓
【重要】入力者に完成した下書き内容をメール送信
↓
入力者が受信メールから下書きを取得・編集
↓
顧客・取引先に転送
🔑 システムの核心価値
🎯 解決した課題
- 技術制約: 他人のGmail下書き作成は不可能
- 権限問題: フォーム入力者 ≠ 下書き作成権限者
- 実用性: それでも誰でも完成した下書きを入手したい
✅ 実装した解決策
- 権限回避: 直接下書き作成ではなく、メール配信方式
- 実用性確保: 受信メールから下書き内容を取得可能
- 品質標準化: 誰が入力しても同じクオリティの下書きを提供
- 運用の簡素化: フォーム入力だけで完成品が手に入る
🛠️ システム構成
1. Googleフォーム設定
- 共有範囲: 組織ドメイン限定(例:@yourcompany.com)
- アクセス制御: 「編集者に転送する」設定
- 認証: Googleアカウントログイン必須
- メールアドレス: 自動収集(必要条件)
2. フォーム入力項目例
項目 | 用途 | 例 |
---|---|---|
顧客・取引先名 | 送信先の特定 | "ABC商事株式会社" |
件名キーワード | メール件名用 | "部品失効通知" |
重要日付 | 期限・有効期限等 | "2024/12/31" |
詳細情報1 | 業務固有情報 | "P-12345-REV-A" |
詳細情報2 | 補足情報 | "東京工場" |
担当者名 | 社内責任者 | "田中太郎" |
送信先メール | 顧客連絡先 | "contact@abc-corp.co.jp" |
3. GASスクリプト機能
- トリガー: フォーム送信時自動実行
- データ取得: フォーム回答の自動読み込み
- メール生成: HTMLテンプレートによる定型メール作成
- 送信制御: 組織ドメイン制限とメールアドレス検証
📧 GASコード全体
/**
* Googleフォーム連携・社外向けメール自動生成システム
* 汎用版 - 様々な定型業務に適用可能
*/
// ===== 設定項目 =====
const CONFIG = {
// 組織設定
ORGANIZATION_DOMAIN: '@yourcompany.com', // 組織ドメイン
ORGANIZATION_NAME: 'YOUR COMPANY', // 組織名
// フォーム・スプレッドシート設定
FORM_ID: 'your_form_id_here', // GoogleフォームID
SPREADSHEET_ID: 'your_spreadsheet_id_here', // 回答スプレッドシートID
SHEET_NAME: 'フォームの回答 1', // 回答シート名
// メール設定
SENDER_NAME: 'システム自動送信', // 送信者名
SUBJECT_PREFIX: 'AUTO', // 件名プレフィックス
// デバッグ設定
DEBUG_MODE: true, // デバッグログ出力
TEST_MODE: false // テストモード(実際の送信を停止)
};
// ===== フォーム項目のマッピング =====
const FORM_FIELDS = {
TIMESTAMP: 0, // タイムスタンプ
EMAIL: 1, // メールアドレス(入力者)
CLIENT_NAME: 2, // 顧客・取引先名
SUBJECT_KEYWORD: 3, // 件名キーワード
IMPORTANT_DATE: 4, // 重要日付
DETAIL_INFO1: 5, // 詳細情報1
DETAIL_INFO2: 6, // 詳細情報2
PERSON_IN_CHARGE: 7, // 担当者名
RECIPIENT_EMAIL: 8 // 送信先メールアドレス
};
// ===== メイン関数 =====
/**
* フォーム送信時に実行される関数
* トリガーで自動実行するように設定
*/
function onFormSubmit(e) {
try {
if (CONFIG.DEBUG_MODE) {
Logger.log('=== フォーム送信処理開始 ===');
}
// フォームデータの取得と処理
const formData = getLatestFormResponse();
if (!formData) {
Logger.log('エラー: フォームデータを取得できませんでした');
return;
}
// データ検証
if (!validateFormData(formData)) {
Logger.log('エラー: データ検証に失敗しました');
return;
}
// メール生成・送信
const success = generateAndSendEmail(formData);
if (success) {
Logger.log('✅ メール送信処理が正常に完了しました');
} else {
Logger.log('❌ メール送信処理に失敗しました');
}
} catch (error) {
Logger.log(`❌ 予期しないエラーが発生しました: ${error.toString()}`);
// エラー通知メール(管理者向け)
sendErrorNotification(error);
}
}
// ===== データ取得・処理関数 =====
/**
* スプレッドシートから最新のフォーム回答を取得
*/
function getLatestFormResponse() {
try {
const sheet = SpreadsheetApp.openById(CONFIG.SPREADSHEET_ID).getSheetByName(CONFIG.SHEET_NAME);
const data = sheet.getDataRange().getValues();
if (data.length < 2) {
Logger.log('エラー: データが存在しません');
return null;
}
// 最新の回答(最後の行)を取得
const latestRow = data[data.length - 1];
const formData = {
timestamp: latestRow[FORM_FIELDS.TIMESTAMP],
inputterEmail: latestRow[FORM_FIELDS.EMAIL],
clientName: latestRow[FORM_FIELDS.CLIENT_NAME],
subjectKeyword: latestRow[FORM_FIELDS.SUBJECT_KEYWORD],
importantDate: latestRow[FORM_FIELDS.IMPORTANT_DATE],
detailInfo1: latestRow[FORM_FIELDS.DETAIL_INFO1],
detailInfo2: latestRow[FORM_FIELDS.DETAIL_INFO2],
personInCharge: latestRow[FORM_FIELDS.PERSON_IN_CHARGE],
recipientEmail: latestRow[FORM_FIELDS.RECIPIENT_EMAIL]
};
if (CONFIG.DEBUG_MODE) {
Logger.log('取得データ:');
Object.keys(formData).forEach(key => {
Logger.log(` ${key}: ${formData[key]}`);
});
}
return formData;
} catch (error) {
Logger.log(`データ取得エラー: ${error.toString()}`);
return null;
}
}
/**
* フォームデータの検証
*/
function validateFormData(formData) {
// 必須項目チェック
const requiredFields = ['inputterEmail', 'clientName', 'recipientEmail'];
for (const field of requiredFields) {
if (!formData[field] || formData[field].toString().trim() === '') {
Logger.log(`エラー: 必須項目が未入力です - ${field}`);
return false;
}
}
// 入力者メールアドレスのドメインチェック
if (!formData.inputterEmail.endsWith(CONFIG.ORGANIZATION_DOMAIN)) {
Logger.log(`エラー: 許可されていないドメインです - ${formData.inputterEmail}`);
return false;
}
// メールアドレス形式チェック
if (!isValidEmail(formData.inputterEmail) || !isValidEmail(formData.recipientEmail)) {
Logger.log('エラー: 無効なメールアドレス形式です');
return false;
}
return true;
}
/**
* メールアドレスの妥当性チェック
*/
function isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
// ===== メール生成・送信関数 =====
/**
* メール生成・送信のメイン処理
*/
function generateAndSendEmail(formData) {
try {
// 日付フォーマット処理
const formattedDate = formatDate(formData.importantDate);
// メール件名生成
const subject = generateEmailSubject(formData, formattedDate);
// HTMLメール本文生成
const htmlBody = generateEmailBody(formData, formattedDate);
// メール送信実行
if (!CONFIG.TEST_MODE) {
GmailApp.sendEmail(
formData.inputterEmail,
subject,
'', // プレーンテキスト版(空)
{
htmlBody: htmlBody,
name: CONFIG.SENDER_NAME
}
);
Logger.log(`✅ メール送信完了: ${formData.inputterEmail}`);
} else {
Logger.log(`🧪 [TEST MODE] メール送信をスキップしました`);
Logger.log(`件名: ${subject}`);
Logger.log(`送信先: ${formData.inputterEmail}`);
}
return true;
} catch (error) {
Logger.log(`メール送信エラー: ${error.toString()}`);
return false;
}
}
/**
* 日付フォーマット処理
*/
function formatDate(dateValue) {
if (!dateValue) return '日付未設定';
try {
const date = new Date(dateValue);
if (isNaN(date.getTime())) return '日付形式エラー';
const year = date.getFullYear();
const month = ('0' + (date.getMonth() + 1)).slice(-2);
const day = ('0' + date.getDate()).slice(-2);
return `${year}/${month}/${day}`;
} catch (error) {
Logger.log(`日付フォーマットエラー: ${error.toString()}`);
return '日付処理エラー';
}
}
/**
* メール件名生成
*/
function generateEmailSubject(formData, formattedDate) {
const parts = [
CONFIG.SUBJECT_PREFIX,
formData.clientName,
formData.subjectKeyword,
formattedDate
].filter(part => part && part.toString().trim() !== '');
return parts.join('_');
}
/**
* HTMLメール本文生成
*/
function generateEmailBody(formData, formattedDate) {
return `
<!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: 'Hiragino Sans', 'Yu Gothic', sans-serif; line-height: 1.6; color: #333; }
.container { max-width: 800px; margin: 0 auto; padding: 20px; }
.header { background-color: #f8f9fa; padding: 15px; border-radius: 5px; margin-bottom: 20px; }
.content { background-color: #ffffff; padding: 20px; border: 1px solid #dee2e6; border-radius: 5px; }
.table { width: 100%; border-collapse: collapse; margin: 20px 0; }
.table th, .table td { padding: 12px; text-align: left; border: 1px solid #dee2e6; }
.table th { background-color: #f8f9fa; font-weight: bold; }
.footer { margin-top: 30px; padding-top: 20px; border-top: 1px solid #dee2e6; font-size: 0.9em; color: #6c757d; }
.highlight { background-color: #fff3cd; padding: 10px; border-radius: 3px; margin: 15px 0; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h2>📧 社外向けメール下書き(自動生成)</h2>
<p><strong>入力者:</strong> ${formData.personInCharge} (${formData.inputterEmail})</p>
<p><strong>生成日時:</strong> ${new Date().toLocaleString('ja-JP')}</p>
</div>
<div class="content">
<h3>📋 基本情報</h3>
<table class="table">
<tr>
<th style="width: 150px;">送信先</th>
<td>${formData.clientName}</td>
</tr>
<tr>
<th>送信先メール</th>
<td>${formData.recipientEmail}</td>
</tr>
<tr>
<th>件名</th>
<td>${generateEmailSubject(formData, formattedDate)}</td>
</tr>
<tr>
<th>重要日付</th>
<td>${formattedDate}</td>
</tr>
</table>
<h3>📄 詳細情報</h3>
<table class="table">
<tr>
<th style="width: 150px;">詳細情報1</th>
<td>${formData.detailInfo1 || '未設定'}</td>
</tr>
<tr>
<th>詳細情報2</th>
<td>${formData.detailInfo2 || '未設定'}</td>
</tr>
<tr>
<th>社内担当者</th>
<td>${formData.personInCharge}</td>
</tr>
</table>
<div class="highlight">
<h4>🔍 確認・編集のお願い</h4>
<p>上記の情報を確認し、必要に応じて内容を編集してから顧客・取引先に送信してください。</p>
<p>このメールは自動生成されたものです。送信前に必ず内容をご確認ください。</p>
</div>
<h3>📝 社外向けメール雛形</h3>
<div style="border: 2px solid #007bff; padding: 20px; border-radius: 5px; background-color: #f8f9ff;">
<p>件名: ${generateEmailSubject(formData, formattedDate)}</p>
<hr>
<p>${formData.clientName} ご担当者様</p>
<p>いつもお世話になっております。<br>
${CONFIG.ORGANIZATION_NAME}の${formData.personInCharge}です。</p>
<p>以下の件についてご連絡いたします。</p>
<table class="table" style="margin: 20px 0;">
<tr>
<th>対象</th>
<td>${formData.detailInfo1 || '詳細情報1'}</td>
</tr>
<tr>
<th>詳細</th>
<td>${formData.detailInfo2 || '詳細情報2'}</td>
</tr>
<tr>
<th>日付</th>
<td>${formattedDate}</td>
</tr>
</table>
<p>ご不明な点がございましたら、お気軽にお問い合わせください。</p>
<p>何卒よろしくお願いいたします。</p>
<p>---<br>
${formData.personInCharge}<br>
${CONFIG.ORGANIZATION_NAME}<br>
${formData.inputterEmail}</p>
</div>
</div>
<div class="footer">
<p>📌 このメールは ${CONFIG.ORGANIZATION_NAME} の自動生成システムによって作成されました。</p>
<p>⚠️ 送信前に必ず内容を確認・編集してください。</p>
</div>
</div>
</body>
</html>`;
}
// ===== エラー処理・通知関数 =====
/**
* エラー発生時の管理者通知
*/
function sendErrorNotification(error) {
try {
const adminEmail = 'admin@yourcompany.com'; // 管理者メールアドレス
const subject = '[システムエラー] フォーム連携メール自動生成システム';
const body = `
システムエラーが発生しました。
エラー内容:
${error.toString()}
発生時刻: ${new Date().toLocaleString('ja-JP')}
システム管理者による確認をお願いします。
`;
if (!CONFIG.TEST_MODE) {
GmailApp.sendEmail(adminEmail, subject, body);
}
Logger.log('エラー通知メールを送信しました');
} catch (notificationError) {
Logger.log(`エラー通知送信に失敗: ${notificationError.toString()}`);
}
}
// ===== セットアップ・管理関数 =====
/**
* トリガー設定関数
* 手動で一度実行してフォーム送信トリガーを設定
*/
function setupFormTrigger() {
try {
// 既存のトリガーを削除
const existingTriggers = ScriptApp.getProjectTriggers();
existingTriggers.forEach(trigger => {
if (trigger.getHandlerFunction() === 'onFormSubmit') {
ScriptApp.deleteTrigger(trigger);
}
});
// 新しいトリガーを設定
const form = FormApp.openById(CONFIG.FORM_ID);
ScriptApp.newTrigger('onFormSubmit')
.onFormSubmit()
.create();
Logger.log('✅ フォーム送信トリガーが正常に設定されました');
} catch (error) {
Logger.log(`❌ トリガー設定エラー: ${error.toString()}`);
}
}
/**
* 設定確認・テスト実行関数
*/
function testConfiguration() {
Logger.log('=== 設定確認テスト ===');
// 設定値確認
Logger.log('CONFIG設定:');
Object.keys(CONFIG).forEach(key => {
Logger.log(` ${key}: ${CONFIG[key]}`);
});
// スプレッドシート接続テスト
try {
const sheet = SpreadsheetApp.openById(CONFIG.SPREADSHEET_ID).getSheetByName(CONFIG.SHEET_NAME);
const data = sheet.getDataRange().getValues();
Logger.log(`✅ スプレッドシート接続成功 - ${data.length}行のデータを確認`);
} catch (error) {
Logger.log(`❌ スプレッドシート接続エラー: ${error.toString()}`);
}
// フォーム接続テスト
try {
const form = FormApp.openById(CONFIG.FORM_ID);
Logger.log(`✅ フォーム接続成功 - "${form.getTitle()}"`);
} catch (error) {
Logger.log(`❌ フォーム接続エラー: ${error.toString()}`);
}
Logger.log('=== テスト完了 ===');
}
/**
* 手動テスト実行(最新のフォーム回答を使用)
*/
function manualTest() {
// テストモードを一時的に有効化
const originalTestMode = CONFIG.TEST_MODE;
CONFIG.TEST_MODE = true;
Logger.log('=== 手動テスト実行 ===');
try {
onFormSubmit();
} finally {
// テストモードを元に戻す
CONFIG.TEST_MODE = originalTestMode;
}
Logger.log('=== 手動テスト完了 ===');
}
📊 運用メリット
1. 技術制約の克服
- 権限の壁突破: GAS権限制約を実用的に回避
- 汎用性確保: 誰でも下書きを入手可能な仕組み
- セキュリティ維持: 各自のアカウント権限は保護
2. 業務効率化
- 時間短縮: 手動メール作成時間を90%削減
- 作業標準化: フォーム入力のみで完了
- 現場の自立: 管理者を介さず各担当者が直接実行可能
- ボトルネック解消: 承認待ち時間の削減
3. 品質向上
- 定型化: 統一されたメールフォーマット
- 記載漏れ防止: 必須項目の自動チェック
- 属人化解消: 誰が作成しても同じ品質を実現
- 誤字脱字削減: テンプレート使用による品質安定
4. 管理・追跡性
- 履歴管理: フォーム回答の自動保存
- 入力者追跡: 誰が何を入力したかを自動記録
- 監査対応: 送信ログの自動記録
- 進捗確認: スプレッドシートでの一覧管理
⚙️ 導入・設定手順
1. 初期設定
-
Googleフォーム作成
- 必要な入力項目を設定
- 組織ドメイン限定に共有設定
- メールアドレス収集を必須に設定
-
GASスクリプト配置
- 上記コードをGoogle Apps Scriptにコピー
- CONFIG設定を環境に合わせて修正
setupFormTrigger()
を実行してトリガー設定
-
動作確認
testConfiguration()
で設定確認manualTest()
で動作テスト- 実際のフォーム送信でテスト
2. カスタマイズポイント
- CONFIG設定: ドメイン・組織名等の基本設定
- FORM_FIELDS: フォーム項目と列番号の対応
- メールテンプレート:
generateEmailBody()
関数内のHTML - 件名フォーマット:
generateEmailSubject()
関数のロジック
3. 運用開始チェックリスト
- [ ] CONFIG設定の確認・修正
- [ ] フォーム項目マッピングの確認
- [ ] トリガー設定完了
- [ ] テスト実行・動作確認
- [ ] ユーザー教育・マニュアル整備
🔍 トラブルシューティング
よくある問題と対処法
問題 | 原因 | 解決策 |
---|---|---|
メールが送信されない | 権限・ドメイン制限 | CONFIG設定見直し・ログ確認 |
フォーム送信エラー | 必須項目未入力 | バリデーション強化 |
文字化け | 文字コード問題 | UTF-8設定確認 |
重複送信 | トリガー重複実行 | トリガー設定見直し |
📈 今後の拡張可能性
機能追加案
- 承認ワークフロー: 管理者承認後送信
- テンプレート選択: 用途別メール形式
- 添付ファイル: 自動資料添付機能
- 多言語対応: 顧客言語に応じた自動翻訳
システム連携
- CRM連携: 顧客管理システムとの統合
- ERP連携: 基幹システムからのデータ取得
- チャット通知: Slack/Teams連携
- スケジュール管理: カレンダー自動登録
💡 応用・展開案
🎯 このアプローチの応用価値
**「技術制約を実用的回避策で克服」**の考え方は、以下の開発課題にも適用可能:
類似の技術制約と解決パターン
- ファイル直接保存不可 → メール添付で配信
- カレンダー直接登録不可 → ics形式でメール送信
- 他人のドライブ書込不可 → 共有リンク生成して通知
- 外部API直接呼出不可 → 中間サーバー経由で処理
📈 業務への展開案
このシステム構成は以下の業務にも応用可能:
- 見積書・提案書の自動生成(PDF化してメール送信)
- 契約更新通知の自動化(期限管理と連動)
- 定期レポート配信(データ集計結果をメール形式で配信)
- イベント案内の一斉送信(参加者リストからメール生成)
- 顧客満足度調査の自動配信(回答結果の自動集計付き)
重要:このシステムの真の価値は「技術的制約の存在を前提として、実用的な代替手段で同等の効果を実現する」設計思想にあります。
コメント
いいね
投げ銭
最新順
人気順
SHOGAKU
19 days ago
コメント
いいね
投げ銭
最新順
人気順