如果你剛好負責內部通知、活動邀請或會員電子報,sendEmail() 會是最稱職的小幫手。
本文用白話的方式把重點拆開:基礎語法、可用參數、HTML 與純文字並存、附檔與內嵌圖、cc/bcc、replyTo 與別名發信,還會示範如何把名單放在 Google 試算表裡跑批次。
中段整理 Gmail 與 Apps Script 的配額限制,末段直接列出常見錯誤與避雷清單,照著做就不怕被「寄太多」或「寄不出去」絆住。希望本篇文章能幫助到需要的您。
目錄
{tocify} $title={目錄}
sendEmail() 是什麼?能做什麼?
在 GAS 中你可以用兩種服務寄信:
GmailApp.sendEmail(...):
能存取郵件、草稿、標籤、別名等 Gmail 能力;寄信時支援 HTML、附件、內嵌圖片、別名發信、cc/bcc、replyTo、noReply 等參數。主旨長度上限 250 字元。
MailApp.sendEmail(...):
只專注「寄信」,不讀信箱;同樣支援 HTML、附件、cc/bcc、replyTo,也有 getRemainingDailyQuota() 可查寄件額度。
小提醒:兩者都受 Apps Script 配額 與 Gmail 郵件大小/收件者數 限制。
最簡單上手:純文字與 HTML 郵件
純文字三行搞定
function sendPlain() {
GmailApp.sendEmail('to@example.com', '測試主旨', '這是一封純文字測試信。');
}
HTML 內容(含純文字備援)
function sendHtml() {
const html = '<h2>您好</h2><p>這是 <b>HTML</b> 測試。</p>';
GmailApp.sendEmail(
'to@example.com',
'HTML 測試',
'如果看不到 HTML,會顯示這段純文字',
{ htmlBody: html }
);
}
支援 htmlBody,收件端可顯示 HTML;同時保留第三個參數作為純文字備援。
常用進階參數:附件、內嵌圖、cc/bcc、別名、回覆位址
夾帶附件(例如把雲端硬碟檔案轉成 PDF)
function sendWithAttachment() {
const file = DriveApp.getFileById('你的檔案ID');
GmailApp.sendEmail(
'to@example.com',
'附檔測試',
'詳見附件',
{ attachments: [file.getAs(MimeType.PDF)], name: '自動化寄信' }
);
}
內嵌圖片(用 CID)
function sendInlineImage() {
const logo = UrlFetchApp.fetch('要放的圖片路徑').getBlob().setName('logo');
const html = '<p>這是內嵌圖:</p><img src="要放的圖片路徑" />';
GmailApp.sendEmail(
'to@example.com',
'內嵌圖片測試',
'純文字備援',
{ htmlBody: html, inlineImages: { brandLogo: logo } }
);
}
inlineImages 是「鍵名 → Blob」的映射;在 htmlBody 用 <img src="cid:鍵名"> 引用。
多收件人、cc/bcc
function sendWithCcBcc() {
GmailApp.sendEmail(
'a@example.com,b@example.com',
'群發測試',
'正文',
{ cc: 'manager@example.com', bcc: 'audit@example.com' }
);
}
to/cc/bcc 皆以逗號分隔清單。每封信的收件者上限 50 位(含 to/cc/bcc 總和)。
指定「回覆至」與「No-Reply」(僅 Workspace 可用)
function sendWithReplyToAndNoReply() {
GmailApp.sendEmail(
'to@example.com',
'回覆位址測試',
'正文',
{ replyTo: 'support@yourdomain.com', noReply: true } // noReply 僅 Workspace
);
}
noReply: true 僅 Google Workspace 帳號可用;個人 Gmail 不支援。
用「別名」發信(From)
function sendFromAlias() {
const aliases = GmailApp.getAliases(); // 先取出帳號的可用別名
if (aliases.length === 0) throw new Error('沒有已設定的別名');
GmailApp.sendEmail(
'to@example.com',
'別名發信測試',
'正文',
{ from: aliases[0] }
);
}
要能用 from 指定別名,必須先在 Gmail 的「以…寄出郵件(Send mail as)」裡新增並驗證該地址。新增後 getAliases() 才會列出,from 參數才能使用。設定路徑:Gmail → 設定 → 帳戶與匯入 → 以…寄出郵件。
草稿工作流:先建立 Draft、確認後再寄
想先在 Gmail 裡預覽再送出,可以先建草稿,再由程式或人工送出。
function draftThenSend() {
const draft = GmailApp.createDraft(
'to@example.com',
'先當草稿',
'正文',
{ htmlBody: '<p>先預覽</p>' }
);
// 程式直接寄出草稿:
const message = draft.send(); // 回傳 GmailMessage
Logger.log('已送出時間:' + message.getDate());
}
createDraft() 會回傳 GmailDraft,可再 send() 送出,也可 update() 修改內容。
實務情境範例
1. 用試算表名單群發(含 HTML、附件、追蹤錯誤)
function bulkSendFromSheet() {
const sh = SpreadsheetApp.getActive().getSheetByName('mailing');
const rows = sh.getDataRange().getValues(); // 第一列標題:email, name, pdfFileId
const header = rows.shift();
const COL = Object.fromEntries(header.map((h,i)=>[h,i]));
rows.forEach((r, idx) => {
try {
const to = String(r[COL.email]||'').trim();
if (!to) throw new Error('缺少 email');
const name = r[COL.name] || '';
const fileId = r[COL.pdfFileId];
const attachments = fileId ? [DriveApp.getFileById(fileId).getAs(MimeType.PDF)] : [];
const html = `<p>${name} 您好:</p><p>這是通知內容。</p>`;
GmailApp.sendEmail(
to,
`【通知】Hello, ${name}`,
'純文字備援內容',
{ htmlBody: html, attachments }
);
sh.getRange(idx+2, header.length+1).setValue('OK'); // 寫入狀態
} catch (e) {
sh.getRange(idx+2, header.length+1).setValue('ERR: ' + e.message);
Utilities.sleep(500); // 避免過於頻繁呼叫
}
});
}
2. 表單回覆自動寄信(時間/表單觸發)
在編輯器:觸發條件 → 新增觸發器,綁定 onFormSubmit 或時間驅動觸發,每分鐘/每小時批次寄送。
觸發器有每日執行總時長限制,避免一次寄太多。
必讀限制與配額(避免一寄就被擋)
1. 每日收件者數(per day):個人 Gmail 約 100 位/天;Workspace 約 1,500 位/天(同網域可到 2,000)。配額以「收件者數」計(to+cc+bcc)。
2. 每封收件者上限(per message):50 位。
3. 附件總大小:25 MB/封;附件數量 250 個/封;本文大小上限(非附件)視帳號等級而定(常見 200–400KB)。
4. 主旨長度:上限 250 字元。
5. 配額重置:以首次請求後 24 小時為窗口滾動重置(非整點零時)。詳細以官方頁面為準。
想知道今天還能寄多少?用 MailApp.getRemainingDailyQuota() 查詢剩餘收件者數。
常見錯誤與雷點
1. Exception: Service invoked too many times / 超出配額
原因:短時間或當天寄太多。
對策:批次處理加 Utilities.sleep() 節流;拆批次;使用時間觸發器分散;在寄送前檢查 getRemainingDailyQuota()。
2. 收件者過多或格式錯誤
症狀:50 人以上被擋、逗號/空白錯誤。
對策:每封 ≤ 50 位(含 to/cc/bcc),用逗號分隔,寄多批。
3. 附件過大或過多
症狀:25MB 以上失敗、或 250 個附件超限。
對策:壓縮、轉 PDF、改分享連結,或分封寄送。
4. HTML 信換行跑版
原因:把 \n 當成 HTML 換行。
對策:在 htmlBody 使用 <br> 或 <p>;保留第三參數純文字備援。(HTML 行為屬常識,但請留意 htmlBody/純文字差異)
5. 內嵌圖片顯示不出來
原因:inlineImages 的鍵名與 <img src="cid:鍵名"> 不一致,或 Blob 沒名稱。
對策:確認鍵名一致、Blob 設好名稱,並在 htmlBody 以 cid: 方式引用。
6. 指定 from 但被忽略或報錯
原因:別名未在 Gmail「以…寄出郵件」中新增/驗證成功;或該地址不在 getAliases() 列表。
對策:先到 Gmail 設定新增並驗證,再用 GmailApp.getAliases() 檢查;options.from 只能用該清單中的地址。
7. noReply: true 無效
原因:個人 Gmail 帳號不支援。
對策:需要 Workspace 帳號才可用 noReply。
8. 一次寄太快導致暫時性錯誤
對策:加退避(exponential backoff),例如每 10 封 sleep 1–2 秒;遇錯誤重試限定次數。
9. 想先讓同事確認內容再寄
對策:先 createDraft(),讓同事在 Gmail 裡看草稿再 send() 寄出。
GmailApp vs MailApp:該用哪一個?
1. 要用 別名發信/存取草稿/讀取郵件、標籤 → GmailApp。
2. 只想「單純寄信 + 查剩餘額度」→ MailApp 較精簡,並提供 getRemainingDailyQuota()。
3. 兩者同樣受每日收件者數與每封收件者上限/大小等限制。
權限與發信身分
1. GmailApp 需要 https://mail.google.com/ 範圍授權(首次執行會跳出授權畫面)。
2. 使用別名(from)前,請先在 Gmail 「以…寄出郵件(Send mail as)」 新增與驗證;否則無法用該地址發信。
問題集
Q1:可以同時寄給很多人嗎?
可以,但每封最多 50 位(含 to/cc/bcc),且每天有收件者總量配額(個人 100、Workspace 1,500 起)。群發請分批。
Q2:如何知道今天還能寄多少?
用 MailApp.getRemainingDailyQuota() 取得剩餘收件者數。
Q3:為什麼我指定 from 沒生效?
因為那個地址未在 Gmail 設定中新增為別名或尚未驗證成功;需先設定,並確認 GmailApp.getAliases() 有列出它。
Q4:想先預覽信件再寄?
用 GmailApp.createDraft() 建草稿;確認 OK 後可在程式裡 draft.send() 寄出。
