跳到主要內容跳到頁尾
作品
  • 省時
  • 減錯

每月退信自動對回客戶,認不出的不硬猜

把退信對回客戶名單,每月產出台灣、海外與名單快照三個分頁,完成後用 Slack 通知。

2026/07
每月約 150 筆退信
案例閱讀約 5 分鐘
背景與使用技術(4 項)

每個月寄給客戶的帳單信,總有一批寄不到而被退回。退信上沒有客戶名稱,唯一的線索是寫給人看的信件主旨,而且主旨有中英兩種格式。現在每月自動撈取上個月的退信、從主旨抽出客戶名稱、比對客戶名單,產出台灣與海外兩個分頁的月報,完成後以 Slack 通知。

Google Apps Script寄信服務 API計費系統 API資料清理

誰在用

負責追帳單、請客戶更新聯絡資訊的營收營運團隊

原本怎麼做

每個月約有 150 封帳單信被退回,代表那位客戶收不到帳單,得有人找出是哪一家、去取得可用的信箱。退信不說是誰,唯一線索是主旨,同一位客戶在不同月份的寫法也不一致。台灣與海外的主旨格式不同,一條規則讀不了兩種。人工比對會漏,也會重複算,而且認錯比漏掉更糟:把 A 客戶的退信算到 B 客戶頭上。

前後對比

每月處理時間改造前20 分鐘改造後1 分鐘估計
對回客戶改造前靠主旨人工比對,會漏也會重複算改造後兩種主旨格式分開查詢;認不出就標 #N/A,不硬猜規格
月報改造前人工登入寄信服務後台撈退信,再貼進試算表改造後月初自動產出 3 個分頁:台灣退信、海外退信、客戶名單快照規格

量測:實測=有計時或有紀錄;估計=喜特先生推估;客戶自述=客戶回報;規格=設計上的固定條件;待補=尚未取得數字。

流程一眼看懂

改造前
  1. 退信郵件

  2. 人工看主旨比對

  3. 找出客戶、取得可用信箱

改造後
  1. 每天同步客戶名單

  2. 月初撈上月退信

  3. 比對客戶對照表

  4. 產出月報(3 個分頁)

  5. Slack 通知

流程示意:依本案既有做法整理,非客戶實際畫面。

做了什麼

  1. 名單與報表分離:客戶資料天天變,報表一個月才跑一次,名單在背景同步。
  2. 地區在查詢層分開:兩組條件各對應一種主旨格式,不必逐封再判斷。
  3. 認不出就標 #N/A 並統計筆數,不硬猜。
  4. 每次新建一份試算表,不覆蓋既有資料。
  5. 執行接近時間上限時主動收手,並附上已建好的試算表連結。

程式碼與實作細節收在頁面下方,需要時再展開。

你的情況像這樣嗎?

  • 你每個月要從一堆郵件裡人工找出是哪些客戶。
  • 不同地區的格式不一樣,一條規則處理不了。
  • 寧可標示「不確定」,也不能把 A 客戶算到 B 客戶頭上。

更多成果與運作條件

  • 20 分鐘 → 1 分鐘(估計)

    每月約 150 筆退信的處理時間

  • 3 個分頁

    台灣退信、海外退信與客戶名單快照,一次產出,供查核對不上的原因

  • 趕在 30 天保存期限內

    寄信服務只保留 30 天的寄送紀錄,所以固定月初執行,把當月退信留成自己的紀錄

以上為本案既有成果與運作條件;估計值依原始資料標示,其他專案需另行評估,並非通用成效保證。

退信月報的欄位

退信對回客戶名稱與 Branch ID;對不上就標 #N/A,留給同事查核。

畫面示意・範例資料,非客戶實際截圖。手機可左右滑動查看。

退信月報的欄位(示意):退信對回客戶名稱與 Branch ID;對不上就標 #N/A,留給同事查核。

挑戰:原本遇到的困難

每個月約有一百五十封帳單信會被退回。退信代表那位客戶收不到帳單,得有人找出是哪一家,去取得可用的信箱。

  • 退信不說是誰:唯一線索是主旨,同一位客戶在不同月份的寫法也不一致
  • 兩種主旨格式:台灣是中文、客戶名稱在最前;海外是英文、客戶名稱在句尾。一條規則讀不了兩種
  • 人工比對會漏,也會重複算
  • 認錯比漏掉更糟:名單會變成打給客戶的電話,把 A 客戶的退信算到 B 客戶頭上

所以這套流程要守住的是:認出來的,一定是對的。

完整解決方案與設計取捨

拆成兩支各自排程的流程。updateCustomerList() 每天把客戶系統的最新名單同步進主試算表;processMailList() 於月初執行,撈上個月的退信、比對客戶對照表,產出一份新的月報試算表,含「台灣退信」「海外退信」兩個分頁與一份客戶名單快照,完成後以 Slack 通知。

核心設計有五點:

  1. 名單與報表分離:客戶資料天天變,報表一個月才跑一次。名單在背景同步,月初那次才不會用到過期資料
  2. 地區在查詢層分開:兩組查詢條件各自對應一種主旨格式,不必對每封信再判斷
  3. 認不出就標 #N/A:客戶名稱抽取或對照失敗時留下記號並統計筆數,不硬猜
  4. 每次新建一份試算表:不覆蓋既有資料,同一個月重跑只會多一份檔案
  5. 接近時間上限就收手:執行快到六分鐘上限時主動停止,並附上已建好的試算表連結

技術與實作細節

展開完整實作說明

每個月初,營收營運團隊(負責追帳單、請客戶更新聯絡資訊的同事)需要一份能直接打電話的名單。以前要有人登進寄信服務(公司寄帳單信用的服務)後台,把上個月的退信一筆筆撈出來,讀主旨、判斷是哪一位客戶,再貼進試算表。撈資料不難,難的是「對回客戶」這一步。

退信上為什麼看不出是哪一家

退信本身只是一個事實:某封信寄不到,被退回來。退信的欄位裡沒有客戶名稱,唯一藏著客戶名稱的地方是信件主旨。

主旨是寫給人看的,而且有兩種長相。台灣客戶收到的是中文主旨,客戶名稱擺在最前面,後面接帳單期間的日期;海外客戶收到的是一句英文,客戶名稱擺在句子的最後。人看哪一種都能一眼認出客戶名稱,程式在沒有人先把「怎麼讀這行字」寫成規則之前,兩種都認不出來。

台灣與海外兩種退信主旨格式,並以綠色底框出客戶名稱所在的位置← 左右滑動看完整的圖,點圖可放大

圖:示意主旨,客戶名稱為範例。台灣的客戶名稱在第一個日期之前,海外的客戶名稱在 for 之後。

同一位客戶在不同月份、不同分支的主旨裡,寫法還常常不完全一致。所以人工比對會有兩種失誤:一種是漏掉,一種是把同一位客戶重複算成好幾家。

最難收拾的是第三種:認錯。這份名單最後會變成打給客戶的電話,把 A 客戶的退信算到 B 客戶頭上,錯誤就會一路帶進與客戶的對話。所以這套流程要守住的是「認出來的都是對的」,寧可漏判。

從主旨切出客戶名稱:兩種格式兩條規則

台灣主旨的客戶名稱是第一個日期之前的那一段,用一條 regex(正規表達式,用符號描述文字格式的比對語法)切出來。海外主旨的客戶名稱是 for 之後的全部,另外用一條規則處理。兩條規則各自都很乾淨,互換使用就全部錯,所以分開寫。兩種都抓不到時回傳空字串,寧可漏判,也不把不確定的結果寫進報表。

示意的程式如下,客戶名稱與主旨皆為範例:

Apps Script
const extractCustomerName_ = (subject) => {
if (!subject) return '';
const twMatch = subject.match(/^(.*?)\s\d{2}-/);
if (twMatch) return twMatch[1].trim();
const overseaMatch = subject.match(/Here's your invoice for (.+)$/);
if (overseaMatch) return overseaMatch[1].trim();
return '';
};

地區在查詢條件裡就分開

台灣那一組查詢,用中文月份對照表(一月到十二月)拼出主旨的搜尋字串,只撈上個月的台灣帳單退信;海外那一組直接比對 Here's your invoice 開頭。同一支腳本、兩組查詢,地區就分開了,不必對每封信再做一次判斷。

報表輸出也保留這個切分,變成「台灣退信」與「海外退信」兩個分頁。兩份名單的跟進方式不同,比對不到的客戶名稱會標成 #N/A,不會硬猜。

分支代號要去掉第一個字

對照表是用計費系統(訂閱帳務服務)的客戶名單建成的 Map:key 是公司名稱,value 是客戶 ID。客戶 ID 前面帶一個前綴字元,真正拿去跟進的分支代號是去掉首字元之後的部分,也就是 customerId.substring(1)。原本這件事靠試算表的 XLOOKUP 加 MID 公式完成,現在改在程式裡一次算好。

對不到的標成 #N/A,並統計筆數。這個數字本身就是線索:它指出下次該補哪些名稱進對照表。

月報的台灣退信分頁,第四列的 Branch ID 格顯示 #N/A 並被選取,備註寫明對照表沒有此名稱← 左右滑動看完整的圖,點圖可放大

圖:示意資料,客戶名稱皆為範例。選取的 B4 是 #N/A,代表對照表沒有這個名稱,要補進名單。

同一位客戶只跟進一次:重複分支標黃

同一位客戶在一個月內退好幾封信很常見,但營收營運團隊只需要聯絡一次。Branch ID 欄掛了一條條件式格式:用 COUNTIF 數同一個值出現幾次,超過一次就標成黃底(#FFD966)。

Google 試算表公式
fx

示意:F 欄為 Branch ID,這條規則套用在 F2 起的每一列

=COUNTIF($F:$F, F2)>1

撞到六分鐘上限,怎麼收手?

Apps Script 一次執行的上限是六分鐘。這支流程要依序撈台灣、撈海外、比對、寫入多個分頁,很容易撞到上限。每個吃重的階段開始前,都會呼叫 checkExecutionTime_ 檢查剩餘時間;不夠就主動停下,並把已建好的試算表網址附在錯誤訊息裡。中途停下,也不會留下一份沒人看得懂的半成品。

查寄信服務時,哪三個地方會踩到

寄信服務的寄送活動查詢語法有三個地方沒講就會踩到:

  • 模糊比對要自己補上 %,否則 LIKE 對不到任何東西
  • 要篩某一把 API key 寄出的信,得用 api_key_id(一串識別碼)
  • 用 status="not_delivered" 一次涵蓋 bounce 與 drop,不必分開查
LIKE 漏了百分號

當時的錯誤是 LIKE 條件沒有補上 %,查詢回傳的是空結果。查詢本身沒有報錯,看起來就像上個月沒有退信,所以一開始沒有人懷疑查詢條件。

分頁也要處理。寄信服務單頁最多 1000 筆,要以前一頁最後一筆的 last_event_time 當作 next_token 往後翻;計費系統的客戶每頁 100 筆。每翻一頁之間停 1500 毫秒(Utilities.sleep(1500)),避免撞到速率限制。所以某個月退信多到一頁裝不下時,資料仍會完整拉回來,不會悄悄停在第一頁。

歸檔留給人,重跑只多一份檔案

月報完成後,歸檔刻意留給人做。通知裡附上資料夾連結,收到通知的人把檔案拖進共用資料夾。接下來要用這份名單的本來就是同一個人,多寫一段搬檔的程式不划算。

月報也沒有防重跑機制:每次執行都建一份新試算表,不覆蓋既有資料,同一個月重跑只會多一份檔案,刪掉即可,不值得為它另外維護一份執行狀態。

完成後會發送 Slack 私訊通知,內容包含台灣與海外的筆數、總筆數,以及試算表連結與 Drive 資料夾連結。寄信服務的寄送活動只保留 30 天,過期就查不到,所以月報固定在月初跑,資料消失前把當月退信抄成自己留得住的紀錄。

上線後還沒量到的事

  • 20 分鐘降到 1 分鐘是估計值,尚未實測
  • 對照表未命中的筆數,逐月趨勢尚未整理
  • 把未命中的名稱補進名單後,下個月的命中率尚未實測

延伸閱讀

有類似的情境想討論

帶著現有流程與去識別範例,討論適合的做法。