Gmail 草稿夾,常常像抽屜裡那疊便條紙:寫到一半、改了又改,最後誰也不敢動。久了,收件框乾淨,草稿卻越堆越多。
這篇文章專講 deleteDraft()——怎麼安全刪、怎麼批次刪、刪之前要不要先「試跑」、刪錯了能不能救。我會用白話把重點講清楚:什麼情境該刪、有哪些權限和限制、常見誤會(例如把草稿當郵件在搜)、以及實務上可直接上線的流程。
你會看到可複製的範例、可避免的地雷、與好維護的策略。讀完,你能把草稿夾整理成「看得懂、留得住、刪得掉」的狀態,不再怕按下刪除鍵。希望本篇文章能夠幫助到需要的您。
目錄
{tocify} $title={目錄}
什麼是 deleteDraft()?會發生什麼事?
對象:deleteDraft() 針對的是「草稿」(GmailDraft),不是一般已送出或收件匣裡的郵件。
效果:呼叫後,該草稿即不存在;如果你再呼叫 draft.getMessage() 會丟出例外(因為草稿已刪)。
跟「寄出草稿」的關係:當你把草稿寄出時,草稿會自動被刪除、並產生一封帶有 SENT 系統標籤的新郵件;因此「寄出後又手動刪草稿」通常是多此一舉。
權限需求:使用 Apps Script 內建 Gmail 服務操作草稿,需要 https://mail.google.com/ 這類高權限 scope。第一次執行會跳權限同意畫面。
小提醒:Gmail API 的 users.drafts.delete 明確寫著「立即且永久刪除」,不是移到垃圾桶;這也是為什麼本文後面會教你「乾跑(dry-run)」保護機制。
什麼情境該用 deleteDraft()?
行銷寄信前的多版本草稿累積、要清掉過期版本
自動化流程(例如:表單填寫 → 先產草稿 → 審核不通過則刪掉)
內容模板管理:先用 createDraft() 產生模板草稿、每次寄出後改版就刪舊留新(或改用 update() 直接覆寫)。
權限與執行限制
Simple Trigger 不能碰 Gmail
onOpen(e) / onEdit(e) 這類簡單觸發器無法存取需要授權的服務(包含 Gmail)。要用請改成「安裝式觸發器」或手動執行。
公司/學校可能封鎖 Apps Script
若你的 Workspace 管理員把 Apps Script 關閉,所有腳本與觸發器都會被擋下。
配額(Quotas)別忽略
像「Email 讀/寫(不含發送)」有每日上限、UrlFetch 也有每日次數上限;批次處理時要分批、加上延遲與重試。
快速開始:刪除第一封草稿(Apps Script 內建 Gmail 服務)
function deleteFirstDraft() {
const drafts = GmailApp.getDrafts();
if (!drafts.length) {
console.log('沒有草稿可刪');
return;
}
const draft = drafts[0];
const subject = draft.getMessage().getSubject(); // 先記下供日誌
draft.deleteDraft(); // 直接刪除
console.log(`已刪除草稿:${subject}`);
}
這正是官方文件示範的精神:GmailApp.getDrafts()[0] → draft.deleteDraft(),刪後再取 getMessage() 會丟例外。
依 ID 精準刪除(適合流程串接)
function deleteDraftById(draftId) {
const draft = GmailApp.getDraft(draftId);
const subject = draft.getMessage().getSubject();
draft.deleteDraft();
console.log(`已刪除 ID=${draftId} 的草稿:${subject}`);
}
getId() 可把 ID 存進資料表或屬性,後續用 getDraft(id) 取回再刪;這個做法在長流程最穩。
批次刪除:以條件過濾草稿
GmailApp 沒有提供「查詢草稿」的搜尋字串 API(search() 是找 Thread,不是 Draft);實務上改為拉出全部草稿,再用 GmailMessage 的欄位自行過濾,例如主旨、收件者、建立時間等。
依主旨關鍵字
function deleteDraftsBySubjectKeyword(keyword, { dryRun = true } = {}) {
const drafts = GmailApp.getDrafts();
let hit = 0;
for (const d of drafts) {
const msg = d.getMessage();
if (msg.getSubject().includes(keyword)) {
hit++;
if (dryRun) {
console.log(`[試運轉] 將刪:${msg.getSubject()}`);
} else {
d.deleteDraft();
console.log(`[已刪除] ${msg.getSubject()}`);
}
}
}
console.log(`符合 ${hit} 封;dryRun=${dryRun}`);
}
依收件者網域
function deleteDraftsByRecipientDomain(domain, { dryRun = true } = {}) {
const drafts = GmailApp.getDrafts();
for (const d of drafts) {
const to = d.getMessage().getTo() || '';
const cc = d.getMessage().getCc() || '';
const bcc = d.getMessage().getBcc() || '';
const buckets = [to, cc, bcc].join(',').toLowerCase();
if (buckets.includes(`@${domain.toLowerCase()}`)) {
if (dryRun) {
console.log(`[試運轉] 將刪(${domain}):${d.getMessage().getSubject()}`);
} else {
d.deleteDraft();
console.log(`[已刪除] ${d.getMessage().getSubject()}`);
}
}
}
}
依建立時間(早於 N 天)
function deleteDraftsOlderThan(days, { dryRun = true } = {}) {
const limit = Date.now() - days * 24 * 60 * 60 * 1000;
const drafts = GmailApp.getDrafts();
for (const d of drafts) {
const msg = d.getMessage();
if (msg.getDate().getTime() < limit) {
if (dryRun) {
console.log(`[試運轉] 將刪(${days} 天前):${msg.getSubject()} / ${msg.getDate()}`);
} else {
d.deleteDraft();
console.log(`[已刪除] ${msg.getSubject()} / ${msg.getDate()}`);
}
}
}
}
錯誤處理:常見例外與解法
| 情況 | 現象 | 可能原因 | 建議處置 |
|---|---|---|---|
draft.getMessage() 在
deleteDraft() 之後呼叫
|
例外 | 草稿已刪除,物件失效 | 刪前記錄必要欄位(主旨、ID),刪後不要再取內容。 |
| 定時觸發器找不到草稿 | 「Oops – can’t find Gmail draft」 | 草稿在排程間被人工刪或寄出;ID 失效 | 捕捉例外、略過不存在的草稿並記錄 ID。 |
在 onEdit 等 simple trigger 執行 Gmail |
無權限、動作被擋 | Simple trigger 不可使用需要授權的服務 | 改用安裝式觸發器;首次授權後再跑。 |
API 呼叫回 403 policyEnforced |
組織政策擋下 | 管理員或帳戶防護策略限制 | 與 Workspace 管理員確認政策或改用核准的流程。 |
| 大量刪除時出現限速/配額錯誤 | rateLimitExceeded 等 |
連續呼叫過快或超出上限 | 分批與延遲、自動重試、拉長排程。 |
常見問題及雷點
1. 把「草稿」當「郵件」:
GmailApp.search() 搜的是 Thread,不保證涵蓋草稿;請用 getDrafts() 再自行過濾。
2. 以為「刪草稿=丟垃圾桶」:
API 的 users.drafts.delete 是永久刪;內建服務刪除亦不可逆,務必先 Dry-run。
3. 寄出後又手動刪草稿:寄出草稿會自動移除草稿實體,沒必要再刪一次。
4. 用 Simple Trigger 操作 Gmail:一定失敗。改安裝式觸發器。
5. 忽略組織政策:公司可能禁用 Apps Script 或 API,導致腳本完全不跑。
6. 批次沒節流:高頻呼叫易撞配額;要分批 + Sleep + 重試。
實務範例集
A. 清掉「測試用」草稿(主旨含 [TEST])
function purgeTestDrafts() {
deleteDraftsBySubjectKeyword('[TEST]', { dryRun: false });
}
B. 清除對特定網域的未送出草稿(例如合作已結束)
function purgePartnerDrafts() {
deleteDraftsByRecipientDomain('old-partner.com', { dryRun: false });
}
C. 定期清理 30 天前的草稿(安裝式觸發器每日執行)
function dailyPurgeOldDrafts() {
deleteDraftsOlderThan(30, { dryRun: false });
}
觸發器請用「安裝式觸發器」,Simple Trigger 無法動到 Gmail。
D. 先列清單給主管確認
function reviewPurgeTargets() {
deleteDraftsBySubjectKeyword('舊合約', { dryRun: true });
deleteDraftsOlderThan(60, { dryRun: true });
}
E. API 精準刪(已知 Draft ID)
function removeByApi() {
const ids = ['r-123abc', 'r-456xyz']; // 例
ids.forEach(apiDeleteDraft);
}
問題集
Q1:我能幫草稿加標籤再用標籤刪嗎?
不行。草稿訊息只能有 DRAFT 系統標籤;要分群,請用主旨前綴或收件者條件。
Q2:有「回收桶」可以救回誤刪草稿嗎?
Gmail API 的草稿刪除是永久的;內建服務刪除後也不可復原。上線前務必先全量乾跑。
Q3:為什麼排程偶爾說找不到草稿?
通常是草稿在排程間被人工刪掉或被寄出(寄出會自刪)。請在程式中接受「ID 不存在」這種例外。
