HTMLタグを直接手打ちしたり専用ツールのエディタに頼る運用では、画像の貼り付け位置が崩れたり、非エンジニアの担当者が原稿作成に参加しづらくなります。
本連載の第2回では、社内で誰もが使い慣れているGoogleドキュメントをそのままメルマガ原稿テンプレートとして活用する仕組みを解説します。
GoogleドキュメントのエクスポートAPIから取得したHTMLをサニタイズし、本文中に直接貼り付けられた画像を自動抽出してGmailのインライン画像(CID形式)へ変換する完全なパイプラインを構築しましょう。
【連載】GASで作る本格メルマガ配信システム(全5回)
- 第1回:全体設計とデータ構造の解説
- 【閲覧中】第2回:GoogleドキュメントのHTMLメール変換とインライン画像埋め込み
- 第3回:Web管理画面の実装(宛先フィルタ・本文プレビュー・予約UI)
- 第4回:メール送信の360秒タイムアウト対策(並列バッチ配信で370秒→約80秒に高速化)
- 第5回:予約配信の自動継続リレーと多層防御セキュリティ対策
図2-1:GoogleドキュメントからCIDインライン画像付きHTMLメールへの変換パイプライン
なぜGoogleドキュメントを本文テンプレートに選ぶのか
メール本文の管理にGoogleドキュメントを採用することには、以下のような強力な実務的メリットがあります。
| メリット | 詳細 |
|---|---|
| 直感的なビジュアル編集 | 文字サイズ、フォントカラー、リンク、表、画像の配置をWYSIWYG(見たまま)で自由に作成できます。HTMLの知識が一切不要です。 |
| チームでの共同校正・承認 | 複数人で同時にコメントを付けたり提案モードで校正作業を行えるため、メルマガ配信前の稟議や確認がスムーズに進みます。 |
| 履歴管理と復元 | Googleドキュメント標準のバージョン履歴により、「誰が・いつ・どこを変更したか」をいつでも確認・過去バージョンへ復元できます。 |
UrlFetchAppによるDocs HTMLエクスポート取得
Google Apps Scriptの「DocumentApp」サービスには、ドキュメント全体をHTML文字列として直接取得するメソッドが存在しません。
そこで、Google Driveのエクスポートエンドポイントに対して、GASの認証トークン(ScriptApp.getOAuthToken())を付与した「UrlFetchApp.fetch」を実行します。
/**
* GoogleドキュメントのIDからHTML文字列を取得する
*/
function fetchDocHtml(docId) {
var url = 'https://docs.google.com/feeds/download/documents/export/Export?id=' + docId + '&exportFormat=html';
var response = UrlFetchApp.fetch(url, {
headers: {
'Authorization': 'Bearer ' + ScriptApp.getOAuthToken()
},
muteHttpExceptions: true
});
if (response.getResponseCode() !== 200) {
throw new Error('Googleドキュメントの取得に失敗しました (HTTP ' + response.getResponseCode() + ')');
}
return response.getContentText('UTF-8');
}
この方法を使えば、ドキュメントの公開権限を「リンクを知っている全員」や「ウェブに公開」に広げる必要がなく、自社の非公開ドキュメントのまま安全にHTMLを取得できます。
ドキュメント特有の不要見出しタグを自動サニタイズする手法
GoogleドキュメントをHTML形式でエクスポートすると、タブ名(デフォルトでは「ドキュメントの概要」や「タブ 1」など)がドキュメントの最上部に巨大な大見出しタグ(h1〜h3等)として自動挿入されてしまいます。
この不要なヘッダーを正規表現で自動検知して除去します。
/**
* Googleドキュメントエクスポート時に先頭へ自動挿入されるタブ名見出しを除去
*/
function removeTabTitleHeading(html) {
if (!html) return '';
// 先頭付近に存在するタブ見出しタグパターンを除去
return html.replace(/]*>(タブ|タブ\s*\d+|概要|無題のドキュメント)<\/h[1-3]>/gi, '');
}
Google独自のリダイレクトURLを本来のリンクに復元するロジック
Googleドキュメント内のハイパーリンクは、エクスポート時に自動的に以下のようなGoogle独自のリダイレクト形式に変換されます:
https://www.google.com/url?q=https://example.com/item&sa=D&sntz=1&usg=...
このままだと、メルマガ受信者がリンクをクリックした際に「リダイレクト警告」が表示されたり、スパムフィルタに警戒される原因になります。
正規表現を用いて「q=」パラメータの値を取り出し、本来のURLへとスマートに復元します。
/**
* Google独自のリダイレクトURL(https://www.google.com/url?q=...)を本来のURLに復元する
*/
function cleanGoogleRedirectLinks(html) {
if (!html) return '';
return html.replace(/href=["']https:\/\/www\.google\.com\/url\?q=([^&"']+)&[^"']*["']/gi, function(match, originalUrl) {
return 'href="' + decodeURIComponent(originalUrl) + '"';
});
}
Base64画像を自動抽出しCIDインライン画像とBlobに変換する技術
Googleドキュメントに貼り付けた画像は、エクスポートしたHTML内では巨大なBase64データURI(<img src="data:image/png;base64,iVBORw...">)として埋め込まれます。
しかし、Gmailをはじめとする多くのメールクライアント(Outlook、Apple Mail等)は、Base64データURIの画像をセキュリティ上の理由で非表示・ブロックします。
HTMLメールで確実に画像を表示させる標準規格が、「CID(Content-ID)参照」です。
CIDインライン画像の仕組み
- HTML本文内の画像タグを
<img src="cid:inlineImage1">という一意のID参照に書き換える。 - Base64文字列からバイナリBlobオブジェクトを復元し、キー名「inlineImage1」として辞書オブジェクトに格納する。
- GmailApp.sendEmailのオプション
inlineImages: { inlineImage1: blob }に渡して送信する。
これを自動で行う中核関数「extractInlineImages」の実装は以下の通りです:
/**
* HTML内のBase64画像を抽出し、CID参照に置換したHTMLとBlob辞書を返す
*/
function extractInlineImages(html) {
var inlineImages = {};
var imgCounter = 0;
// data:image/(png|jpeg|gif|webp);base64,... にマッチ
var regex = /
]+)src=["']data:image\/([a-zA-Z]+);base64,([^"']+)["']([^>]*)>/gi;
var newHtml = html.replace(regex, function(match, prefix, ext, base64Data, suffix) {
imgCounter++;
var cid = 'inlineImage' + imgCounter;
try {
var decodedBytes = Utilities.base64Decode(base64Data);
var contentType = 'image/' + ext.toLowerCase();
var blob = Utilities.newBlob(decodedBytes, contentType, cid);
inlineImages[cid] = blob;
return '
';
} catch (e) {
Logger.log('画像変換エラー (' + cid + '): ' + e.message);
return match; // 変換失敗時は元タグを維持
}
});
return {
html: newHtml,
inlineImages: inlineImages
};
}
宛先ごとのパーソナライズ差し込み({{Recipient.Name}})の実装
メール本文に「○○様」のように宛先氏名を自動挿入するため、テンプレート内に {{Recipient.Name}} または {{Recipient.LastName}} というプレースホルダーを用意します。
/**
* 本文中の差し込みタグを宛先氏名に置換する
*/
function personalize(html, fullName) {
if (!html) return '';
if (html.indexOf('{{') === -1) return html; // タグがない場合はスキップして高速化
var name = fullName || 'お客様';
var lastName = name.split(/\s+/)[0] || name;
return html
.replace(/\{\{Recipient\.Name\}\}/g, name)
.replace(/\{\{Recipient\.LastName\}\}/g, lastName);
}
本文変換エンジンの完全なバックエンドコード
これらすべてのサニタイズ・変換ロジックを統合した「buildNewsletterContent()」関数です。
/**
* Googleドキュメントから配信用のHTML本文とインライン画像Blob辞書を統合生成する
*/
function buildNewsletterContent() {
// 1. GoogleドキュメントのHTMLを取得
var rawHtml = fetchDocHtml(TEMPLATE_DOC_ID);
// 2. タブ名見出しの除去
var cleanHtml = removeTabTitleHeading(rawHtml);
// 3. Google独自リダイレクトURLの復元
cleanHtml = cleanGoogleRedirectLinks(cleanHtml);
// 4. Base64画像をCID参照に変換 & Blob抽出
var extracted = extractInlineImages(cleanHtml);
return {
htmlTemplate: extracted.html,
inlineImages: extracted.inlineImages
};
}
まとめと次回予告
第2回のまとめ
- Googleドキュメントを活用することで、非エンジニアでも直感的にリッチなメルマガ原稿を作成・共同校正できる。
- エクスポートHTMLに含まれる不要なタブ見出しやGoogle独自リダイレクトURLを自動サニタイズ。
- 多くのメールソフトで遮断されるBase64画像を、Gmail標準の「CIDインライン画像(MIME添付)」へ完全自動変換。
- パーソナライズ差し込みタグ({{Recipient.Name}})により、宛先ごとに親しみやすいメールを作成可能。
次回(第3回)は、「GAS Webアプリで作るメルマガ管理画面 宛先フィルタ・本文プレビューと予約機能の解説」をお届けします。
HtmlServiceを活用し、宛先テーブルの動的検索・本文プレビュー・即時/予約配信の切り替えを備えたモダンなSPA風管理画面(index.html)の実装手法を詳しく解説します。
【連載】GASで作る本格メルマガ配信システム(全5回)
- 第1回:全体設計とデータ構造の解説
- 【閲覧中】第2回:GoogleドキュメントのHTMLメール変換とインライン画像埋め込み
- 第3回:Web管理画面の実装(宛先フィルタ・本文プレビュー・予約UI)
- 第4回:メール送信の360秒タイムアウト対策(並列バッチ配信で370秒→約80秒に高速化)
- 第5回:予約配信の自動継続リレーと多層防御セキュリティ対策
