技術ブログ

【GAS】請求書・納品書を自動作成&メール送信!コピペOK

/ 初心者向け

【GAS】請求書・納品書を自動作成&メール送信!コピペOK

GASで請求書・納品書を自動生成&メール送信!もう手作業は卒業

「請求書や納品書の作成、毎回時間がかかって大変…」

「メールで送るのも手間がかかる…」

そんな悩みを抱えていませんか?

この記事では、Google Apps Script(GAS)を使って、Googleスプレッドシートに蓄積されたデータから請求書や納品書を自動で生成し、顧客へメール送信する具体的な方法を、コピペできるコード付きで解説します。

この記事で解決できること

  • 請求書・納品書作成の工数削減: 手作業でのデータ入力や転記作業をなくし、大幅な時間短縮を実現します。
  • メール送信の自動化: 生成した書類を顧客ごとに自動でメール送信できます。
  • ヒューマンエラーの削減: データ入力ミスや書類作成ミスを減らし、正確性を向上させます。
  • GoogleスプレッドシートとGASの連携: 身近なツールで高度な業務自動化を体験できます。

準備するもの

1. Googleアカウント: GmailとGoogleドライブがあればOKです。

2. Googleスプレッドシート: 請求書・納品書の元データ(顧客情報、商品情報、数量、単価など)を入力するシート。

3. Googleドキュメント(テンプレート): 請求書・納品書のテンプレートとなるドキュメント。

実装手順

ステップ1:請求書・納品書テンプレートの準備

Googleドキュメントで、請求書または納品書のテンプレートを作成します。テンプレートには、後ほどGASで差し込むための「プレースホルダー」を設定します。

プレースホルダーの例:

  • {{会社名}}
  • {{氏名}}
  • {{請求日}}
  • {{請求番号}}
  • {{商品名}}
  • {{数量}}
  • {{単価}}
  • {{金額}}
  • {{合計金額}}

テンプレートができたら、ファイル名を分かりやすいもの(例:「請求書テンプレート」)にして、Googleドライブに保存してください。

ステップ2:元データ用スプレッドシートの準備

Googleスプレッドシートで、請求書・納品書の元データとなるシートを作成します。各列には、テンプレートのプレースホルダーに対応する項目を設定します。

スプレッドシートの例:

| 顧客ID | 会社名 | 氏名 | 請求日 | 請求番号 | 商品名 | 数量 | 単価 | 金額 | 状況 | メール送信フラグ |

|---|---|---|---|---|---|---|---|---|---|---|

| 001 | 株式会社〇〇 | 田中 一郎 | 2023/10/27 | INV-001 | 商品A | 2 | 1000 | 2000 | 未送信 | |

| 002 | △△商事 | 佐藤 花子 | 2023/10/27 | INV-002 | 商品B | 1 | 5000 | 5000 | 未送信 | |

  • 状況 列は、作成済みか未作成かなどのステータス管理に便利です。
  • メール送信フラグ 列は、メール送信済みかどうかを管理するために使用します。

ステップ3:GASコードの記述

「拡張機能」>「Apps Script」からスクリプトエディタを開き、以下のコードを記述します。

function generateAndSendInvoices() {
  // ★★★ 設定項目 ★★★
  const TEMPLATE_DOC_ID = 'YOUR_TEMPLATE_DOCUMENT_ID'; // 請求書テンプレートのドキュメントID
  const SHEET_NAME = '請求データ'; // 元データが入力されているシート名
  const EMAIL_SUBJECT_PREFIX = '【請求書】'; // メール件名のプレフィックス
  const SENDER_NAME = 'あなたの会社名'; // 送信者名(Gmailの表示名)
  const ATTACHMENT_FOLDER_NAME = '作成済み請求書'; // 作成した請求書を保存するフォルダ名

  // ★★★ テンプレートのプレースホルダーとスプレッドシートの列の対応 ★★★
  // ドキュメントのプレースホルダー名: スプレッドシートの列名
  const PLACEHOLDER_MAP = {
    '{{会社名}}': '会社名',
    '{{氏名}}': '氏名',
    '{{請求日}}': '請求日',
    '{{請求番号}}': '請求番号',
    // 商品明細はループで処理するため、ここでは例として合計金額のみ
    '{{合計金額}}': '金額' // ここはスプレッドシートの集計結果の列名に合わせる
  };
  // 商品明細のテンプレート部分(例:1行分の商品明細)
  // ※ ここはテンプレートの構造に合わせて調整が必要です。
  //   例:<table><tbody><tr><td>{{商品名}}</td><td>{{数量}}</td><td>{{単価}}</td><td>{{金額}}</td></tr></tbody></table>
  //   GAS側では、この<tbody>...</tbody>の部分を動的に生成・置換します。
  const ITEM_TEMPLATE_ROW = '<tr><td>{{商品名}}</td><td>{{数量}}</td><td>{{単価}}</td><td>{{金額}}</td></tr>';

  // ★★★ スプレッドシートの列インデックス(0から始まる) ★★★
  const COL_EMAIL_SENT_FLAG = 10; // メール送信フラグの列インデックス
  const COL_STATUS = 9; // 状況の列インデックス
  const COL_TOTAL_AMOUNT = 8; // 合計金額の列インデックス (Placeholder Mapの'{{合計金額}}'に対応)

  // --------------------------------------------------------------------

  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName(SHEET_NAME);
  const dataRange = sheet.getDataRange();
  const values = dataRange.getValues();

  const headerRow = values[0]; // ヘッダー行
  const dataRows = values.slice(1); // データ行

  // テンプレートドキュメントを取得
  const templateDoc = DocumentApp.openById(TEMPLATE_DOC_ID);
  const templateBody = templateDoc.getBody();

  // 作成済み請求書を保存するフォルダを取得または作成
  let saveFolder = DriveApp.getFoldersByName(ATTACHMENT_FOLDER_NAME).next();
  if (!saveFolder) {
    saveFolder = DriveApp.createFolder(ATTACHMENT_FOLDER_NAME);
  }

  dataRows.forEach((row, index) => {
    const rowIndex = index + 1; // シート上の行番号(1から始まる)
    const emailSentFlag = row[COL_EMAIL_SENT_FLAG];
    const status = row[COL_STATUS];

    // メール送信済み、または「作成済」の場合はスキップ
    if (emailSentFlag === '送信済' || status === '作成済') {
      return;
    }

    const invoiceNumber = row[headerRow.indexOf('請求番号')]; // 請求番号
    const recipientName = row[headerRow.indexOf('氏名')];
    const recipientEmail = row[headerRow.indexOf('メールアドレス')]; // ★★★ メールアドレスの列名に合わせてください ★★★

    // PDFとして保存するためのドキュメントコピーを作成
    const newDoc = templateDoc.makeCopy(invoiceNumber + '_請求書');
    const newDocBody = newDoc.getBody();

    // プレースホルダーをデータで置換
    for (const placeholder in PLACEHOLDER_MAP) {
      const columnName = PLACEHOLDER_MAP[placeholder];
      const columnIndex = headerRow.indexOf(columnName);
      if (columnIndex !== -1) {
        newDocBody.replaceText(placeholder, row[columnIndex]);
      }
    }

    // 商品明細を動的に生成・置換
    // ★★★ 商品明細のデータがあるシートの範囲を正しく指定してください ★★★
    // 例:商品明細が別シートにある場合や、1行に複数商品がカンマ区切りなどで入っている場合など、
    //     実際のデータ構造に合わせてこの部分を実装してください。
    //     ここでは、簡略化のため、1行に1つの商品明細が入っていると仮定します。
    //     より複雑な明細構造の場合は、別途商品明細用のループ処理が必要です。
    //     例:templateBody.replaceText('{{商品明細テーブル}}', generatedItemRows);

    // 例: templateBody.replaceText('{{商品名}}', row[headerRow.indexOf('商品名')]);
    //     templateBody.replaceText('{{数量}}', row[headerRow.indexOf('数量')]);
    //     templateBody.replaceText('{{単価}}', row[headerRow.indexOf('単価')]);
    //     templateBody.replaceText('{{金額}}', row[headerRow.indexOf('金額')]);

    // TODO: 実際のデータ構造に合わせて、商品明細の生成ロジックを実装してください。
    // 例:商品明細が別シートにある場合
    // const itemSheet = ss.getSheetByName('商品明細');
    // const itemData = itemSheet.getDataRange().getValues();
    // const itemHeader = itemData[0];
    // let generatedItemRows = '';
    // itemData.slice(1).forEach(itemRow => {
    //   if (itemRow[itemHeader.indexOf('請求番号')] === invoiceNumber) {
    //     let itemRowHtml = ITEM_TEMPLATE_ROW;
    //     itemRowHtml = itemRowHtml.replace('{{商品名}}', itemRow[itemHeader.indexOf('商品名')]);
    //     itemRowHtml = itemRowHtml.replace('{{数量}}', itemRow[itemHeader.indexOf('数量')]);
    //     itemRowHtml = itemRowHtml.replace('{{単価}}', itemRow[itemHeader.indexOf('単価')]);
    //     itemRowHtml = itemRowHtml.replace('{{金額}}', itemRow[itemHeader.indexOf('金額')]);
    //     generatedItemRows += itemRowHtml;
    //   }
    // });
    // newDocBody.replaceText('<tbody>{{商品明細}}</tbody>', '<tbody>' + generatedItemRows + '</tbody>'); // {{商品明細}}を置換するプレースホルダーをテンプレートに用意

    // ドキュメントを保存
    newDoc.saveAndClose();

    // PDFに変換
    const pdfBlob = newDoc.getAs('application/pdf');
    pdfBlob.setName(invoiceNumber + '_請求書.pdf');

    // 作成したPDFをフォルダに保存
    const uploadedFile = saveFolder.createFile(pdfBlob);

    // メール送信
    try {
      const emailBody = `
${recipientName}様

いつもお世話になっております。

${EMAIL_SUBJECT_PREFIX}${invoiceNumber}の請求書をお送りいたします。

ご確認ください。

よろしくお願いいたします。

${SENDER_NAME}
`;
      GmailApp.sendEmail(recipientEmail, `${EMAIL_SUBJECT_PREFIX}${invoiceNumber}`, emailBody, {
        attachments: [pdfBlob],
        name: SENDER_NAME
      });

      // スプレッドシートの状況を更新
      sheet.getRange(rowIndex + 1, COL_STATUS + 1).setValue('作成済');
      sheet.getRange(rowIndex + 1, COL_EMAIL_SENT_FLAG + 1).setValue('送信済');
      Logger.log(`${invoiceNumber} の請求書を ${recipientEmail} へ送信しました。`);

    } catch (e) {
      Logger.log(`${invoiceNumber} のメール送信中にエラーが発生しました: ${e.toString()}`);
      sheet.getRange(rowIndex + 1, COL_STATUS + 1).setValue('メール送信エラー');
    }

    // 作成したドキュメントを削除 (任意)
    // DriveApp.getFileById(newDoc.getId()).setTrashed(true);
  });

  Logger.log('請求書自動生成・送信処理が完了しました。');
}

【重要】コード内の設定項目について

  • YOUR_TEMPLATE_DOCUMENT_ID: 作成した請求書テンプレートのドキュメントIDを貼り付けます。ドキュメントIDは、GoogleドキュメントのURLの /d//edit の間の文字列です。

例: https://docs.google.com/document/d/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/editxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx の部分。

  • SHEET_NAME: 元データが入力されているスプレッドシートのシート名を正確に指定します。
  • EMAIL_SUBJECT_PREFIX: メール件名の冒頭につく文字列です。必要に応じて変更してください。
  • SENDER_NAME: Gmailの送信者名として表示される名前です。
  • ATTACHMENT_FOLDER_NAME: 作成されたPDF請求書を保存するGoogleドライブ上のフォルダ名です。存在しない場合は自動で作成されます。
  • PLACEHOLDER_MAP: ここが最も重要です。左側にGoogleドキュメントテンプレートで設定したプレースホルダー(例: {{会社名}})、右側にスプレッドシートのヘッダー行にある対応する列名を指定します。スプレッドシートの列名と完全に一致させてください。
  • ITEM_TEMPLATE_ROW: 商品明細の1行分のHTMLテンプレートです。もしテンプレートのHTML構造が異なる場合は、ここを修正してください。
  • COL_EMAIL_SENT_FLAG, COL_STATUS, COL_TOTAL_AMOUNT: それぞれメール送信フラグ、状況、合計金額の列のインデックス(0から始まる番号)を指定します。スプレッドシートの列構成に合わせて調整してください。
  • 商品明細の処理: 上記コードでは、商品明細の生成部分がコメントアウトされており、TODO となっています。これは、商品明細のデータ構造がユーザーによって大きく異なるためです。実際のデータ構造(別シートにあるか、1つのセルにカンマ区切りで入っているかなど)に合わせて、この部分を実装する必要があります。

ステップ4:トリガーの設定

GASコードを自動実行させるために、トリガーを設定します。スクリプトエディタの左側にある時計アイコン(トリガー)をクリックし、「トリガーを追加」ボタンを押します。

  • 実行する関数を選択: generateAndSendInvoices
  • 実行するデプロイを選択: Head
  • イベントのソースを選択: 時間駆動
  • 時間間隔の種類を選択: 毎日(または必要に応じた間隔)
  • 時間帯を選択: 実行したい時間帯

必要に応じて、他の条件(例: 特定のシートが更新されたら実行など)も設定できます。

ステップ5:権限の承認

初めてスクリプトを実行する際、Googleアカウントの権限を承認する必要があります。画面の指示に従って承認してください。

よくあるエラーと対処法

  • Document not found または ID is invalid:
  • TEMPLATE_DOC_ID が正しく設定されていない可能性があります。GoogleドキュメントのURLからIDをコピーし、正確に貼り付けてください。
  • Cannot retrieve the name of array (または似たエラー):
  • PLACEHOLDER_MAP で指定しているスプレッドシートの列名が、実際のヘッダー行と一致していない可能性が高いです。大文字・小文字、全角・半角を確認してください。
  • headerRow.indexOf(columnName)-1 を返している場合、その列名がヘッダーに存在しないことになります。
  • メールが送信されない、またはエラーメッセージが出る:
  • Gmailの送信上限に達していないか確認してください。GASからのメール送信には1日の上限があります。
  • recipientEmail が正しく設定されているか、スプレッドシートの該当セルに有効なメールアドレスが入力されているか確認してください。
  • Gmailの「迷惑メール」フォルダに誤って振り分けられていないか確認してください。
  • Gmailの「設定」>「すべての設定を表示」>「メールの作成と返信」>「署名」に内容が複数ある場合、GASからのメール送信で問題が発生することがあります。署名が1つであることを確認してください。
  • PDFが作成されない、または文字化けする:
  • テンプレートのフォントが、GASでサポートされていない特殊なフォントの可能性があります。標準的なフォント(Arial, Times New Romanなど)を使用してください。
  • テンプレート内の画像や複雑なレイアウトが原因で、PDF変換に失敗することがあります。シンプルなレイアウトを心がけてください。
  • PERMISSION_DENIED:
  • スクリプトがGoogleドキュメントやGmailにアクセスするための権限が不足しています。スクリプト実行時に表示される権限承認画面で、必要な権限をすべて許可してください。

FAQ

Q1. 請求書だけでなく、納品書も同時に自動作成できますか?

A1. はい、可能です。納品書用のテンプレートも別途用意し、同様の手順でGASコードを記述すれば、請求書と納品書の両方を自動生成・送信できます。必要に応じて、generateAndSendInvoices 関数をコピー&ペーストして、テンプレートIDやメール件名などを変更した新しい関数を作成してください。

Q2. 複数商品(明細)がある場合の処理はどうすればいいですか?

A2. 上記コードのTODO部分で触れていますが、商品明細のデータ構造に合わせて実装する必要があります。一般的には、商品明細が別シートに顧客IDや請求番号と紐づけて管理されている場合、そのシートから該当する明細を取得し、HTMLテーブル形式で生成してテンプレートの特定の箇所(例: <tbody>...</tbody> の部分)に埋め込む形になります。より詳細な実装については、Google Apps Script のドキュメントやコミュニティでの質問をご参照ください。

Q3. PDFではなく、Excel(xlsx)形式で作成・送信することはできますか?

A3. GASで直接Excel形式のファイルを作成・送信するのは、PDFに比べて少し複雑になります。Googleスプレッドシートのデータを直接Excelファイルとしてダウンロードし、それを添付する方法や、外部ライブラリ(例: SheetsAPI を使ってスプレッドシートをExcel形式でエクスポート)を利用する方法が考えられます。PDF形式での自動化が最も一般的で手軽な方法です。

Q4. 毎月決まった日に自動送信したいのですが、どうすればいいですか?

A4. ステップ4のトリガー設定で、「時間駆動」を選び、「毎日」または「毎週」「毎月」といった頻度と、実行したい時間帯を設定することで実現できます。

まとめ

Google Apps Scriptを活用すれば、日々の請求書・納品書作成からメール送信までの一連の作業を劇的に効率化できます。

今回ご紹介したコードはあくまで一例ですが、これをベースに皆様の業務に合わせてカスタマイズすることで、さらなる生産性向上に繋がるはずです。

もし、これらの自動化ツール導入や、さらに複雑な業務プロセスのGASによる自動化にご興味があれば、ぜひ弊社のGASコンサルティングサービスにご相談ください。専門家が貴社の課題に合わせた最適なソリューションをご提案いたします。

まずは、この記事のコードをコピペして、ご自身の環境で試してみてください!

---

GAS自動化の導入相談

請求書PDF作成、Gmail自動送信、Slack通知、スプレッドシート連携などを業務に合わせて実装できます。

請求書自動生成ツールを見る / SNS自動投稿ツールを見る