結論先講
自動化寄信最常見的事故不是寄不出去,是:
- 寄錯人——測試時不小心寄給了真實客戶名單
- 重複寄——流程重跑,同一個人收到三封
- 一次爆量——迴圈寫錯,五百封同時飛出去
這三種都不是技術難題,是沒有設防線。三道防線各擋一種:
| 防線 | 擋什麼 | 實作成本 |
|---|---|---|
| 白名單 | 寄錯人 | 約十行程式 |
| 狀態過濾 | 重複寄 | 一個資料庫欄位 |
| 額度上限 | 一次爆量 | 一個計數器 |
第一道:白名單
為什麼測試階段最需要
事故幾乎都發生在測試時。 你以為在測,結果真的寄給了客戶名單。
做法
用環境變數存允許的收件者清單,寄出前檢查:
import os
# 環境變數:ALLOWED_RECIPIENTS="me@example.com,test@example.com"
ALLOWED = [
e.strip()
for e in os.getenv("ALLOWED_RECIPIENTS", "").split(",")
if e.strip()
]
# 空清單代表未設定 → 一律不寄,避免「忘了設定」變成「全部放行」
DRY_RUN = os.getenv("EMAIL_DRY_RUN", "true").lower() == "true"
def can_send(to_addr: str) -> tuple[bool, str]:
if DRY_RUN:
return False, "DRY_RUN 模式,未實際寄出"
if not ALLOWED:
return False, "白名單未設定,拒絕寄送"
if to_addr not in ALLOWED:
return False, f"收件者不在白名單:{to_addr}"
return True, "OK"
兩個設計重點
一、預設為「不寄」。 EMAIL_DRY_RUN 預設 true、空白名單一律拒絕——忘記設定時的行為應該是安全的,不是全部放行。
這一點反過來寫,就是事故的來源。
二、跳過要留紀錄。 不在白名單時記錄下來,不要靜默略過。否則你會以為寄了,實際上沒寄。
正式上線時才把白名單換成真實名單來源,並關閉 dry run。
第二道:狀態過濾
為什麼需要
自動化流程會重跑。 網路中斷、程式報錯、手動重試——都可能讓同一批名單被處理兩次。
沒有狀態標記,客戶就會收到重複的信。
做法
每筆記錄加一個寄送狀態欄位:
| 欄位 | 用途 |
|---|---|
email_status | pending / sent / failed / skipped |
sent_at | 寄出時間 |
attempt_count | 嘗試次數 |
last_error | 最後一次的錯誤訊息 |
流程變成:
- 只取
status = pending的名單 - 寄出成功後立刻更新為
sent - 失敗更新為
failed並記錄錯誤
順序很重要
先更新狀態,還是先寄信?
正確答案是:寄出成功後立刻更新,而且更新失敗要視為嚴重錯誤並停止流程。
理由:如果先標記 sent 再寄,寄送失敗時這個人就永遠不會再被寄到(漏掉)。如果寄了但沒更新成功,下次重跑會重複寄(打擾)。兩害相權,漏掉比打擾更難發現,但重複寄對客戶的傷害更直接——所以更新失敗要當成事故處理,不要讓流程繼續。
attempt_count 的作用
它防的是無限重試。設一個上限(例如 3),超過就標記 failed 不再嘗試。
否則一個永遠寄不出去的地址(例如網域已失效),會讓流程每次都卡在同一筆。
第三道:額度上限
設多少
設成你能接受的最大損失,不是服務商的上限。
| 一封寄錯的代價 | 建議上限 |
|---|---|
| 道歉一次就過去 | 20 封 |
| 會影響商譽 | 5 封 |
| 涉及敏感內容 | 1 封(等於強制逐封確認) |
上限的真正作用
不是省額度,是限制單次事故的規模。
程式出錯時,這個數字決定了你要收拾 5 封還是 500 封。
實作
BATCH_LIMIT = int(os.getenv("EMAIL_BATCH_LIMIT", "5"))
sent = 0
for row in pending_rows:
if sent >= BATCH_LIMIT:
log(f"已達單次上限 {BATCH_LIMIT} 封,剩餘 {len(pending_rows) - sent} 筆留待下一輪")
break
# ... 寄送邏輯
sent += 1
達到上限時要記錄剩餘筆數。 靜默停止會讓你以為寄完了。
台灣寄行銷信:個資法的三件事
這一節不是加分項,是義務。
一、蒐集時要明確告知
依個人資料保護法,蒐集個資時應告知當事人:蒐集目的、個資類別、利用期間與地區、對象及方式,以及當事人的權利。
實務落地:表單旁邊要有清楚的說明,不能只放一個沒有內容的勾選框。
二、行銷利用要在原告知目的範圍內
如果當初蒐集的目的是「訂單處理」,後來拿去寄行銷信,可能超出原本告知的特定目的。
實務落地:表單上就明確寫「同意接收行銷資訊」,並與其他同意項目分開勾選——不要綁在「同意服務條款」裡。
三、每封信都要有可用的退訂方式
當事人表示拒絕接受行銷時,必須立即停止利用其個資行銷。
實務落地:
- 每封信底部有明顯的退訂連結
- 退訂免費,且不需要登入
- 退訂後立刻更新狀態,下一輪流程要讀取這個狀態
退訂要接回第二道防線
退訂狀態必須成為狀態過濾的一部分:
取名單條件:status = pending AND unsubscribed = false
很多系統做了退訂按鈕,但流程沒讀那個欄位——於是使用者退訂了還是收到信。這比沒有退訂按鈕更糟,因為它是可證明的違規。
用個人信箱寄行銷信的問題
技術上可行,但有兩個實際問題:
- 每日發送量有限,超量容易被暫時鎖定
- 會影響寄件信譽——大量發送後,連你正常的工作信件都可能被判為垃圾信
行銷信建議使用專門的寄信服務,它們處理了退信管理、退訂機制與信譽維護。用個人信箱省下的錢,可能要用「重要信件寄不到客戶」來付。
上線前的檢查清單
- 白名單預設為空、dry run 預設開啟(忘記設定時是安全的)
- 被跳過的收件者有留紀錄
- 每筆名單有寄送狀態欄位,寄成功後立刻更新
- 有重試次數上限
- 單次批量有上限,達到上限時記錄剩餘筆數
- 蒐集表單有完整的告知內容
- 行銷同意與服務條款分開勾選
- 每封信有免費、免登入的退訂連結
- 取名單的查詢條件包含了退訂狀態
最後一項是最容易漏、後果也最明確的一項。
常見問題
測試階段就要做白名單嗎?感覺很麻煩。
測試階段最需要。事故幾乎都發生在測試時——你以為在測,結果真的寄給了客戶名單。做法很簡單:設一個環境變數存允許的收件者清單,程式在寄出前檢查收件者是否在清單內,不在就記錄並跳過。這段程式大約十行,但它擋掉的是最不可挽回的錯誤。
為什麼需要「狀態過濾」?
因為自動化流程會重跑。網路中斷、程式報錯、手動重試,都可能讓同一批名單被處理兩次。如果沒有在資料庫標記「這個人已經寄過了」,客戶就會收到重複的信。做法是每筆記錄加一個寄送狀態欄位,寄成功就標記,寄之前先檢查。
額度上限該設多少?
設成你能接受的最大損失,而不是服務商的上限。如果一封信寄錯的代價是道歉一次,那設 20 封;如果會影響商譽,設 5 封。上限的目的不是省額度,是限制單次事故的規模——程式出錯時,它決定了你要收拾 5 封還是 500 封。
在台灣寄行銷信,法律上要注意什麼?
三件事:蒐集個資時要明確告知用途、寄行銷信前要取得當事人同意、每封信都要提供免費且有效的退訂方式。個人資料保護法要求蒐集時就告知特定目的,而後續用於行銷需在該目的範圍內;當事人表示拒絕接受行銷時,必須立即停止。這三項不是加分項,是義務。
用自己的個人信箱寄行銷信可以嗎?
技術上可以,但不建議。個人信箱的每日發送量有限,超量容易被暫時鎖定;而且大量發送會影響該信箱的寄件信譽,之後連正常的工作信件都可能被判為垃圾信。行銷信建議使用專門的寄信服務,它們處理了退信、退訂與信譽管理。