![]()
我曾經認為電子郵件是 AI 的可怕戰場。
太雜亂、太人性化、充滿 2017 年的轉寄郵件,以及公司裡沒人知道是哪個軟體產生的 HTML。
後來我花時間閱讀信箱自動化的討論串,特別是 r/openclaw 上關於郵件流程的一篇好文,終於搞懂了模式:
如果你不再讓模型扮演郵件伺服器的角色,電子郵件其實是 AI 非常好的應用場景。
這聽起來很明顯。但許多信箱自動化仍然在做以下事情:
這不是智能。
這是昂貴的失憶症。
更好的模式很簡單:
這樣的區分讓我的信箱工作流程變得更便宜、更容易除錯,也遠比以前穩固。
OpenClaw 工作流程討論中的一則留言,比大多數文件都說得更好:
如果你的工作流程在達到 LLM 使用上限時就停止運作,那 LLM 很可能做得太多了。
這原本是在談程式碼代理人,但它完全適用於信箱自動化。
如果你的郵件管線需要仰賴模型來記住信箱狀態、去除重複事件、處理重試,或每次執行都重新檢查路由規則,那你就是建錯系統了。
模型擅長判斷。
它們不擅長當管理員。
人類體驗郵件是混亂的。
機器不是。
每封郵件到來時都帶有有用的結構:
FromToReply-ToSubject這很重要,因為許多路由決策根本不該交給 LLM。
如果發票永遠都要寄到 [email protected],GPT-5 就不該每天早上重新發現這條規則。
如果客服郵件永遠落在特定別名上,程式碼就該確定性地路由它。
如果某個討論串已經處理過,你的 worker 應該從資料庫知道,而不是從提示詞知道。
把 GPT-5、Claude Opus 4.6、Grok、Qwen 或 Llama 用在真正需要推理的部分:
所有重複性工作:
這種架構不像「AI 信箱代理人」那麼炫目。
但它也是下個月還能繼續運作的架構。
這部分徹底改變了我對信箱管線的看法。
Google 公開了 Gmail API 方法的配額成本。
history.list = 2 個配額單位messages.list = 5 個配額單位messages.get = 20 個配額單位threads.get = 40 個配額單位messages.send = 100 個配額單位這些數字不是 trivia。
它們是設計提示。
Google 正在告訴你應該這樣做:
而不是這樣:
如果你的工作流程每分鐘醒來一次,就叫前沿模型檢查所有未讀郵件,那你不是在打造自動化。
你是在打造一張持續的帳單。
對 Gmail 來說,模式很直觀:
users.watch 訂閱收件匣變更
history.list 取得變更的訊息 IDPOST https://www.googleapis.com/gmail/v1/users/me/watch
Content-Type: application/json
{
"topicName": "projects/myproject/topics/mytopic",
"labelIds": ["INBOX"],
"labelFilterBehavior": "INCLUDE"
}
進入全螢幕模式
退出全螢幕模式
import { google } from "googleapis";
const gmail = google.gmail({ version: "v1", auth });
async function processMailboxChange(startHistoryId: string) {
const history = await gmail.users.history.list({
userId: "me",
startHistoryId,
historyTypes: ["messageAdded"]
});
const messageIds = new Set<string>();
for (const item of history.data.history ?? []) {
for (const added of item.messagesAdded ?? []) {
if (added.message?.id) messageIds.add(added.message.id);
}
}
for (const id of messageIds) {
const msg = await gmail.users.messages.get({
userId: "me",
id,
format: "metadata",
metadataHeaders: ["From", "To", "Subject", "Reply-To"]
});
const headers = Object.fromEntries(
(msg.data.payload?.headers ?? []).map(h => [h.name!, h.value!])
);
const subject = headers.Subject || "";
const to = headers.To || "";
const from = headers.From || "";
if (to.includes("[email protected]")) {
await routeToAccountsPayable(id);
continue;
}
if (from.endsWith("@trustedvendor.com") && subject.includes("Invoice")) {
await routeToAccountsPayable(id);
continue;
}
await sendToLLMForClassification({ id, subject, from, headers });
}
}
進入全螢幕模式
退出全螢幕模式
重要的不是程式碼風格。
重要的是操作順序:
Microsoft Graph 有相同的架構,只是用 delta query 而不是 Gmail history。
GET https://graph.microsoft.com/v1.0/me/mailFolders/{id}/messages/delta
進入全螢幕模式
退出全螢幕模式
你需要保留:
@odata.nextLink 來分頁@odata.deltaLink 給下一次同步週期那個 token 就是你的記憶。
你的 worker 應該擁有它。
而不是模型。
async function syncOutlookFolder(deltaUrl?: string) {
const url = deltaUrl ?? "https://graph.microsoft.com/v1.0/me/mailFolders/inbox/messages/delta";
const res = await fetch(url, {
headers: { Authorization: `Bearer ${token}` }
});
const data = await res.json();
for (const msg of data.value ?? []) {
if (msg.toRecipients?.some((r: any) => r.emailAddress?.address === "[email protected]")) {
await routeToSupport(msg);
continue;
}
await classifyIfNeeded(msg);
}
if (data["@odata.nextLink"]) {
return syncOutlookFolder(data["@odata.nextLink"]);
}
if (data["@odata.deltaLink"]) {
await saveDeltaLink(data["@odata.deltaLink"]);
}
}
進入全螢幕模式
退出全螢幕模式
相同的模式。
不同的 API。
這是我最喜歡的例子,因為區分非常乾淨。
import PostalMime from "postal-mime";
export default {
async email(message, env, ctx): Promise<void> {
const subject = message.headers.get("subject") || "";
const from = message.from || "";
const to = message.to || "";
if (to === "[email protected]") {
await message.forward("[email protected]");
return;
}
if (subject.includes("Invoice") && from.endsWith("@vendor.com")) {
await message.forward("[email protected]");
return;
}
const parsed = await PostalMime.parse(message.raw);
const llmResult = await classifyEmail({
subject,
from,
to,
text: parsed.text,
html: parsed.html
});
if (llmResult.label === "support") {
await message.forward("[email protected]");
}
}
};
進入全螢幕模式
退出全螢幕模式
第一個 if 陳述式,比很多「自主代理人」展示還要做更多有用的事。
| 選項 | 最適合的用途 |
|---|---|
| Gmail API | 使用 users.watch 推送通知、history.list 增量同步,以及在呼叫 LLM 前的配額感知篩選 |
| Microsoft Graph Mail API | 資料夾層級的 delta 同步、使用 @odata.nextLink 與 @odata.deltaLink 的持久信箱狀態,以及選擇性分類 |
| Cloudflare Email Workers | 直接在入站郵件上執行自訂邏輯,包含標頭、原始 MIME,以及 forward()、reply()、setReject() 等動作
|
| OpenClaw | 自託管編排,包含 cron、webhook、工具、記憶體,以及圍繞實際模型呼叫的多代理人路由 |
| n8n / Make / Zapier | 快速的無程式碼或低程式碼編排,適合希望在加入 LLM 步驟前先有規則、重試與整合的團隊 |
這些工具都不是行銷意義上的「AI 原生」。
這正是它們有用的原因。
公平地說,電子郵件在實務上並不乾淨。
你還是得面對:
這就是為什麼純規則不夠。
但這也是為什麼純 LLM 管線是個錯誤。
正確的模式是硬邊界搭配軟性後備機制:
這種混合架構沒有「完全自主信箱代理人」那麼性感。
但這才是大人打造生產環境自動化的方式。
這就是經濟面變得惱人的地方。
如果你的工作流程不斷把同樣的路由決策送給模型,你就是在付錢讓模型一次又一次重新發現你自己的商業邏輯。
這是偽裝成 AI 問題的糟糕架構問題。
對那些整天在 n8n、Make、Zapier、OpenClaw 或自訂 Node worker 中執行代理人的團隊來說,按 token 計費會讓情況快速惡化。
你最後看著使用量儀表板,而不是在出貨。
這正是我喜歡確定性編排優先、LLM 呼叫其次模式的原因,也說明了為什麼固定費率的 API 存取對自動化密集的工作負載如此吸引人。
使用 Standard Compute,你可以保留工作流程已經習慣的 OpenAI 相容 API 形態,卻不用把每次分類、重試和長討論串都當成計費事件。它是現有 SDK 與 HTTP 用戶端的即插即用替代方案,背後是 GPT-5.4、Claude Opus 4.6 與 Grok 4.20 的動態路由,價格則是可預測的月費。
當你的自動化 24/7 持續執行,而重點是要同時停止照顧信箱和 token 計量器時,這一點非常重要。
如果我從頭開始打造信箱自動化,這是我信任的堆疊:
這就是整個轉變。
別再讓模型當迴圈。
讓模型回答問題。
在那之後,一切都變得更便宜。
一切也都變得更好。