[總管交辦] 新零件:send_email(CF 原生寄信 binding)——工作流目前無法寄信 #75

Open
opened 2026-07-27 07:48:59 +00:00 by Leo · 1 comment
Owner

缺口

Arcrun 工作流目前無法寄信——沒有對應零件。想寄信只能繞道(自建 worker 直接用 binding),
那條路不在零件體系內、工作流用不到。

為什麼值得做(leo 2026-07-27)

「CF 提供了很好的功能,如果你去 Linode 自架,這些都要另外很複雜的搞,但 CF 都內建了。
只是,如果把 Arcrun 當作 CF 的好用外皮,那我們要讓它知道『這太好用了』,
不然在 n8n 就要去外接 gmail 了。」

差異化很實在

  • n8n 那條路:外接 Gmail/SMTP → 管 OAuth token → 撞每日配額 → token 過期要重授權
  • CF 這條路:一個 binding 就完事,SMTP/DKIM/Return-Path/退信處理平台全包

自架要搞一整套(MTA、DKIM 簽章、SPF、反向 DNS、退信迴圈),CF 內建。
這是 Arcrun 包在 CF 上的價值主張的一個具體體現。

技術形狀

平台能力=send_email binding(非 SMTP、非 http_request):

import { EmailMessage } from "cloudflare:email";
const msg = new EmailMessage(from, to, rawMime);
await env.SEND_EMAIL.send(msg);
send_email = [{ name = "SEND_EMAIL" }]

注意它不是外部 API——無 endpoint/無 token/無 auth,
所以 recipe 那套(URL+認證+參數映射)在這裡沒有東西可映射,
零件本身要直接吃 binding,設計上與 http_request 家族不同。

🔴 實作必踩的雷(2026-07-27 花了數小時才定位,請務必內建)

手組 MIME 必須包含 Message-IDDate,否則 CF 判 spam 拒收:

  • 走 CF REST APIPOST /accounts/{id}/email/sending/send)→ 服務端會自動補這兩個表頭
  • send_email binding必須自己給,缺了就被拒

症狀極具誤導性:Activity Log 的 Subject/Sender 兩欄全空(CF 解析不出表頭),
send() 不會拋錯wrangler tail 看到 exceptions:[]),
呼叫端因此以為寄成功。實例排除清單:DNS/SPF/DKIM 齊備、DMARC 非主因、
Suppressions 空、收件方無關(假地址同樣被拒)。

正解:

`Message-ID: <${crypto.randomUUID()}@${domain}>`,
`Date: ${new Date().toUTCString()}`,

另 MIME 須以 CRLF 結尾(RFC 5322 §2.1)。

零件應該把這些封裝掉,讓工作流作者不必知道 RFC 5322。

參考實作

arcrun-raglanding/worker.jssendCodeEmail()(已修正版,端到端實測寄達)。


[總管交辦] 來源:2026-07-27 leo 走小白安裝旅程時的討論

## 缺口 Arcrun 工作流**目前無法寄信**——沒有對應零件。想寄信只能繞道(自建 worker 直接用 binding), 那條路不在零件體系內、工作流用不到。 ## 為什麼值得做(leo 2026-07-27) > 「CF 提供了很好的功能,如果你去 Linode 自架,這些都要另外很複雜的搞,但 CF 都內建了。 > 只是,如果把 Arcrun 當作 CF 的好用外皮,那我們要讓它知道『這太好用了』, > 不然在 n8n 就要去外接 gmail 了。」 **差異化很實在**: - **n8n 那條路**:外接 Gmail/SMTP → 管 OAuth token → 撞每日配額 → token 過期要重授權 - **CF 這條路**:一個 binding 就完事,SMTP/DKIM/Return-Path/退信處理**平台全包** 自架要搞一整套(MTA、DKIM 簽章、SPF、反向 DNS、退信迴圈),CF 內建。 這是 Arcrun 包在 CF 上的價值主張的一個具體體現。 ## 技術形狀 平台能力=**`send_email` binding**(非 SMTP、非 http_request): ```js import { EmailMessage } from "cloudflare:email"; const msg = new EmailMessage(from, to, rawMime); await env.SEND_EMAIL.send(msg); ``` ```toml send_email = [{ name = "SEND_EMAIL" }] ``` **注意它不是外部 API**——無 endpoint/無 token/無 auth, 所以 recipe 那套(URL+認證+參數映射)在這裡沒有東西可映射, 零件本身要直接吃 binding,設計上與 http_request 家族不同。 ## 🔴 實作必踩的雷(2026-07-27 花了數小時才定位,請務必內建) 手組 MIME **必須包含 `Message-ID` 與 `Date`**,否則 CF 判 spam 拒收: - **走 CF REST API**(`POST /accounts/{id}/email/sending/send`)→ 服務端**會自動補**這兩個表頭 - **走 `send_email` binding** → **必須自己給**,缺了就被拒 症狀極具誤導性:Activity Log 的 **Subject/Sender 兩欄全空**(CF 解析不出表頭), 而 `send()` **不會拋錯**(`wrangler tail` 看到 `exceptions:[]`), 呼叫端因此以為寄成功。實例排除清單:DNS/SPF/DKIM 齊備、DMARC 非主因、 Suppressions 空、收件方無關(假地址同樣被拒)。 正解: ```js `Message-ID: <${crypto.randomUUID()}@${domain}>`, `Date: ${new Date().toUTCString()}`, ``` 另 MIME 須以 CRLF 結尾(RFC 5322 §2.1)。 **零件應該把這些封裝掉,讓工作流作者不必知道 RFC 5322。** ## 參考實作 `arcrun-rag` 的 `landing/worker.js` → `sendCodeEmail()`(已修正版,端到端實測寄達)。 --- [總管交辦] 來源:2026-07-27 leo 走小白安裝旅程時的討論
Leo added the
s
backlog
label 2026-08-09 13:13:10 +00:00
Author
Owner

🔴 這張票現在擋著一個已經上 prod 的繞道(2026-08-11)

2026-08-11 出貨的「忘記密碼代寄」走的正是本票所說的繞道
自建 worker 直接用 send_email binding,174 行手寫 JS,不在零件體系內

leo 當天看 leo21c 的工作流頁時當場抓到:「沒有這條工作流,你是怎麼達成的?

⇒ 本票不再只是「差異化很實在」的加分項,它是一條已經在生產環境上的技術債的前置
⇒ 對應票:Leo/arcrun-rag#75(已掛依賴指向本票)。

## 🔴 這張票現在擋著一個**已經上 prod 的繞道**(2026-08-11) 2026-08-11 出貨的「忘記密碼代寄」**走的正是本票所說的繞道**: 自建 worker 直接用 `send_email` binding,**174 行手寫 JS,不在零件體系內**。 leo 當天看 leo21c 的工作流頁時當場抓到:「**沒有這條工作流,你是怎麼達成的?**」 ⇒ 本票不再只是「差異化很實在」的加分項,**它是一條已經在生產環境上的技術債的前置**。 ⇒ 對應票:`Leo/arcrun-rag#75`(已掛依賴指向本票)。
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Leo/Arcrun#75