[{"data":1,"prerenderedAt":8476},["ShallowReactive",2],{"seo-locale-availability:\u002Fblog\u002Fcloudflare-smtp-email-sending-guide":3,"post:zh-CN:\u002Fblog\u002Fcloudflare-smtp-email-sending-guide":7,"surroundings:zh-CN":1683},[4,5,6],"zh-CN","en","ja",{"post":8,"contentLocale":4,"isFallback":1663},{"id":9,"title":10,"accent":11,"body":12,"description":1662,"draft":1663,"experience":1664,"extension":1665,"featured":539,"image":1666,"meta":1672,"navigation":539,"path":1673,"publishedAt":1674,"readingTime":690,"seo":1675,"stem":1676,"tags":1677,"updatedAt":1664,"video":1664,"__hash__":1682},"posts_zh\u002Fblog\u002Fcloudflare-smtp-email-sending-guide.md","Cloudflare SMTP 发信实战：从控制台配置到 Nodemailer 上线","cyan",{"type":13,"value":14,"toc":1644},"minimark",[15,24,27,37,59,63,156,159,175,182,185,190,193,203,206,210,216,223,229,232,252,255,275,279,282,289,295,298,331,334,409,421,424,456,460,463,491,494,511,514,803,809,818,821,1031,1051,1055,1058,1065,1157,1160,1163,1220,1223,1227,1230,1309,1319,1322,1330,1366,1369,1372,1375,1378,1457,1460,1466,1472,1476,1481,1501,1504,1507,1602,1605,1608,1611,1614,1640],[16,17,18,19,23],"p",{},"Cloudflare Email Service 在 2026 年加入了认证 SMTP。对于已经使用 Nodemailer、Python ",[20,21,22],"code",{},"smtplib","、PHPMailer，或者某个只会配置 SMTP 的后台系统来说，这意味着不必为了更换邮件服务商重写整个发送层。",[16,25,26],{},"本文是一篇独立的 SMTP 教程。目标不是重复介绍邮件接收、Email Routing 或 Email Workers，而是完成一件具体的事：",[28,29,30],"blockquote",{},[16,31,32,33,36],{},"使用 ",[20,34,35],{},"smtp.mx.cloudflare.net:465","，通过 Cloudflare API Token 完成 SMTPS 认证，并从自己的域名发送一封事务邮件。",[16,38,39,40,44,45,52,53,58],{},"本文依据的是 ",[41,42,43],"strong",{},"2026 年 7 月 31 日","可见的 Cloudflare Email Service Beta 能力。Beta 阶段的配额和控制台界面仍可能变化，上线前应重新核对",[46,47,51],"a",{"href":48,"rel":49},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Femail-service\u002Fget-started\u002Fsend-emails\u002F",[50],"nofollow","官方发送指南","与",[46,54,57],{"href":55,"rel":56},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Femail-service\u002Fapi\u002Fsend-emails\u002Fsmtp\u002F",[50],"SMTP API 参考","。",[60,61,62],"h2",{"id":62},"一分钟配置表",[64,65,66,79],"table",{},[67,68,69],"thead",{},[70,71,72,76],"tr",{},[73,74,75],"th",{},"项目",[73,77,78],{},"值",[80,81,82,93,103,111,122,134,148],"tbody",{},[70,83,84,88],{},[85,86,87],"td",{},"SMTP 主机",[85,89,90],{},[20,91,92],{},"smtp.mx.cloudflare.net",[70,94,95,98],{},[85,96,97],{},"端口",[85,99,100],{},[20,101,102],{},"465",[70,104,105,108],{},[85,106,107],{},"加密",[85,109,110],{},"隐式 TLS，也就是 SMTPS",[70,112,113,116],{},[85,114,115],{},"用户名",[85,117,118,119],{},"固定为字面量 ",[20,120,121],{},"api_token",[70,123,124,127],{},[85,125,126],{},"密码",[85,128,129,130,133],{},"具有 ",[20,131,132],{},"Email Sending: Edit"," 权限的 Cloudflare API Token",[70,135,136,139],{},[85,137,138],{},"认证方式",[85,140,141,144,145],{},[20,142,143],{},"AUTH PLAIN"," 或 ",[20,146,147],{},"AUTH LOGIN",[70,149,150,153],{},[85,151,152],{},"发件人",[85,154,155],{},"已在 Email Sending 中启用的域名地址",[16,157,158],{},"这里有三个不能互换的概念：",[160,161,162,169,172],"ol",{},[163,164,165,166,168],"li",{},"用户名不是 Cloudflare 邮箱，也不是 Account ID，而是固定字符串 ",[20,167,121],{},"；",[163,170,171],{},"密码不是登录 Cloudflare 的密码，而是专用 API Token；",[163,173,174],{},"端口 465 使用连接建立时立即开始的 TLS，不能照搬端口 587 的 STARTTLS 配置。",[16,176,177,178,58],{},"Cloudflare 当前不提供 587 STARTTLS，也不把 25 端口作为出站中继；25 端口用于入站 Email Routing。详细差异可查阅",[46,179,181],{"href":55,"rel":180},[50],"官方 SMTP 文档",[60,183,184],{"id":184},"开始前需要什么",[186,187,189],"h3",{"id":188},"_1-域名使用-cloudflare-dns","1. 域名使用 Cloudflare DNS",[16,191,192],{},"Email Sending 要求发件域由 Cloudflare DNS 托管。进入：",[194,195,201],"pre",{"className":196,"code":198,"language":199,"meta":200},[197],"language-text","Cloudflare Dashboard\n→ Compute\n→ Email Service\n→ Email Sending\n","text","",[20,202,198],{"__ignoreMap":200},[16,204,205],{},"选择域名并完成启用。Cloudflare 会配置发件验证、DKIM 与退信处理需要的 DNS 记录。控制台显示为“已配置”以后，再继续调 SMTP；否则认证成功也可能因为发件域未获授权而被拒绝。",[186,207,209],{"id":208},"_2-创建最小权限的-api-token","2. 创建最小权限的 API Token",[16,211,212,213,215],{},"进入域名详情的“连接”页面，切换到 SMTP。页面会提示创建具有 ",[20,214,132],{}," 权限的 API Token。",[16,217,218],{},[219,220],"img",{"alt":221,"src":222},"Cloudflare Email Service 的 SMTP 连接页，显示主机、端口、用户名与 cURL 示例","\u002Fmedia\u002Fposts\u002Fcloudflare-smtp-email-sending-guide\u002Fdashboard-smtp-curl-v1.webp",[16,224,225],{},[226,227,228],"em",{},"截图采集于 2026 年 7 月 31 日。画面只包含 Cloudflare 提供的配置值和环境变量占位符；真实 Token 没有被创建、显示或写入文章。",[16,230,231],{},"建议使用账户级 Token，并尽可能限制：",[233,234,235,238,243,246,249],"ul",{},[163,236,237],{},"只授权需要发信的 Cloudflare 账户；",[163,239,240,241,168],{},"只授予 ",[20,242,132],{},[163,244,245],{},"为 Token 设置合理有效期；",[163,247,248],{},"如果部署出口 IP 稳定，再增加客户端 IP 过滤；",[163,250,251],{},"每个应用使用独立 Token，方便吊销和审计。",[16,253,254],{},"Token 只在创建时完整显示一次。将它写入服务端 Secret 管理或运行环境变量，绝不能：",[233,256,257,260,266,269,272],{},[163,258,259],{},"写进 Git 仓库、Markdown 或构建日志；",[163,261,262,263,168],{},"放进 ",[20,264,265],{},"NUXT_PUBLIC_*",[163,267,268],{},"交给浏览器 JavaScript；",[163,270,271],{},"在异常日志中打印 SMTP 配置对象；",[163,273,274],{},"与其他项目共享同一个长期有效 Token。",[60,276,278],{"id":277},"先用-curl-验证链路","先用 cURL 验证链路",[16,280,281],{},"在接入框架以前，先用 cURL 发送最小测试邮件，可以把“Cloudflare 配置问题”和“应用代码问题”拆开排查。",[16,283,284,285,288],{},"创建 ",[20,286,287],{},"mail.txt","：",[194,290,293],{"className":291,"code":292,"language":199,"meta":200},[197],"From: Jackie Moon \u003Cnoreply@sparkles-editor.com>\nTo: recipient@example.com\nSubject: Cloudflare SMTP test\nMIME-Version: 1.0\nContent-Type: text\u002Fplain; charset=UTF-8\n\nHello,\n\nThis message was delivered through Cloudflare Email Service SMTP.\n",[20,294,292],{"__ignoreMap":200},[16,296,297],{},"把 Token 放入当前终端进程的环境变量。不要把真实值写进 Shell 历史：",[194,299,303],{"className":300,"code":301,"language":302,"meta":200,"style":200},"language-bash shiki shiki-themes github-dark github-dark","read -s CF_EMAIL_SMTP_TOKEN\nexport CF_EMAIL_SMTP_TOKEN\n","bash",[20,304,305,321],{"__ignoreMap":200},[306,307,310,314,317],"span",{"class":308,"line":309},"line",1,[306,311,313],{"class":312},"s8ozJ","read",[306,315,316],{"class":312}," -s",[306,318,320],{"class":319},"s4wv1"," CF_EMAIL_SMTP_TOKEN\n",[306,322,324,328],{"class":308,"line":323},2,[306,325,327],{"class":326},"sOPea","export",[306,329,320],{"class":330},"suv1-",[16,332,333],{},"然后发送：",[194,335,337],{"className":300,"code":336,"language":302,"meta":200,"style":200},"curl --url \"smtps:\u002F\u002Fsmtp.mx.cloudflare.net:465\" \\\n  --ssl-reqd \\\n  --user \"api_token:${CF_EMAIL_SMTP_TOKEN}\" \\\n  --mail-from \"noreply@sparkles-editor.com\" \\\n  --mail-rcpt \"recipient@example.com\" \\\n  --upload-file mail.txt\n",[20,338,339,354,361,378,389,400],{"__ignoreMap":200},[306,340,341,345,348,351],{"class":308,"line":309},[306,342,344],{"class":343},"sFR8T","curl",[306,346,347],{"class":312}," --url",[306,349,350],{"class":319}," \"smtps:\u002F\u002Fsmtp.mx.cloudflare.net:465\"",[306,352,353],{"class":312}," \\\n",[306,355,356,359],{"class":308,"line":323},[306,357,358],{"class":312},"  --ssl-reqd",[306,360,353],{"class":312},[306,362,364,367,370,373,376],{"class":308,"line":363},3,[306,365,366],{"class":312},"  --user",[306,368,369],{"class":319}," \"api_token:${",[306,371,372],{"class":330},"CF_EMAIL_SMTP_TOKEN",[306,374,375],{"class":319},"}\"",[306,377,353],{"class":312},[306,379,381,384,387],{"class":308,"line":380},4,[306,382,383],{"class":312},"  --mail-from",[306,385,386],{"class":319}," \"noreply@sparkles-editor.com\"",[306,388,353],{"class":312},[306,390,392,395,398],{"class":308,"line":391},5,[306,393,394],{"class":312},"  --mail-rcpt",[306,396,397],{"class":319}," \"recipient@example.com\"",[306,399,353],{"class":312},[306,401,403,406],{"class":308,"line":402},6,[306,404,405],{"class":312},"  --upload-file",[306,407,408],{"class":319}," mail.txt\n",[16,410,411,412,415,416,58],{},"成功时，SMTP 会返回 ",[20,413,414],{},"250","。响应中包含的 Message-ID 可以用来关联 Email Service 控制台里的发送日志。完整格式可对照",[46,417,420],{"href":418,"rel":419},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Femail-service\u002Fexamples\u002Femail-sending\u002Fsmtp\u002F",[50],"Cloudflare 的 cURL 示例",[16,422,423],{},"如果这里失败，先不要开始改 Nodemailer：",[233,425,426,434,440,446],{},[163,427,428,431,432,168],{},[20,429,430],{},"535","：Token 无效、过期或缺少 ",[20,433,132],{},[163,435,436,439],{},[20,437,438],{},"550","：发件地址或发件域没有被允许；",[163,441,442,445],{},[20,443,444],{},"552","：邮件超过大小限制；",[163,447,448,451,452,455],{},[20,449,450],{},"421"," \u002F ",[20,453,454],{},"451","：临时故障或限流，等待后重试。",[60,457,459],{"id":458},"在-nodejs-中使用-nodemailer","在 Node.js 中使用 Nodemailer",[16,461,462],{},"下面的方案适用于 Node.js、Docker、VPS、传统 Serverless Function，或运行在 Node 兼容环境的 Nitro 服务端。安装依赖：",[194,464,466],{"className":300,"code":465,"language":302,"meta":200,"style":200},"pnpm add nodemailer\npnpm add -D @types\u002Fnodemailer\n",[20,467,468,479],{"__ignoreMap":200},[306,469,470,473,476],{"class":308,"line":309},[306,471,472],{"class":343},"pnpm",[306,474,475],{"class":319}," add",[306,477,478],{"class":319}," nodemailer\n",[306,480,481,483,485,488],{"class":308,"line":323},[306,482,472],{"class":343},[306,484,475],{"class":319},[306,486,487],{"class":312}," -D",[306,489,490],{"class":319}," @types\u002Fnodemailer\n",[16,492,493],{},"服务端环境变量：",[194,495,499],{"className":496,"code":497,"language":498,"meta":200,"style":200},"language-dotenv shiki shiki-themes github-dark github-dark","CF_EMAIL_SMTP_TOKEN=replace-with-a-server-side-secret\nSMTP_FROM=\"Jackie Moon \u003Cnoreply@sparkles-editor.com>\"\n","dotenv",[20,500,501,506],{"__ignoreMap":200},[306,502,503],{"class":308,"line":309},[306,504,505],{},"CF_EMAIL_SMTP_TOKEN=replace-with-a-server-side-secret\n",[306,507,508],{"class":308,"line":323},[306,509,510],{},"SMTP_FROM=\"Jackie Moon \u003Cnoreply@sparkles-editor.com>\"\n",[16,512,513],{},"创建 Transport：",[194,515,519],{"className":516,"code":517,"language":518,"meta":200,"style":200},"language-ts shiki shiki-themes github-dark github-dark","import nodemailer from 'nodemailer'\n\nconst transporter = nodemailer.createTransport({\n  host: 'smtp.mx.cloudflare.net',\n  port: 465,\n  secure: true,\n  auth: {\n    user: 'api_token',\n    pass: process.env.CF_EMAIL_SMTP_TOKEN,\n  },\n  connectionTimeout: 10_000,\n  greetingTimeout: 10_000,\n  socketTimeout: 30_000,\n})\n\nconst info = await transporter.sendMail({\n  from: process.env.SMTP_FROM,\n  to: 'recipient@example.com',\n  subject: 'Welcome to Signal Log',\n  text: 'Your account is ready.',\n  html: '\u003Cp>Your account is ready.\u003C\u002Fp>',\n})\n\nconsole.info('Email accepted', {\n  messageId: info.messageId,\n  accepted: info.accepted.length,\n  rejected: info.rejected.length,\n})\n","ts",[20,520,521,535,541,561,572,581,591,597,608,618,624,635,645,656,662,667,688,699,710,721,732,743,748,753,771,777,788,798],{"__ignoreMap":200},[306,522,523,526,529,532],{"class":308,"line":309},[306,524,525],{"class":326},"import",[306,527,528],{"class":330}," nodemailer ",[306,530,531],{"class":326},"from",[306,533,534],{"class":319}," 'nodemailer'\n",[306,536,537],{"class":308,"line":323},[306,538,540],{"emptyLinePlaceholder":539},true,"\n",[306,542,543,546,549,552,555,558],{"class":308,"line":363},[306,544,545],{"class":326},"const",[306,547,548],{"class":312}," transporter",[306,550,551],{"class":326}," =",[306,553,554],{"class":330}," nodemailer.",[306,556,557],{"class":343},"createTransport",[306,559,560],{"class":330},"({\n",[306,562,563,566,569],{"class":308,"line":380},[306,564,565],{"class":330},"  host: ",[306,567,568],{"class":319},"'smtp.mx.cloudflare.net'",[306,570,571],{"class":330},",\n",[306,573,574,577,579],{"class":308,"line":391},[306,575,576],{"class":330},"  port: ",[306,578,102],{"class":312},[306,580,571],{"class":330},[306,582,583,586,589],{"class":308,"line":402},[306,584,585],{"class":330},"  secure: ",[306,587,588],{"class":312},"true",[306,590,571],{"class":330},[306,592,594],{"class":308,"line":593},7,[306,595,596],{"class":330},"  auth: {\n",[306,598,600,603,606],{"class":308,"line":599},8,[306,601,602],{"class":330},"    user: ",[306,604,605],{"class":319},"'api_token'",[306,607,571],{"class":330},[306,609,611,614,616],{"class":308,"line":610},9,[306,612,613],{"class":330},"    pass: process.env.",[306,615,372],{"class":312},[306,617,571],{"class":330},[306,619,621],{"class":308,"line":620},10,[306,622,623],{"class":330},"  },\n",[306,625,627,630,633],{"class":308,"line":626},11,[306,628,629],{"class":330},"  connectionTimeout: ",[306,631,632],{"class":312},"10_000",[306,634,571],{"class":330},[306,636,638,641,643],{"class":308,"line":637},12,[306,639,640],{"class":330},"  greetingTimeout: ",[306,642,632],{"class":312},[306,644,571],{"class":330},[306,646,648,651,654],{"class":308,"line":647},13,[306,649,650],{"class":330},"  socketTimeout: ",[306,652,653],{"class":312},"30_000",[306,655,571],{"class":330},[306,657,659],{"class":308,"line":658},14,[306,660,661],{"class":330},"})\n",[306,663,665],{"class":308,"line":664},15,[306,666,540],{"emptyLinePlaceholder":539},[306,668,670,672,675,677,680,683,686],{"class":308,"line":669},16,[306,671,545],{"class":326},[306,673,674],{"class":312}," info",[306,676,551],{"class":326},[306,678,679],{"class":326}," await",[306,681,682],{"class":330}," transporter.",[306,684,685],{"class":343},"sendMail",[306,687,560],{"class":330},[306,689,691,694,697],{"class":308,"line":690},17,[306,692,693],{"class":330},"  from: process.env.",[306,695,696],{"class":312},"SMTP_FROM",[306,698,571],{"class":330},[306,700,702,705,708],{"class":308,"line":701},18,[306,703,704],{"class":330},"  to: ",[306,706,707],{"class":319},"'recipient@example.com'",[306,709,571],{"class":330},[306,711,713,716,719],{"class":308,"line":712},19,[306,714,715],{"class":330},"  subject: ",[306,717,718],{"class":319},"'Welcome to Signal Log'",[306,720,571],{"class":330},[306,722,724,727,730],{"class":308,"line":723},20,[306,725,726],{"class":330},"  text: ",[306,728,729],{"class":319},"'Your account is ready.'",[306,731,571],{"class":330},[306,733,735,738,741],{"class":308,"line":734},21,[306,736,737],{"class":330},"  html: ",[306,739,740],{"class":319},"'\u003Cp>Your account is ready.\u003C\u002Fp>'",[306,742,571],{"class":330},[306,744,746],{"class":308,"line":745},22,[306,747,661],{"class":330},[306,749,751],{"class":308,"line":750},23,[306,752,540],{"emptyLinePlaceholder":539},[306,754,756,759,762,765,768],{"class":308,"line":755},24,[306,757,758],{"class":330},"console.",[306,760,761],{"class":343},"info",[306,763,764],{"class":330},"(",[306,766,767],{"class":319},"'Email accepted'",[306,769,770],{"class":330},", {\n",[306,772,774],{"class":308,"line":773},25,[306,775,776],{"class":330},"  messageId: info.messageId,\n",[306,778,780,783,786],{"class":308,"line":779},26,[306,781,782],{"class":330},"  accepted: info.accepted.",[306,784,785],{"class":312},"length",[306,787,571],{"class":330},[306,789,791,794,796],{"class":308,"line":790},27,[306,792,793],{"class":330},"  rejected: info.rejected.",[306,795,785],{"class":312},[306,797,571],{"class":330},[306,799,801],{"class":308,"line":800},28,[306,802,661],{"class":330},[16,804,805],{},[219,806],{"alt":807,"src":808},"Cloudflare 控制台中的 Nodemailer 示例，密码从环境变量读取","\u002Fmedia\u002Fposts\u002Fcloudflare-smtp-email-sending-guide\u002Fdashboard-smtp-node-v1.webp",[16,810,811],{},[226,812,813,814,817],{},"控制台示例使用 ",[20,815,816],{},"process.env.CLOUDFLARE_API_TOKEN","。实际项目可以使用更明确的变量名，但必须保证它只存在于服务端。",[16,819,820],{},"生产代码还应补上输入边界：",[194,822,824],{"className":516,"code":823,"language":518,"meta":200,"style":200},"const allowedFrom = new Set([\n  'noreply@sparkles-editor.com',\n  'support@sparkles-editor.com',\n])\n\nfunction assertEmailEnvelope(from: string, to: string) {\n  if (!allowedFrom.has(from)) {\n    throw new Error('Sender is not allowed')\n  }\n\n  if (!\u002F^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$\u002F.test(to)) {\n    throw new Error('Recipient is invalid')\n  }\n}\n",[20,825,826,844,851,858,863,867,899,919,937,942,946,1007,1022,1026],{"__ignoreMap":200},[306,827,828,830,833,835,838,841],{"class":308,"line":309},[306,829,545],{"class":326},[306,831,832],{"class":312}," allowedFrom",[306,834,551],{"class":326},[306,836,837],{"class":326}," new",[306,839,840],{"class":343}," Set",[306,842,843],{"class":330},"([\n",[306,845,846,849],{"class":308,"line":323},[306,847,848],{"class":319},"  'noreply@sparkles-editor.com'",[306,850,571],{"class":330},[306,852,853,856],{"class":308,"line":363},[306,854,855],{"class":319},"  'support@sparkles-editor.com'",[306,857,571],{"class":330},[306,859,860],{"class":308,"line":380},[306,861,862],{"class":330},"])\n",[306,864,865],{"class":308,"line":391},[306,866,540],{"emptyLinePlaceholder":539},[306,868,869,872,875,877,880,883,886,889,892,894,896],{"class":308,"line":402},[306,870,871],{"class":326},"function",[306,873,874],{"class":343}," assertEmailEnvelope",[306,876,764],{"class":330},[306,878,531],{"class":879},"s-3mD",[306,881,882],{"class":326},":",[306,884,885],{"class":312}," string",[306,887,888],{"class":330},", ",[306,890,891],{"class":879},"to",[306,893,882],{"class":326},[306,895,885],{"class":312},[306,897,898],{"class":330},") {\n",[306,900,901,904,907,910,913,916],{"class":308,"line":593},[306,902,903],{"class":326},"  if",[306,905,906],{"class":330}," (",[306,908,909],{"class":326},"!",[306,911,912],{"class":330},"allowedFrom.",[306,914,915],{"class":343},"has",[306,917,918],{"class":330},"(from)) {\n",[306,920,921,924,926,929,931,934],{"class":308,"line":599},[306,922,923],{"class":326},"    throw",[306,925,837],{"class":326},[306,927,928],{"class":343}," Error",[306,930,764],{"class":330},[306,932,933],{"class":319},"'Sender is not allowed'",[306,935,936],{"class":330},")\n",[306,938,939],{"class":308,"line":610},[306,940,941],{"class":330},"  }\n",[306,943,944],{"class":308,"line":620},[306,945,540],{"emptyLinePlaceholder":539},[306,947,948,950,952,954,957,960,963,965,968,971,975,977,979,981,983,987,989,991,993,996,998,1001,1004],{"class":308,"line":626},[306,949,903],{"class":326},[306,951,906],{"class":330},[306,953,909],{"class":326},[306,955,956],{"class":319},"\u002F",[306,958,959],{"class":326},"^",[306,961,962],{"class":312},"[",[306,964,959],{"class":326},[306,966,967],{"class":312},"\\s@]",[306,969,970],{"class":326},"+",[306,972,974],{"class":973},"se7xv","@",[306,976,962],{"class":312},[306,978,959],{"class":326},[306,980,967],{"class":312},[306,982,970],{"class":326},[306,984,986],{"class":985},"s-Lb2","\\.",[306,988,962],{"class":312},[306,990,959],{"class":326},[306,992,967],{"class":312},[306,994,995],{"class":326},"+$",[306,997,956],{"class":319},[306,999,1000],{"class":330},".",[306,1002,1003],{"class":343},"test",[306,1005,1006],{"class":330},"(to)) {\n",[306,1008,1009,1011,1013,1015,1017,1020],{"class":308,"line":637},[306,1010,923],{"class":326},[306,1012,837],{"class":326},[306,1014,928],{"class":343},[306,1016,764],{"class":330},[306,1018,1019],{"class":319},"'Recipient is invalid'",[306,1021,936],{"class":330},[306,1023,1024],{"class":308,"line":647},[306,1025,941],{"class":330},[306,1027,1028],{"class":308,"line":658},[306,1029,1030],{"class":330},"}\n",[16,1032,1033,1034,1036,1037,1036,1039,1042,1043,1046,1047,1050],{},"不要提供一个接收任意 ",[20,1035,531],{},"、",[20,1038,891],{},[20,1040,1041],{},"subject"," 和 HTML 的公开 API。否则应用很容易变成开放邮件中继，或者被用来发送钓鱼邮件。更安全的接口只接收业务参数，例如 ",[20,1044,1045],{},"userId"," 和 ",[20,1048,1049],{},"templateId","；服务端再从数据库取已验证地址，并从白名单模板生成标题与正文。",[60,1052,1054],{"id":1053},"nuxt-部署在-cloudflare-workers-时怎么办","Nuxt 部署在 Cloudflare Workers 时怎么办",[16,1056,1057],{},"这里需要特别区分运行时。",[16,1059,1060,1061,1064],{},"Nodemailer 是面向传统 Node SMTP Socket 的库。Cloudflare Workers 上的 Nuxt 应用已经运行在 Cloudflare 网络内，通常更适合使用 ",[20,1062,1063],{},"send_email"," binding：",[194,1066,1068],{"className":516,"code":1067,"language":518,"meta":200,"style":200},"interface Env {\n  EMAIL: SendEmail\n}\n\nawait env.EMAIL.send({\n  from: 'noreply@sparkles-editor.com',\n  to: 'recipient@example.com',\n  subject: 'Verification code',\n  text: 'Your code is 123456',\n})\n",[20,1069,1070,1081,1091,1095,1099,1117,1127,1135,1144,1153],{"__ignoreMap":200},[306,1071,1072,1075,1078],{"class":308,"line":309},[306,1073,1074],{"class":326},"interface",[306,1076,1077],{"class":343}," Env",[306,1079,1080],{"class":330}," {\n",[306,1082,1083,1086,1088],{"class":308,"line":323},[306,1084,1085],{"class":879},"  EMAIL",[306,1087,882],{"class":326},[306,1089,1090],{"class":343}," SendEmail\n",[306,1092,1093],{"class":308,"line":363},[306,1094,1030],{"class":330},[306,1096,1097],{"class":308,"line":380},[306,1098,540],{"emptyLinePlaceholder":539},[306,1100,1101,1104,1107,1110,1112,1115],{"class":308,"line":391},[306,1102,1103],{"class":326},"await",[306,1105,1106],{"class":330}," env.",[306,1108,1109],{"class":312},"EMAIL",[306,1111,1000],{"class":330},[306,1113,1114],{"class":343},"send",[306,1116,560],{"class":330},[306,1118,1119,1122,1125],{"class":308,"line":402},[306,1120,1121],{"class":330},"  from: ",[306,1123,1124],{"class":319},"'noreply@sparkles-editor.com'",[306,1126,571],{"class":330},[306,1128,1129,1131,1133],{"class":308,"line":593},[306,1130,704],{"class":330},[306,1132,707],{"class":319},[306,1134,571],{"class":330},[306,1136,1137,1139,1142],{"class":308,"line":599},[306,1138,715],{"class":330},[306,1140,1141],{"class":319},"'Verification code'",[306,1143,571],{"class":330},[306,1145,1146,1148,1151],{"class":308,"line":610},[306,1147,726],{"class":330},[306,1149,1150],{"class":319},"'Your code is 123456'",[306,1152,571],{"class":330},[306,1154,1155],{"class":308,"line":620},[306,1156,661],{"class":330},[16,1158,1159],{},"这样不需要在 Worker 中保存 SMTP API Token，权限也由 binding 管理。",[16,1161,1162],{},"选择原则很简单：",[64,1164,1165,1175],{},[67,1166,1167],{},[70,1168,1169,1172],{},[73,1170,1171],{},"场景",[73,1173,1174],{},"推荐入口",[80,1176,1177,1188,1196,1204,1212],{},[70,1178,1179,1182],{},[85,1180,1181],{},"Nuxt \u002F API 已经部署在 Cloudflare Workers",[85,1183,1184,1185,1187],{},"Workers ",[20,1186,1063],{}," binding",[70,1189,1190,1193],{},[85,1191,1192],{},"外部服务支持 HTTPS API，且便于管理 Token",[85,1194,1195],{},"REST API",[70,1197,1198,1201],{},[85,1199,1200],{},"现有 Node、Python、PHP 应用已经支持 SMTP",[85,1202,1203],{},"认证 SMTP",[70,1205,1206,1209],{},[85,1207,1208],{},"打印机、旧式 CMS 或第三方工具只能填 SMTP",[85,1210,1211],{},"认证 SMTP，但先确认能使用 465 SMTPS",[70,1213,1214,1217],{},[85,1215,1216],{},"浏览器前端直接发送",[85,1218,1219],{},"不允许；必须经过受控服务端",[16,1221,1222],{},"三个入口最后进入同一套 Cloudflare 投递管线、日志和域名认证系统。SMTP 的价值是兼容性，不是要求所有新服务都退回 Socket 协议。",[60,1224,1226],{"id":1225},"python-最小示例","Python 最小示例",[16,1228,1229],{},"Python 标准库无需额外依赖：",[194,1231,1235],{"className":1232,"code":1233,"language":1234,"meta":200,"style":200},"language-python shiki shiki-themes github-dark github-dark","import os\nimport smtplib\nfrom email.message import EmailMessage\n\ntoken = os.environ[\"CF_EMAIL_SMTP_TOKEN\"]\n\nmessage = EmailMessage()\nmessage[\"From\"] = \"Jackie Moon \u003Cnoreply@sparkles-editor.com>\"\nmessage[\"To\"] = \"recipient@example.com\"\nmessage[\"Subject\"] = \"Cloudflare SMTP test\"\nmessage.set_content(\"This email was sent over implicit TLS.\")\n\nwith smtplib.SMTP_SSL(\"smtp.mx.cloudflare.net\", 465, timeout=30) as smtp:\n    smtp.login(\"api_token\", token)\n    smtp.send_message(message)\n","python",[20,1236,1237,1242,1247,1252,1256,1261,1265,1270,1275,1280,1285,1290,1294,1299,1304],{"__ignoreMap":200},[306,1238,1239],{"class":308,"line":309},[306,1240,1241],{},"import os\n",[306,1243,1244],{"class":308,"line":323},[306,1245,1246],{},"import smtplib\n",[306,1248,1249],{"class":308,"line":363},[306,1250,1251],{},"from email.message import EmailMessage\n",[306,1253,1254],{"class":308,"line":380},[306,1255,540],{"emptyLinePlaceholder":539},[306,1257,1258],{"class":308,"line":391},[306,1259,1260],{},"token = os.environ[\"CF_EMAIL_SMTP_TOKEN\"]\n",[306,1262,1263],{"class":308,"line":402},[306,1264,540],{"emptyLinePlaceholder":539},[306,1266,1267],{"class":308,"line":593},[306,1268,1269],{},"message = EmailMessage()\n",[306,1271,1272],{"class":308,"line":599},[306,1273,1274],{},"message[\"From\"] = \"Jackie Moon \u003Cnoreply@sparkles-editor.com>\"\n",[306,1276,1277],{"class":308,"line":610},[306,1278,1279],{},"message[\"To\"] = \"recipient@example.com\"\n",[306,1281,1282],{"class":308,"line":620},[306,1283,1284],{},"message[\"Subject\"] = \"Cloudflare SMTP test\"\n",[306,1286,1287],{"class":308,"line":626},[306,1288,1289],{},"message.set_content(\"This email was sent over implicit TLS.\")\n",[306,1291,1292],{"class":308,"line":637},[306,1293,540],{"emptyLinePlaceholder":539},[306,1295,1296],{"class":308,"line":647},[306,1297,1298],{},"with smtplib.SMTP_SSL(\"smtp.mx.cloudflare.net\", 465, timeout=30) as smtp:\n",[306,1300,1301],{"class":308,"line":658},[306,1302,1303],{},"    smtp.login(\"api_token\", token)\n",[306,1305,1306],{"class":308,"line":664},[306,1307,1308],{},"    smtp.send_message(message)\n",[16,1310,1311,1312,1315,1316,58],{},"关键是 ",[20,1313,1314],{},"SMTP_SSL","，而不是先明文连接再调用 ",[20,1317,1318],{},"starttls()",[60,1320,1321],{"id":1321},"限额与附件",[16,1323,1324,1325,288],{},"根据当前",[46,1326,1329],{"href":1327,"rel":1328},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Femail-service\u002Fplatform\u002Flimits\u002F",[50],"Email Service 平台限制",[233,1331,1332,1335,1341,1344,1347,1354,1360,1363],{},[163,1333,1334],{},"每封邮件最多合计 50 个收件人；",[163,1336,1337,1338,168],{},"SMTP 会话最多接受 50 次 ",[20,1339,1340],{},"RCPT TO",[163,1342,1343],{},"普通邮件总大小上限为 5 MiB；",[163,1345,1346],{},"发往“已验证目标地址”时，总大小上限可到 25 MiB；",[163,1348,1349,1350,1353],{},"SMTP ",[20,1351,1352],{},"AUTH"," 超时为 30 秒；",[163,1355,1349,1356,1359],{},[20,1357,1358],{},"DATA"," 超时为 300 秒；",[163,1361,1362],{},"自定义邮件头合计上限为 16 KiB；",[163,1364,1365],{},"Subject 上限为 998 个字符。",[16,1367,1368],{},"邮件总大小包含 MIME 编码后的正文和附件。二进制附件经过 Base64 后通常会膨胀约三分之一，因此不要把一个 4.9 MiB 文件误认为一定能塞进 5 MiB 邮件。",[16,1370,1371],{},"控制台显示的每日配额可能因账户、Beta 阶段和使用情况而变化。不要把某次看到的数字写死在业务逻辑里；应读取当前控制台配额，并在自己的发送队列中保留余量。",[60,1373,1374],{"id":1374},"错误处理和重试",[16,1376,1377],{},"SMTP 的返回码已经表达了“要不要重试”：",[64,1379,1380,1393],{},[67,1381,1382],{},[70,1383,1384,1387,1390],{},[73,1385,1386],{},"返回码",[73,1388,1389],{},"含义",[73,1391,1392],{},"建议",[80,1394,1395,1407,1421,1433,1445],{},[70,1396,1397,1401,1404],{},[85,1398,1399],{},[20,1400,414],{},[85,1402,1403],{},"已被 Cloudflare 接受",[85,1405,1406],{},"记录 Message-ID，不再重复发送",[70,1408,1409,1415,1418],{},[85,1410,1411,451,1413],{},[20,1412,450],{},[20,1414,454],{},[85,1416,1417],{},"临时故障或限流",[85,1419,1420],{},"指数退避并加入随机抖动",[70,1422,1423,1427,1430],{},[85,1424,1425],{},[20,1426,430],{},[85,1428,1429],{},"认证失败",[85,1431,1432],{},"停止自动重试，检查 Token",[70,1434,1435,1439,1442],{},[85,1436,1437],{},[20,1438,438],{},[85,1440,1441],{},"发件域、地址或收件策略被拒绝",[85,1443,1444],{},"修正配置或数据后再发",[70,1446,1447,1451,1454],{},[85,1448,1449],{},[20,1450,444],{},[85,1452,1453],{},"邮件过大",[85,1455,1456],{},"缩小正文或附件",[16,1458,1459],{},"推荐的重试节奏可以是 30 秒、2 分钟、10 分钟、30 分钟，最多尝试固定次数。每次业务发送必须有幂等键，例如：",[194,1461,1464],{"className":1462,"code":1463,"language":199,"meta":200},[197],"password-reset:{userId}:{resetRequestId}\ninvoice:{invoiceId}:issued\n",[20,1465,1463],{"__ignoreMap":200},[16,1467,1468,1469,1471],{},"临时错误进入队列，永久错误进入人工检查或失败状态。不要把所有非 ",[20,1470,414],{}," 都立即重试，也不要让同一个验证码因为网络抖动发送十次。",[60,1473,1475],{"id":1474},"投递率不是返回-250-就结束","投递率不是“返回 250 就结束”",[16,1477,1478,1480],{},[20,1479,414],{}," 代表 Cloudflare 接受了消息，不代表最终收件箱一定出现了邮件。生产环境还要观察：",[233,1482,1483,1486,1489,1492,1495,1498],{},[163,1484,1485],{},"Delivery、Bounce、Deferred 与 Suppression；",[163,1487,1488],{},"Gmail、Outlook、QQ 邮箱等主要目的地的结果；",[163,1490,1491],{},"From、Return-Path、DKIM 和 DMARC 对齐；",[163,1493,1494],{},"硬退信地址是否立即停止发送；",[163,1496,1497],{},"投诉率与无效地址增长；",[163,1499,1500],{},"验证码和密码重置是否设置明确有效期。",[16,1502,1503],{},"事务邮件应该使用稳定、可识别的发件人，并同时提供纯文本与 HTML。不要伪造 Reply-To，不要用验证码通道发送营销群发，也不要让用户输入直接进入邮件头，避免 Header Injection。",[60,1505,1506],{"id":1506},"上线检查清单",[233,1508,1511,1520,1526,1534,1543,1549,1555,1561,1567,1578,1590,1596],{"className":1509},[1510],"contains-task-list",[163,1512,1515,1519],{"className":1513},[1514],"task-list-item",[1516,1517],"input",{"disabled":539,"type":1518},"checkbox"," 发件域在 Email Sending 中为“已配置”",[163,1521,1523,1525],{"className":1522},[1514],[1516,1524],{"disabled":539,"type":1518}," 使用 465 + 隐式 TLS",[163,1527,1529,1531,1532],{"className":1528},[1514],[1516,1530],{"disabled":539,"type":1518}," 用户名严格为 ",[20,1533,121],{},[163,1535,1537,1539,1540,1542],{"className":1536},[1514],[1516,1538],{"disabled":539,"type":1518}," Token 只有 ",[20,1541,132],{}," 最小权限",[163,1544,1546,1548],{"className":1545},[1514],[1516,1547],{"disabled":539,"type":1518}," Token 位于服务端 Secret，不进入 Git、日志和前端",[163,1550,1552,1554],{"className":1551},[1514],[1516,1553],{"disabled":539,"type":1518}," From 地址来自服务端白名单",[163,1556,1558,1560],{"className":1557},[1514],[1516,1559],{"disabled":539,"type":1518}," 收件地址经过验证、去重和长度限制",[163,1562,1564,1566],{"className":1563},[1514],[1516,1565],{"disabled":539,"type":1518}," HTML 使用固定模板并保留纯文本版本",[163,1568,1570,1572,1573,451,1575,1577],{"className":1569},[1514],[1516,1571],{"disabled":539,"type":1518}," ",[20,1574,450],{},[20,1576,454],{}," 使用带抖动的指数退避",[163,1579,1581,1572,1583,451,1585,451,1587,1589],{"className":1580},[1514],[1516,1582],{"disabled":539,"type":1518},[20,1584,430],{},[20,1586,438],{},[20,1588,444],{}," 不做盲目重试",[163,1591,1593,1595],{"className":1592},[1514],[1516,1594],{"disabled":539,"type":1518}," 记录 Message-ID、模板名与业务幂等键，不记录 Token",[163,1597,1599,1601],{"className":1598},[1514],[1516,1600],{"disabled":539,"type":1518}," 监控退信、抑制、投诉和每日配额",[60,1603,1604],{"id":1604},"最后的选择",[16,1606,1607],{},"如果你有一个已经稳定运行的 Node、Python 或 PHP 应用，Cloudflare SMTP 的优势很直接：只换主机、端口和认证信息，就能接入 Cloudflare 的投递与日志体系。",[16,1609,1610],{},"如果应用原生运行在 Workers，则没有必要绕一圈模拟传统服务器，优先使用 Email binding。SMTP 是一座兼容旧有生态的桥，而不是唯一入口。",[186,1612,1613],{"id":1613},"参考资料",[233,1615,1616,1622,1628,1634],{},[163,1617,1618],{},[46,1619,1621],{"href":48,"rel":1620},[50],"Cloudflare Email Service：发送邮件入门",[163,1623,1624],{},[46,1625,1627],{"href":55,"rel":1626},[50],"Cloudflare Email Service：SMTP API 参考",[163,1629,1630],{},[46,1631,1633],{"href":418,"rel":1632},[50],"Cloudflare Email Service：SMTP 示例",[163,1635,1636],{},[46,1637,1639],{"href":1327,"rel":1638},[50],"Cloudflare Email Service：平台限制",[1641,1642,1643],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s8ozJ, html code.shiki .s8ozJ{--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .s4wv1, html code.shiki .s4wv1{--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html pre.shiki code .sOPea, html code.shiki .sOPea{--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .suv1-, html code.shiki .suv1-{--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .sFR8T, html code.shiki .sFR8T{--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .s-3mD, html code.shiki .s-3mD{--shiki-default:#FFAB70;--shiki-dark:#FFAB70}html pre.shiki code .se7xv, html code.shiki .se7xv{--shiki-default:#DBEDFF;--shiki-dark:#DBEDFF}html pre.shiki code .s-Lb2, html code.shiki .s-Lb2{--shiki-default:#85E89D;--shiki-default-font-weight:bold;--shiki-dark:#85E89D;--shiki-dark-font-weight:bold}",{"title":200,"searchDepth":363,"depth":363,"links":1645},[1646,1647,1651,1652,1653,1654,1655,1656,1657,1658,1659],{"id":62,"depth":323,"text":62},{"id":184,"depth":323,"text":184,"children":1648},[1649,1650],{"id":188,"depth":363,"text":189},{"id":208,"depth":363,"text":209},{"id":277,"depth":323,"text":278},{"id":458,"depth":323,"text":459},{"id":1053,"depth":323,"text":1054},{"id":1225,"depth":323,"text":1226},{"id":1321,"depth":323,"text":1321},{"id":1374,"depth":323,"text":1374},{"id":1474,"depth":323,"text":1475},{"id":1506,"depth":323,"text":1506},{"id":1604,"depth":323,"text":1604,"children":1660},[1661],{"id":1613,"depth":363,"text":1613},"一篇独立、可落地的 Cloudflare Email Service SMTP 教程，覆盖域名接入、API Token、cURL、Nodemailer、Python、限额、错误码与生产安全。",false,null,"md",{"src":1667,"alt":1668,"caption":1669,"width":1670,"height":1671},"\u002Fmedia\u002Fposts\u002Fcloudflare-smtp-email-sending-guide\u002Fcover-v1.webp","一封事务邮件通过加密 SMTP 连接进入全球邮件投递网络","把现有应用接到 Cloudflare Email Service，不一定要重写发送层；标准 SMTP 就是一条兼容路径",1672,941,{},"\u002Fblog\u002Fcloudflare-smtp-email-sending-guide","2026-07-31",{"title":10,"description":1662},"blog\u002Fcloudflare-smtp-email-sending-guide",[1678,1679,1680,1681],"Cloudflare","SMTP","Email","Nodemailer","R8uAwk9ZxnoMXRXPp0ZH4b_WX8vXe-kowkiJTSTdZ2Y",[1684,2900,5293,7001,7259,7711,8112],{"id":9,"title":10,"accent":11,"body":1685,"description":1662,"draft":1663,"experience":1664,"extension":1665,"featured":539,"image":2896,"meta":2897,"navigation":539,"path":1673,"publishedAt":1674,"readingTime":690,"seo":2898,"stem":1676,"tags":2899,"updatedAt":1664,"video":1664,"__hash__":1682,"contentLocale":4,"isFallback":1663},{"type":13,"value":1686,"toc":2878},[1687,1691,1693,1699,1709,1711,1777,1779,1789,1794,1796,1798,1800,1805,1807,1809,1813,1817,1821,1823,1837,1839,1853,1855,1857,1861,1866,1868,1886,1888,1942,1949,1951,1973,1975,1977,1999,2001,2013,2015,2227,2231,2237,2239,2407,2419,2421,2423,2427,2505,2507,2509,2553,2555,2557,2559,2623,2629,2631,2636,2660,2662,2664,2666,2668,2734,2736,2741,2745,2747,2751,2765,2767,2769,2846,2848,2850,2852,2854,2876],[16,1688,18,1689,23],{},[20,1690,22],{},[16,1692,26],{},[28,1694,1695],{},[16,1696,32,1697,36],{},[20,1698,35],{},[16,1700,39,1701,44,1703,52,1706,58],{},[41,1702,43],{},[46,1704,51],{"href":48,"rel":1705},[50],[46,1707,57],{"href":55,"rel":1708},[50],[60,1710,62],{"id":62},[64,1712,1713,1721],{},[67,1714,1715],{},[70,1716,1717,1719],{},[73,1718,75],{},[73,1720,78],{},[80,1722,1723,1731,1739,1745,1753,1761,1771],{},[70,1724,1725,1727],{},[85,1726,87],{},[85,1728,1729],{},[20,1730,92],{},[70,1732,1733,1735],{},[85,1734,97],{},[85,1736,1737],{},[20,1738,102],{},[70,1740,1741,1743],{},[85,1742,107],{},[85,1744,110],{},[70,1746,1747,1749],{},[85,1748,115],{},[85,1750,118,1751],{},[20,1752,121],{},[70,1754,1755,1757],{},[85,1756,126],{},[85,1758,129,1759,133],{},[20,1760,132],{},[70,1762,1763,1765],{},[85,1764,138],{},[85,1766,1767,144,1769],{},[20,1768,143],{},[20,1770,147],{},[70,1772,1773,1775],{},[85,1774,152],{},[85,1776,155],{},[16,1778,158],{},[160,1780,1781,1785,1787],{},[163,1782,165,1783,168],{},[20,1784,121],{},[163,1786,171],{},[163,1788,174],{},[16,1790,177,1791,58],{},[46,1792,181],{"href":55,"rel":1793},[50],[60,1795,184],{"id":184},[186,1797,189],{"id":188},[16,1799,192],{},[194,1801,1803],{"className":1802,"code":198,"language":199,"meta":200},[197],[20,1804,198],{"__ignoreMap":200},[16,1806,205],{},[186,1808,209],{"id":208},[16,1810,212,1811,215],{},[20,1812,132],{},[16,1814,1815],{},[219,1816],{"alt":221,"src":222},[16,1818,1819],{},[226,1820,228],{},[16,1822,231],{},[233,1824,1825,1827,1831,1833,1835],{},[163,1826,237],{},[163,1828,240,1829,168],{},[20,1830,132],{},[163,1832,245],{},[163,1834,248],{},[163,1836,251],{},[16,1838,254],{},[233,1840,1841,1843,1847,1849,1851],{},[163,1842,259],{},[163,1844,262,1845,168],{},[20,1846,265],{},[163,1848,268],{},[163,1850,271],{},[163,1852,274],{},[60,1854,278],{"id":277},[16,1856,281],{},[16,1858,284,1859,288],{},[20,1860,287],{},[194,1862,1864],{"className":1863,"code":292,"language":199,"meta":200},[197],[20,1865,292],{"__ignoreMap":200},[16,1867,297],{},[194,1869,1870],{"className":300,"code":301,"language":302,"meta":200,"style":200},[20,1871,1872,1880],{"__ignoreMap":200},[306,1873,1874,1876,1878],{"class":308,"line":309},[306,1875,313],{"class":312},[306,1877,316],{"class":312},[306,1879,320],{"class":319},[306,1881,1882,1884],{"class":308,"line":323},[306,1883,327],{"class":326},[306,1885,320],{"class":330},[16,1887,333],{},[194,1889,1890],{"className":300,"code":336,"language":302,"meta":200,"style":200},[20,1891,1892,1902,1908,1920,1928,1936],{"__ignoreMap":200},[306,1893,1894,1896,1898,1900],{"class":308,"line":309},[306,1895,344],{"class":343},[306,1897,347],{"class":312},[306,1899,350],{"class":319},[306,1901,353],{"class":312},[306,1903,1904,1906],{"class":308,"line":323},[306,1905,358],{"class":312},[306,1907,353],{"class":312},[306,1909,1910,1912,1914,1916,1918],{"class":308,"line":363},[306,1911,366],{"class":312},[306,1913,369],{"class":319},[306,1915,372],{"class":330},[306,1917,375],{"class":319},[306,1919,353],{"class":312},[306,1921,1922,1924,1926],{"class":308,"line":380},[306,1923,383],{"class":312},[306,1925,386],{"class":319},[306,1927,353],{"class":312},[306,1929,1930,1932,1934],{"class":308,"line":391},[306,1931,394],{"class":312},[306,1933,397],{"class":319},[306,1935,353],{"class":312},[306,1937,1938,1940],{"class":308,"line":402},[306,1939,405],{"class":312},[306,1941,408],{"class":319},[16,1943,411,1944,415,1946,58],{},[20,1945,414],{},[46,1947,420],{"href":418,"rel":1948},[50],[16,1950,423],{},[233,1952,1953,1959,1963,1967],{},[163,1954,1955,431,1957,168],{},[20,1956,430],{},[20,1958,132],{},[163,1960,1961,439],{},[20,1962,438],{},[163,1964,1965,445],{},[20,1966,444],{},[163,1968,1969,451,1971,455],{},[20,1970,450],{},[20,1972,454],{},[60,1974,459],{"id":458},[16,1976,462],{},[194,1978,1979],{"className":300,"code":465,"language":302,"meta":200,"style":200},[20,1980,1981,1989],{"__ignoreMap":200},[306,1982,1983,1985,1987],{"class":308,"line":309},[306,1984,472],{"class":343},[306,1986,475],{"class":319},[306,1988,478],{"class":319},[306,1990,1991,1993,1995,1997],{"class":308,"line":323},[306,1992,472],{"class":343},[306,1994,475],{"class":319},[306,1996,487],{"class":312},[306,1998,490],{"class":319},[16,2000,493],{},[194,2002,2003],{"className":496,"code":497,"language":498,"meta":200,"style":200},[20,2004,2005,2009],{"__ignoreMap":200},[306,2006,2007],{"class":308,"line":309},[306,2008,505],{},[306,2010,2011],{"class":308,"line":323},[306,2012,510],{},[16,2014,513],{},[194,2016,2017],{"className":516,"code":517,"language":518,"meta":200,"style":200},[20,2018,2019,2029,2033,2047,2055,2063,2071,2075,2083,2091,2095,2103,2111,2119,2123,2127,2143,2151,2159,2167,2175,2183,2187,2191,2203,2207,2215,2223],{"__ignoreMap":200},[306,2020,2021,2023,2025,2027],{"class":308,"line":309},[306,2022,525],{"class":326},[306,2024,528],{"class":330},[306,2026,531],{"class":326},[306,2028,534],{"class":319},[306,2030,2031],{"class":308,"line":323},[306,2032,540],{"emptyLinePlaceholder":539},[306,2034,2035,2037,2039,2041,2043,2045],{"class":308,"line":363},[306,2036,545],{"class":326},[306,2038,548],{"class":312},[306,2040,551],{"class":326},[306,2042,554],{"class":330},[306,2044,557],{"class":343},[306,2046,560],{"class":330},[306,2048,2049,2051,2053],{"class":308,"line":380},[306,2050,565],{"class":330},[306,2052,568],{"class":319},[306,2054,571],{"class":330},[306,2056,2057,2059,2061],{"class":308,"line":391},[306,2058,576],{"class":330},[306,2060,102],{"class":312},[306,2062,571],{"class":330},[306,2064,2065,2067,2069],{"class":308,"line":402},[306,2066,585],{"class":330},[306,2068,588],{"class":312},[306,2070,571],{"class":330},[306,2072,2073],{"class":308,"line":593},[306,2074,596],{"class":330},[306,2076,2077,2079,2081],{"class":308,"line":599},[306,2078,602],{"class":330},[306,2080,605],{"class":319},[306,2082,571],{"class":330},[306,2084,2085,2087,2089],{"class":308,"line":610},[306,2086,613],{"class":330},[306,2088,372],{"class":312},[306,2090,571],{"class":330},[306,2092,2093],{"class":308,"line":620},[306,2094,623],{"class":330},[306,2096,2097,2099,2101],{"class":308,"line":626},[306,2098,629],{"class":330},[306,2100,632],{"class":312},[306,2102,571],{"class":330},[306,2104,2105,2107,2109],{"class":308,"line":637},[306,2106,640],{"class":330},[306,2108,632],{"class":312},[306,2110,571],{"class":330},[306,2112,2113,2115,2117],{"class":308,"line":647},[306,2114,650],{"class":330},[306,2116,653],{"class":312},[306,2118,571],{"class":330},[306,2120,2121],{"class":308,"line":658},[306,2122,661],{"class":330},[306,2124,2125],{"class":308,"line":664},[306,2126,540],{"emptyLinePlaceholder":539},[306,2128,2129,2131,2133,2135,2137,2139,2141],{"class":308,"line":669},[306,2130,545],{"class":326},[306,2132,674],{"class":312},[306,2134,551],{"class":326},[306,2136,679],{"class":326},[306,2138,682],{"class":330},[306,2140,685],{"class":343},[306,2142,560],{"class":330},[306,2144,2145,2147,2149],{"class":308,"line":690},[306,2146,693],{"class":330},[306,2148,696],{"class":312},[306,2150,571],{"class":330},[306,2152,2153,2155,2157],{"class":308,"line":701},[306,2154,704],{"class":330},[306,2156,707],{"class":319},[306,2158,571],{"class":330},[306,2160,2161,2163,2165],{"class":308,"line":712},[306,2162,715],{"class":330},[306,2164,718],{"class":319},[306,2166,571],{"class":330},[306,2168,2169,2171,2173],{"class":308,"line":723},[306,2170,726],{"class":330},[306,2172,729],{"class":319},[306,2174,571],{"class":330},[306,2176,2177,2179,2181],{"class":308,"line":734},[306,2178,737],{"class":330},[306,2180,740],{"class":319},[306,2182,571],{"class":330},[306,2184,2185],{"class":308,"line":745},[306,2186,661],{"class":330},[306,2188,2189],{"class":308,"line":750},[306,2190,540],{"emptyLinePlaceholder":539},[306,2192,2193,2195,2197,2199,2201],{"class":308,"line":755},[306,2194,758],{"class":330},[306,2196,761],{"class":343},[306,2198,764],{"class":330},[306,2200,767],{"class":319},[306,2202,770],{"class":330},[306,2204,2205],{"class":308,"line":773},[306,2206,776],{"class":330},[306,2208,2209,2211,2213],{"class":308,"line":779},[306,2210,782],{"class":330},[306,2212,785],{"class":312},[306,2214,571],{"class":330},[306,2216,2217,2219,2221],{"class":308,"line":790},[306,2218,793],{"class":330},[306,2220,785],{"class":312},[306,2222,571],{"class":330},[306,2224,2225],{"class":308,"line":800},[306,2226,661],{"class":330},[16,2228,2229],{},[219,2230],{"alt":807,"src":808},[16,2232,2233],{},[226,2234,813,2235,817],{},[20,2236,816],{},[16,2238,820],{},[194,2240,2241],{"className":516,"code":823,"language":518,"meta":200,"style":200},[20,2242,2243,2257,2263,2269,2273,2277,2301,2315,2329,2333,2337,2385,2399,2403],{"__ignoreMap":200},[306,2244,2245,2247,2249,2251,2253,2255],{"class":308,"line":309},[306,2246,545],{"class":326},[306,2248,832],{"class":312},[306,2250,551],{"class":326},[306,2252,837],{"class":326},[306,2254,840],{"class":343},[306,2256,843],{"class":330},[306,2258,2259,2261],{"class":308,"line":323},[306,2260,848],{"class":319},[306,2262,571],{"class":330},[306,2264,2265,2267],{"class":308,"line":363},[306,2266,855],{"class":319},[306,2268,571],{"class":330},[306,2270,2271],{"class":308,"line":380},[306,2272,862],{"class":330},[306,2274,2275],{"class":308,"line":391},[306,2276,540],{"emptyLinePlaceholder":539},[306,2278,2279,2281,2283,2285,2287,2289,2291,2293,2295,2297,2299],{"class":308,"line":402},[306,2280,871],{"class":326},[306,2282,874],{"class":343},[306,2284,764],{"class":330},[306,2286,531],{"class":879},[306,2288,882],{"class":326},[306,2290,885],{"class":312},[306,2292,888],{"class":330},[306,2294,891],{"class":879},[306,2296,882],{"class":326},[306,2298,885],{"class":312},[306,2300,898],{"class":330},[306,2302,2303,2305,2307,2309,2311,2313],{"class":308,"line":593},[306,2304,903],{"class":326},[306,2306,906],{"class":330},[306,2308,909],{"class":326},[306,2310,912],{"class":330},[306,2312,915],{"class":343},[306,2314,918],{"class":330},[306,2316,2317,2319,2321,2323,2325,2327],{"class":308,"line":599},[306,2318,923],{"class":326},[306,2320,837],{"class":326},[306,2322,928],{"class":343},[306,2324,764],{"class":330},[306,2326,933],{"class":319},[306,2328,936],{"class":330},[306,2330,2331],{"class":308,"line":610},[306,2332,941],{"class":330},[306,2334,2335],{"class":308,"line":620},[306,2336,540],{"emptyLinePlaceholder":539},[306,2338,2339,2341,2343,2345,2347,2349,2351,2353,2355,2357,2359,2361,2363,2365,2367,2369,2371,2373,2375,2377,2379,2381,2383],{"class":308,"line":626},[306,2340,903],{"class":326},[306,2342,906],{"class":330},[306,2344,909],{"class":326},[306,2346,956],{"class":319},[306,2348,959],{"class":326},[306,2350,962],{"class":312},[306,2352,959],{"class":326},[306,2354,967],{"class":312},[306,2356,970],{"class":326},[306,2358,974],{"class":973},[306,2360,962],{"class":312},[306,2362,959],{"class":326},[306,2364,967],{"class":312},[306,2366,970],{"class":326},[306,2368,986],{"class":985},[306,2370,962],{"class":312},[306,2372,959],{"class":326},[306,2374,967],{"class":312},[306,2376,995],{"class":326},[306,2378,956],{"class":319},[306,2380,1000],{"class":330},[306,2382,1003],{"class":343},[306,2384,1006],{"class":330},[306,2386,2387,2389,2391,2393,2395,2397],{"class":308,"line":637},[306,2388,923],{"class":326},[306,2390,837],{"class":326},[306,2392,928],{"class":343},[306,2394,764],{"class":330},[306,2396,1019],{"class":319},[306,2398,936],{"class":330},[306,2400,2401],{"class":308,"line":647},[306,2402,941],{"class":330},[306,2404,2405],{"class":308,"line":658},[306,2406,1030],{"class":330},[16,2408,1033,2409,1036,2411,1036,2413,1042,2415,1046,2417,1050],{},[20,2410,531],{},[20,2412,891],{},[20,2414,1041],{},[20,2416,1045],{},[20,2418,1049],{},[60,2420,1054],{"id":1053},[16,2422,1057],{},[16,2424,1060,2425,1064],{},[20,2426,1063],{},[194,2428,2429],{"className":516,"code":1067,"language":518,"meta":200,"style":200},[20,2430,2431,2439,2447,2451,2455,2469,2477,2485,2493,2501],{"__ignoreMap":200},[306,2432,2433,2435,2437],{"class":308,"line":309},[306,2434,1074],{"class":326},[306,2436,1077],{"class":343},[306,2438,1080],{"class":330},[306,2440,2441,2443,2445],{"class":308,"line":323},[306,2442,1085],{"class":879},[306,2444,882],{"class":326},[306,2446,1090],{"class":343},[306,2448,2449],{"class":308,"line":363},[306,2450,1030],{"class":330},[306,2452,2453],{"class":308,"line":380},[306,2454,540],{"emptyLinePlaceholder":539},[306,2456,2457,2459,2461,2463,2465,2467],{"class":308,"line":391},[306,2458,1103],{"class":326},[306,2460,1106],{"class":330},[306,2462,1109],{"class":312},[306,2464,1000],{"class":330},[306,2466,1114],{"class":343},[306,2468,560],{"class":330},[306,2470,2471,2473,2475],{"class":308,"line":402},[306,2472,1121],{"class":330},[306,2474,1124],{"class":319},[306,2476,571],{"class":330},[306,2478,2479,2481,2483],{"class":308,"line":593},[306,2480,704],{"class":330},[306,2482,707],{"class":319},[306,2484,571],{"class":330},[306,2486,2487,2489,2491],{"class":308,"line":599},[306,2488,715],{"class":330},[306,2490,1141],{"class":319},[306,2492,571],{"class":330},[306,2494,2495,2497,2499],{"class":308,"line":610},[306,2496,726],{"class":330},[306,2498,1150],{"class":319},[306,2500,571],{"class":330},[306,2502,2503],{"class":308,"line":620},[306,2504,661],{"class":330},[16,2506,1159],{},[16,2508,1162],{},[64,2510,2511,2519],{},[67,2512,2513],{},[70,2514,2515,2517],{},[73,2516,1171],{},[73,2518,1174],{},[80,2520,2521,2529,2535,2541,2547],{},[70,2522,2523,2525],{},[85,2524,1181],{},[85,2526,1184,2527,1187],{},[20,2528,1063],{},[70,2530,2531,2533],{},[85,2532,1192],{},[85,2534,1195],{},[70,2536,2537,2539],{},[85,2538,1200],{},[85,2540,1203],{},[70,2542,2543,2545],{},[85,2544,1208],{},[85,2546,1211],{},[70,2548,2549,2551],{},[85,2550,1216],{},[85,2552,1219],{},[16,2554,1222],{},[60,2556,1226],{"id":1225},[16,2558,1229],{},[194,2560,2561],{"className":1232,"code":1233,"language":1234,"meta":200,"style":200},[20,2562,2563,2567,2571,2575,2579,2583,2587,2591,2595,2599,2603,2607,2611,2615,2619],{"__ignoreMap":200},[306,2564,2565],{"class":308,"line":309},[306,2566,1241],{},[306,2568,2569],{"class":308,"line":323},[306,2570,1246],{},[306,2572,2573],{"class":308,"line":363},[306,2574,1251],{},[306,2576,2577],{"class":308,"line":380},[306,2578,540],{"emptyLinePlaceholder":539},[306,2580,2581],{"class":308,"line":391},[306,2582,1260],{},[306,2584,2585],{"class":308,"line":402},[306,2586,540],{"emptyLinePlaceholder":539},[306,2588,2589],{"class":308,"line":593},[306,2590,1269],{},[306,2592,2593],{"class":308,"line":599},[306,2594,1274],{},[306,2596,2597],{"class":308,"line":610},[306,2598,1279],{},[306,2600,2601],{"class":308,"line":620},[306,2602,1284],{},[306,2604,2605],{"class":308,"line":626},[306,2606,1289],{},[306,2608,2609],{"class":308,"line":637},[306,2610,540],{"emptyLinePlaceholder":539},[306,2612,2613],{"class":308,"line":647},[306,2614,1298],{},[306,2616,2617],{"class":308,"line":658},[306,2618,1303],{},[306,2620,2621],{"class":308,"line":664},[306,2622,1308],{},[16,2624,1311,2625,1315,2627,58],{},[20,2626,1314],{},[20,2628,1318],{},[60,2630,1321],{"id":1321},[16,2632,1324,2633,288],{},[46,2634,1329],{"href":1327,"rel":2635},[50],[233,2637,2638,2640,2644,2646,2648,2652,2656,2658],{},[163,2639,1334],{},[163,2641,1337,2642,168],{},[20,2643,1340],{},[163,2645,1343],{},[163,2647,1346],{},[163,2649,1349,2650,1353],{},[20,2651,1352],{},[163,2653,1349,2654,1359],{},[20,2655,1358],{},[163,2657,1362],{},[163,2659,1365],{},[16,2661,1368],{},[16,2663,1371],{},[60,2665,1374],{"id":1374},[16,2667,1377],{},[64,2669,2670,2680],{},[67,2671,2672],{},[70,2673,2674,2676,2678],{},[73,2675,1386],{},[73,2677,1389],{},[73,2679,1392],{},[80,2681,2682,2692,2704,2714,2724],{},[70,2683,2684,2688,2690],{},[85,2685,2686],{},[20,2687,414],{},[85,2689,1403],{},[85,2691,1406],{},[70,2693,2694,2700,2702],{},[85,2695,2696,451,2698],{},[20,2697,450],{},[20,2699,454],{},[85,2701,1417],{},[85,2703,1420],{},[70,2705,2706,2710,2712],{},[85,2707,2708],{},[20,2709,430],{},[85,2711,1429],{},[85,2713,1432],{},[70,2715,2716,2720,2722],{},[85,2717,2718],{},[20,2719,438],{},[85,2721,1441],{},[85,2723,1444],{},[70,2725,2726,2730,2732],{},[85,2727,2728],{},[20,2729,444],{},[85,2731,1453],{},[85,2733,1456],{},[16,2735,1459],{},[194,2737,2739],{"className":2738,"code":1463,"language":199,"meta":200},[197],[20,2740,1463],{"__ignoreMap":200},[16,2742,1468,2743,1471],{},[20,2744,414],{},[60,2746,1475],{"id":1474},[16,2748,2749,1480],{},[20,2750,414],{},[233,2752,2753,2755,2757,2759,2761,2763],{},[163,2754,1485],{},[163,2756,1488],{},[163,2758,1491],{},[163,2760,1494],{},[163,2762,1497],{},[163,2764,1500],{},[16,2766,1503],{},[60,2768,1506],{"id":1506},[233,2770,2772,2777,2782,2789,2796,2801,2806,2811,2816,2825,2836,2841],{"className":2771},[1510],[163,2773,2775,1519],{"className":2774},[1514],[1516,2776],{"disabled":539,"type":1518},[163,2778,2780,1525],{"className":2779},[1514],[1516,2781],{"disabled":539,"type":1518},[163,2783,2785,1531,2787],{"className":2784},[1514],[1516,2786],{"disabled":539,"type":1518},[20,2788,121],{},[163,2790,2792,1539,2794,1542],{"className":2791},[1514],[1516,2793],{"disabled":539,"type":1518},[20,2795,132],{},[163,2797,2799,1548],{"className":2798},[1514],[1516,2800],{"disabled":539,"type":1518},[163,2802,2804,1554],{"className":2803},[1514],[1516,2805],{"disabled":539,"type":1518},[163,2807,2809,1560],{"className":2808},[1514],[1516,2810],{"disabled":539,"type":1518},[163,2812,2814,1566],{"className":2813},[1514],[1516,2815],{"disabled":539,"type":1518},[163,2817,2819,1572,2821,451,2823,1577],{"className":2818},[1514],[1516,2820],{"disabled":539,"type":1518},[20,2822,450],{},[20,2824,454],{},[163,2826,2828,1572,2830,451,2832,451,2834,1589],{"className":2827},[1514],[1516,2829],{"disabled":539,"type":1518},[20,2831,430],{},[20,2833,438],{},[20,2835,444],{},[163,2837,2839,1595],{"className":2838},[1514],[1516,2840],{"disabled":539,"type":1518},[163,2842,2844,1601],{"className":2843},[1514],[1516,2845],{"disabled":539,"type":1518},[60,2847,1604],{"id":1604},[16,2849,1607],{},[16,2851,1610],{},[186,2853,1613],{"id":1613},[233,2855,2856,2861,2866,2871],{},[163,2857,2858],{},[46,2859,1621],{"href":48,"rel":2860},[50],[163,2862,2863],{},[46,2864,1627],{"href":55,"rel":2865},[50],[163,2867,2868],{},[46,2869,1633],{"href":418,"rel":2870},[50],[163,2872,2873],{},[46,2874,1639],{"href":1327,"rel":2875},[50],[1641,2877,1643],{},{"title":200,"searchDepth":363,"depth":363,"links":2879},[2880,2881,2885,2886,2887,2888,2889,2890,2891,2892,2893],{"id":62,"depth":323,"text":62},{"id":184,"depth":323,"text":184,"children":2882},[2883,2884],{"id":188,"depth":363,"text":189},{"id":208,"depth":363,"text":209},{"id":277,"depth":323,"text":278},{"id":458,"depth":323,"text":459},{"id":1053,"depth":323,"text":1054},{"id":1225,"depth":323,"text":1226},{"id":1321,"depth":323,"text":1321},{"id":1374,"depth":323,"text":1374},{"id":1474,"depth":323,"text":1475},{"id":1506,"depth":323,"text":1506},{"id":1604,"depth":323,"text":1604,"children":2894},[2895],{"id":1613,"depth":363,"text":1613},{"src":1667,"alt":1668,"caption":1669,"width":1670,"height":1671},{},{"title":10,"description":1662},[1678,1679,1680,1681],{"id":2901,"title":2902,"accent":11,"body":2903,"description":5270,"draft":1663,"experience":1664,"extension":1665,"featured":539,"image":5271,"meta":5277,"navigation":539,"path":5278,"publishedAt":5279,"readingTime":745,"seo":5280,"stem":5281,"tags":5282,"updatedAt":1664,"video":5285,"__hash__":5292,"contentLocale":4,"isFallback":1663},"posts_zh\u002Fblog\u002Fcloudflare-email-service-send-receive-guide.md","Cloudflare Email Service 收发实战：从 Email Routing 到 Workers、API 与 SMTP",{"type":13,"value":2904,"toc":5237},[2905,2912,2915,2926,2946,2953,2956,2961,2965,2968,3018,3021,3027,3030,3036,3042,3047,3051,3054,3058,3061,3064,3086,3090,3093,3096,3110,3113,3117,3120,3123,3126,3137,3140,3148,3155,3172,3175,3185,3188,3194,3197,3201,3204,3219,3222,3229,3238,3571,3581,3675,3690,3694,3700,3831,3834,3851,3855,3881,3884,3904,3907,3911,3914,3921,3990,3996,3999,4018,4024,4028,4233,4239,4243,4246,4559,4562,4568,4573,4577,4580,4630,4633,4636,4655,4658,4661,4783,4786,4789,4792,4795,4801,4804,4827,4834,4837,4840,4843,4849,4852,4858,4862,4865,4889,4892,4912,4915,4919,4929,4935,4938,4941,4959,4962,5021,5024,5027,5059,5063,5066,5086,5089,5106,5109,5112,5115,5118,5121,5141,5144,5146,5234],[16,2906,2907,2908,2911],{},"很长一段时间里，Cloudflare 与邮件的关系主要停留在两件事上：管理 DNS，以及用 Email Routing 把 ",[20,2909,2910],{},"support@example.com"," 转发到一个真实邮箱。",[16,2913,2914],{},"2026 年，这个边界明显向前移动了。",[16,2916,2917,2918,2921,2922,2925],{},"Cloudflare 在 4 月把 ",[41,2919,2920],{},"Email Sending"," 推进公开 Beta，让 Workers 可以直接调用 ",[20,2923,2924],{},"env.EMAIL.send()"," 发送事务邮件；6 月又加入了认证 SMTP。加上原有的 Email Routing 与 Email Workers，现在一套 Cloudflare Email Service 已经可以覆盖：",[233,2927,2928,2931,2934,2937,2940,2943],{},[163,2929,2930],{},"接收发往自有域名的邮件；",[163,2932,2933],{},"转发到经过验证的真实邮箱；",[163,2935,2936],{},"把来信交给 Worker 解析、过滤、入库或触发自动化；",[163,2938,2939],{},"在原邮件线程中回复；",[163,2941,2942],{},"从 Worker、REST API 或 SMTP 发送事务邮件；",[163,2944,2945],{},"查看发送日志、退信、抑制与投递指标。",[16,2947,2948,2949,2952],{},"这篇文章以 ",[41,2950,2951],{},"2026 年 7 月 28 日","的官方能力为准，梳理 Cloudflare 邮件收发的完整模型，并给出一套适合 Nuxt、Workers 与 D1 项目的实现思路。",[16,2954,2955],{},"先说结论：",[28,2957,2958],{},[16,2959,2960],{},"Cloudflare Email Service 不是传统的 IMAP \u002F POP3 邮箱，也不是让你在网页里收信的企业邮箱套件。它更像一组“邮件入口、发送通道和可编程事件”，适合验证码、密码重置、订单通知、系统告警、支持工单和邮件驱动的自动化。",[60,2962,2964],{"id":2963},"先把接收和发送分开","先把“接收”和“发送”分开",[16,2966,2967],{},"邮件收发看起来是一件事，在 Cloudflare 里却是两条独立链路。",[64,2969,2970,2986],{},[67,2971,2972],{},[70,2973,2974,2977,2980,2983],{},[73,2975,2976],{},"方向",[73,2978,2979],{},"Cloudflare 产品",[73,2981,2982],{},"入口",[73,2984,2985],{},"Worker 能做什么",[80,2987,2988,3002],{},[70,2989,2990,2993,2996,2999],{},[85,2991,2992],{},"接收",[85,2994,2995],{},"Email Routing \u002F Email Workers",[85,2997,2998],{},"外部邮件服务器连接 Cloudflare MX",[85,3000,3001],{},"转发、回复、拒收、解析、写入 D1\u002FR2、投递 Queue",[70,3003,3004,3007,3009,3012],{},[85,3005,3006],{},"发送",[85,3008,2920],{},[85,3010,3011],{},"Workers binding、REST API、认证 SMTP",[85,3013,3014,3015],{},"发送文本\u002FHTML、附件、自定义头，获得 ",[20,3016,3017],{},"messageId",[16,3019,3020],{},"接收链路大致是：",[194,3022,3025],{"className":3023,"code":3024,"language":199,"meta":200},[197],"发送方 SMTP\n  → Cloudflare MX\n  → SPF \u002F DKIM \u002F DMARC 与信誉检查\n  → 匹配地址规则或 Catch-all\n  → 转发到邮箱 \u002F 交给 Worker \u002F 丢弃\n",[20,3026,3024],{"__ignoreMap":200},[16,3028,3029],{},"发送链路则是：",[194,3031,3034],{"className":3032,"code":3033,"language":199,"meta":200},[197],"Nuxt API \u002F Worker \u002F 外部应用\n  → EMAIL binding \u002F REST API \u002F SMTP\n  → 发件域与参数校验\n  → Cloudflare 投递管线\n  → 收件方邮件服务器\n",[20,3035,3033],{"__ignoreMap":200},[16,3037,3038],{},[219,3039],{"alt":3040,"src":3041},"邮件在边缘入口完成检查后，被分发到真实邮箱、Worker 处理节点或拒绝路径","\u002Fmedia\u002Fposts\u002Fcloudflare-email-service-send-receive-guide\u002Finbound-routing-v1.webp",[16,3043,3044],{},[226,3045,3046],{},"这是一张概念图。真实判断还包含 SMTP 会话、邮件认证、规则匹配和 Worker 执行限制。",[60,3048,3050],{"id":3049},"_2026-年的产品边界与价格","2026 年的产品边界与价格",[16,3052,3053],{},"Cloudflare 当前把接收与发送放在同一个 Email Service 下，但两部分的可用计划不同。",[186,3055,3057],{"id":3056},"email-routing接收与转发","Email Routing：接收与转发",[16,3059,3060],{},"Email Routing 在 Workers Free 和 Workers Paid 都可以使用。官方价格页把入站邮件数量标为不限量，但如果邮件被交给 Worker，仍然会消耗对应的 Worker 请求与计算资源。",[16,3062,3063],{},"它适合：",[233,3065,3066,3073,3076,3083],{},[163,3067,3068,3069,3072],{},"把 ",[20,3070,3071],{},"support@yourdomain.com"," 转发到现有个人邮箱；",[163,3074,3075],{},"为不同网站创建独立的联系地址；",[163,3077,3078,3079,3082],{},"用 ",[20,3080,3081],{},"orders+shop-a@yourdomain.com"," 这样的子地址区分来源；",[163,3084,3085],{},"把来信交给 Worker 转换成工单、Webhook 或数据库记录。",[186,3087,3089],{"id":3088},"email-sending事务邮件","Email Sending：事务邮件",[16,3091,3092],{},"Email Sending 当前仍是公开 Beta。",[16,3094,3095],{},"向任意收件人发送需要 Workers Paid 计划。按照 2026 年 7 月的官方价格：",[233,3097,3098,3101,3104,3107],{},[163,3099,3100],{},"每个账户每月包含 3,000 封出站邮件；",[163,3102,3103],{},"超出后每 1,000 封为 0.35 美元；",[163,3105,3106],{},"发往账户内“已验证目标地址”的邮件始终免费，不计入包含额度；",[163,3108,3109],{},"邮件被 Email Service 接受后，即使随后发生硬退信，通常也会计入额度；在 API 边界直接拒绝的请求不会计入。",[16,3111,3112],{},"价格和 Beta 状态都可能继续调整，真正上线前应重新检查官方价格页，而不是把本文数字长期写死在预算模型里。",[60,3114,3116],{"id":3115},"第一步正确接入域名","第一步：正确接入域名",[16,3118,3119],{},"Email Service 要求域名使用 Cloudflare DNS。发送与接收会创建两组用途不同的 DNS 记录。",[186,3121,3122],{"id":3122},"接收邮件使用根域记录",[16,3124,3125],{},"启用 Email Routing 后，Cloudflare 会在域名根节点配置：",[233,3127,3128,3131,3134],{},[163,3129,3130],{},"MX：把来信送到 Cloudflare 的邮件入口；",[163,3132,3133],{},"SPF：授权 Cloudflare 转发邮件；",[163,3135,3136],{},"DKIM：为转发链路提供签名。",[16,3138,3139],{},"这些记录负责的是“别人发到你的域名”。",[186,3141,3143,3144,3147],{"id":3142},"发送邮件使用-cf-bounce-记录","发送邮件使用 ",[20,3145,3146],{},"cf-bounce"," 记录",[16,3149,3150,3151,3154],{},"启用 Email Sending 时，Cloudflare 会为 ",[20,3152,3153],{},"cf-bounce.yourdomain.com"," 配置：",[233,3156,3157,3160,3163,3166],{},[163,3158,3159],{},"MX：处理退信；",[163,3161,3162],{},"SPF：授权 Cloudflare 代域名发送；",[163,3164,3165],{},"DKIM：验证邮件内容未在途中被修改；",[163,3167,3168,3171],{},[20,3169,3170],{},"_dmarc.yourdomain.com","：声明认证失败时的处理策略。",[16,3173,3174],{},"发送和接收使用不同的 DKIM selector。看到两条 DKIM 记录并不意味着重复配置。",[16,3176,3177,3178,3181,3182,58],{},"如果域名已有 Google Workspace、Microsoft 365 或其他邮件服务，尤其要检查 MX 和 SPF 是否冲突。一个域名只能有一条有效 SPF 策略；需要同时授权多个服务时，应合并 ",[20,3179,3180],{},"include","，而不是再创建第二条 ",[20,3183,3184],{},"v=spf1",[16,3186,3187],{},"我的建议是把不同信誉用途拆到子域：",[194,3189,3192],{"className":3190,"code":3191,"language":199,"meta":200},[197],"noreply@example.com          账户验证码与密码重置\nnotify@mail.example.com      产品通知\nsupport@example.com          人工支持与来信处理\n",[20,3193,3191],{"__ignoreMap":200},[16,3195,3196],{},"事务通知、人工往来与未来可能出现的批量邮件互不拖累，故障和迁移边界也更清晰。",[60,3198,3200],{"id":3199},"接收邮件从转发规则到-email-worker","接收邮件：从转发规则到 Email Worker",[16,3202,3203],{},"最简单的接收方式完全不用代码：",[160,3205,3206,3209,3213,3216],{},[163,3207,3208],{},"在 Email Routing 中添加一个经过验证的目标邮箱；",[163,3210,284,3211,168],{},[20,3212,3071],{},[163,3214,3215],{},"选择“发送到邮箱”；",[163,3217,3218],{},"来信会被 Cloudflare 转发到目标地址。",[16,3220,3221],{},"如果需要按主题分类、提取附件、建立工单或自动回复，就把动作改为“发送到 Worker”。",[186,3223,3225,3228],{"id":3224},"email-是入站邮件入口",[20,3226,3227],{},"email()"," 是入站邮件入口",[16,3230,3231,3232,3235,3236,288],{},"Email Worker 的入口不是 ",[20,3233,3234],{},"fetch()","，而是 ",[20,3237,3227],{},[194,3239,3241],{"className":516,"code":3240,"language":518,"meta":200,"style":200},"interface Env {\n  SUPPORT_FORWARD_TO: string\n}\n\nexport default {\n  async email(message, env): Promise\u003Cvoid> {\n    const recipient = message.to.toLowerCase()\n    const subject = message.headers.get('subject') ?? ''\n\n    if (message.rawSize > 10 * 1024 * 1024) {\n      message.setReject('Message is too large for this mailbox')\n      return\n    }\n\n    if (recipient === 'support@example.com') {\n      console.log('Support email accepted', {\n        fromDomain: message.from.split('@').at(-1),\n        subjectLength: subject.length,\n        rawSize: message.rawSize,\n      })\n\n      await message.forward(env.SUPPORT_FORWARD_TO)\n      return\n    }\n\n    message.setReject('Unknown recipient')\n  },\n} satisfies ExportedHandler\u003CEnv>\n",[20,3242,3243,3251,3261,3265,3269,3278,3313,3332,3361,3365,3391,3406,3411,3416,3420,3435,3450,3480,3489,3494,3499,3503,3522,3526,3530,3534,3548,3552],{"__ignoreMap":200},[306,3244,3245,3247,3249],{"class":308,"line":309},[306,3246,1074],{"class":326},[306,3248,1077],{"class":343},[306,3250,1080],{"class":330},[306,3252,3253,3256,3258],{"class":308,"line":323},[306,3254,3255],{"class":879},"  SUPPORT_FORWARD_TO",[306,3257,882],{"class":326},[306,3259,3260],{"class":312}," string\n",[306,3262,3263],{"class":308,"line":363},[306,3264,1030],{"class":330},[306,3266,3267],{"class":308,"line":380},[306,3268,540],{"emptyLinePlaceholder":539},[306,3270,3271,3273,3276],{"class":308,"line":391},[306,3272,327],{"class":326},[306,3274,3275],{"class":326}," default",[306,3277,1080],{"class":330},[306,3279,3280,3283,3286,3288,3291,3293,3296,3299,3301,3304,3307,3310],{"class":308,"line":402},[306,3281,3282],{"class":326},"  async",[306,3284,3285],{"class":343}," email",[306,3287,764],{"class":330},[306,3289,3290],{"class":879},"message",[306,3292,888],{"class":330},[306,3294,3295],{"class":879},"env",[306,3297,3298],{"class":330},")",[306,3300,882],{"class":326},[306,3302,3303],{"class":343}," Promise",[306,3305,3306],{"class":330},"\u003C",[306,3308,3309],{"class":312},"void",[306,3311,3312],{"class":330},"> {\n",[306,3314,3315,3318,3321,3323,3326,3329],{"class":308,"line":593},[306,3316,3317],{"class":326},"    const",[306,3319,3320],{"class":312}," recipient",[306,3322,551],{"class":326},[306,3324,3325],{"class":330}," message.to.",[306,3327,3328],{"class":343},"toLowerCase",[306,3330,3331],{"class":330},"()\n",[306,3333,3334,3336,3339,3341,3344,3347,3349,3352,3355,3358],{"class":308,"line":599},[306,3335,3317],{"class":326},[306,3337,3338],{"class":312}," subject",[306,3340,551],{"class":326},[306,3342,3343],{"class":330}," message.headers.",[306,3345,3346],{"class":343},"get",[306,3348,764],{"class":330},[306,3350,3351],{"class":319},"'subject'",[306,3353,3354],{"class":330},") ",[306,3356,3357],{"class":326},"??",[306,3359,3360],{"class":319}," ''\n",[306,3362,3363],{"class":308,"line":610},[306,3364,540],{"emptyLinePlaceholder":539},[306,3366,3367,3370,3373,3376,3379,3382,3385,3387,3389],{"class":308,"line":620},[306,3368,3369],{"class":326},"    if",[306,3371,3372],{"class":330}," (message.rawSize ",[306,3374,3375],{"class":326},">",[306,3377,3378],{"class":312}," 10",[306,3380,3381],{"class":326}," *",[306,3383,3384],{"class":312}," 1024",[306,3386,3381],{"class":326},[306,3388,3384],{"class":312},[306,3390,898],{"class":330},[306,3392,3393,3396,3399,3401,3404],{"class":308,"line":626},[306,3394,3395],{"class":330},"      message.",[306,3397,3398],{"class":343},"setReject",[306,3400,764],{"class":330},[306,3402,3403],{"class":319},"'Message is too large for this mailbox'",[306,3405,936],{"class":330},[306,3407,3408],{"class":308,"line":637},[306,3409,3410],{"class":326},"      return\n",[306,3412,3413],{"class":308,"line":647},[306,3414,3415],{"class":330},"    }\n",[306,3417,3418],{"class":308,"line":658},[306,3419,540],{"emptyLinePlaceholder":539},[306,3421,3422,3424,3427,3430,3433],{"class":308,"line":664},[306,3423,3369],{"class":326},[306,3425,3426],{"class":330}," (recipient ",[306,3428,3429],{"class":326},"===",[306,3431,3432],{"class":319}," 'support@example.com'",[306,3434,898],{"class":330},[306,3436,3437,3440,3443,3445,3448],{"class":308,"line":669},[306,3438,3439],{"class":330},"      console.",[306,3441,3442],{"class":343},"log",[306,3444,764],{"class":330},[306,3446,3447],{"class":319},"'Support email accepted'",[306,3449,770],{"class":330},[306,3451,3452,3455,3458,3460,3463,3466,3469,3471,3474,3477],{"class":308,"line":690},[306,3453,3454],{"class":330},"        fromDomain: message.from.",[306,3456,3457],{"class":343},"split",[306,3459,764],{"class":330},[306,3461,3462],{"class":319},"'@'",[306,3464,3465],{"class":330},").",[306,3467,3468],{"class":343},"at",[306,3470,764],{"class":330},[306,3472,3473],{"class":326},"-",[306,3475,3476],{"class":312},"1",[306,3478,3479],{"class":330},"),\n",[306,3481,3482,3485,3487],{"class":308,"line":701},[306,3483,3484],{"class":330},"        subjectLength: subject.",[306,3486,785],{"class":312},[306,3488,571],{"class":330},[306,3490,3491],{"class":308,"line":712},[306,3492,3493],{"class":330},"        rawSize: message.rawSize,\n",[306,3495,3496],{"class":308,"line":723},[306,3497,3498],{"class":330},"      })\n",[306,3500,3501],{"class":308,"line":734},[306,3502,540],{"emptyLinePlaceholder":539},[306,3504,3505,3508,3511,3514,3517,3520],{"class":308,"line":745},[306,3506,3507],{"class":326},"      await",[306,3509,3510],{"class":330}," message.",[306,3512,3513],{"class":343},"forward",[306,3515,3516],{"class":330},"(env.",[306,3518,3519],{"class":312},"SUPPORT_FORWARD_TO",[306,3521,936],{"class":330},[306,3523,3524],{"class":308,"line":750},[306,3525,3410],{"class":326},[306,3527,3528],{"class":308,"line":755},[306,3529,3415],{"class":330},[306,3531,3532],{"class":308,"line":773},[306,3533,540],{"emptyLinePlaceholder":539},[306,3535,3536,3539,3541,3543,3546],{"class":308,"line":779},[306,3537,3538],{"class":330},"    message.",[306,3540,3398],{"class":343},[306,3542,764],{"class":330},[306,3544,3545],{"class":319},"'Unknown recipient'",[306,3547,936],{"class":330},[306,3549,3550],{"class":308,"line":790},[306,3551,623],{"class":330},[306,3553,3554,3557,3560,3563,3565,3568],{"class":308,"line":800},[306,3555,3556],{"class":330},"} ",[306,3558,3559],{"class":326},"satisfies",[306,3561,3562],{"class":343}," ExportedHandler",[306,3564,3306],{"class":330},[306,3566,3567],{"class":343},"Env",[306,3569,3570],{"class":330},">\n",[16,3572,3573,3574,3576,3577,3580],{},"其中 ",[20,3575,3290],{}," 是 ",[20,3578,3579],{},"ForwardableEmailMessage","，常用字段和操作包括：",[64,3582,3583,3593],{},[67,3584,3585],{},[70,3586,3587,3590],{},[73,3588,3589],{},"能力",[73,3591,3592],{},"用途",[80,3594,3595,3605,3615,3625,3635,3645,3655,3665],{},[70,3596,3597,3602],{},[85,3598,3599],{},[20,3600,3601],{},"message.from",[85,3603,3604],{},"SMTP envelope 的发件人",[70,3606,3607,3612],{},[85,3608,3609],{},[20,3610,3611],{},"message.to",[85,3613,3614],{},"SMTP envelope 的收件人",[70,3616,3617,3622],{},[85,3618,3619],{},[20,3620,3621],{},"message.headers",[85,3623,3624],{},"读取 Subject、Message-ID 等邮件头",[70,3626,3627,3632],{},[85,3628,3629],{},[20,3630,3631],{},"message.raw",[85,3633,3634],{},"原始 MIME 数据流",[70,3636,3637,3642],{},[85,3638,3639],{},[20,3640,3641],{},"message.rawSize",[85,3643,3644],{},"原始邮件大小",[70,3646,3647,3652],{},[85,3648,3649],{},[20,3650,3651],{},"message.forward(address)",[85,3653,3654],{},"转发到已验证目标地址",[70,3656,3657,3662],{},[85,3658,3659],{},[20,3660,3661],{},"message.reply(email)",[85,3663,3664],{},"在约束范围内回复原发件人",[70,3666,3667,3672],{},[85,3668,3669],{},[20,3670,3671],{},"message.setReject(reason)",[85,3673,3674],{},"返回永久 SMTP 拒收",[16,3676,3677,3678,451,3680,3682,3683,451,3686,3689],{},"这里有一个容易忽略的安全点：",[20,3679,3601],{},[20,3681,3611],{}," 是 SMTP envelope，邮件正文头部中的 ",[20,3684,3685],{},"From:",[20,3687,3688],{},"To:"," 则可能是另一组值。做授权、路由与审计时，不要把显示给用户看的头部地址误当成可信身份。",[186,3691,3693],{"id":3692},"需要正文时再解析-mime","需要正文时再解析 MIME",[16,3695,3696,3697,288],{},"只做转发或按地址路由时，没有必要完整解析邮件。要提取正文和附件，可以使用 ",[20,3698,3699],{},"postal-mime",[194,3701,3703],{"className":516,"code":3702,"language":518,"meta":200,"style":200},"import PostalMime from 'postal-mime'\n\nexport default {\n  async email(message): Promise\u003Cvoid> {\n    const parsed = await PostalMime.parse(message.raw)\n\n    console.log({\n      subject: parsed.subject,\n      textLength: parsed.text?.length ?? 0,\n      attachmentCount: parsed.attachments.length,\n    })\n  },\n} satisfies ExportedHandler\n",[20,3704,3705,3717,3721,3729,3751,3771,3775,3784,3789,3804,3813,3818,3822],{"__ignoreMap":200},[306,3706,3707,3709,3712,3714],{"class":308,"line":309},[306,3708,525],{"class":326},[306,3710,3711],{"class":330}," PostalMime ",[306,3713,531],{"class":326},[306,3715,3716],{"class":319}," 'postal-mime'\n",[306,3718,3719],{"class":308,"line":323},[306,3720,540],{"emptyLinePlaceholder":539},[306,3722,3723,3725,3727],{"class":308,"line":363},[306,3724,327],{"class":326},[306,3726,3275],{"class":326},[306,3728,1080],{"class":330},[306,3730,3731,3733,3735,3737,3739,3741,3743,3745,3747,3749],{"class":308,"line":380},[306,3732,3282],{"class":326},[306,3734,3285],{"class":343},[306,3736,764],{"class":330},[306,3738,3290],{"class":879},[306,3740,3298],{"class":330},[306,3742,882],{"class":326},[306,3744,3303],{"class":343},[306,3746,3306],{"class":330},[306,3748,3309],{"class":312},[306,3750,3312],{"class":330},[306,3752,3753,3755,3758,3760,3762,3765,3768],{"class":308,"line":391},[306,3754,3317],{"class":326},[306,3756,3757],{"class":312}," parsed",[306,3759,551],{"class":326},[306,3761,679],{"class":326},[306,3763,3764],{"class":330}," PostalMime.",[306,3766,3767],{"class":343},"parse",[306,3769,3770],{"class":330},"(message.raw)\n",[306,3772,3773],{"class":308,"line":402},[306,3774,540],{"emptyLinePlaceholder":539},[306,3776,3777,3780,3782],{"class":308,"line":593},[306,3778,3779],{"class":330},"    console.",[306,3781,3442],{"class":343},[306,3783,560],{"class":330},[306,3785,3786],{"class":308,"line":599},[306,3787,3788],{"class":330},"      subject: parsed.subject,\n",[306,3790,3791,3794,3796,3799,3802],{"class":308,"line":610},[306,3792,3793],{"class":330},"      textLength: parsed.text?.",[306,3795,785],{"class":312},[306,3797,3798],{"class":326}," ??",[306,3800,3801],{"class":312}," 0",[306,3803,571],{"class":330},[306,3805,3806,3809,3811],{"class":308,"line":620},[306,3807,3808],{"class":330},"      attachmentCount: parsed.attachments.",[306,3810,785],{"class":312},[306,3812,571],{"class":330},[306,3814,3815],{"class":308,"line":626},[306,3816,3817],{"class":330},"    })\n",[306,3819,3820],{"class":308,"line":637},[306,3821,623],{"class":330},[306,3823,3824,3826,3828],{"class":308,"line":647},[306,3825,3556],{"class":330},[306,3827,3559],{"class":326},[306,3829,3830],{"class":343}," ExportedHandler\n",[16,3832,3833],{},"解析后的 HTML、文件名、Content-Type 与附件都来自外部输入。正确做法是：",[233,3835,3836,3839,3842,3845,3848],{},[163,3837,3838],{},"不在管理页面直接插入来信 HTML；",[163,3840,3841],{},"限制正文、附件数量和解压后大小；",[163,3843,3844],{},"文件写入 R2 前重新生成对象键，不使用原始文件名作为路径；",[163,3846,3847],{},"不让来信内容直接组成 SQL、Shell、模板或 AI 工具参数；",[163,3849,3850],{},"大任务交给 Queue，不要把杀毒、OCR、模型调用全塞进入站事件的同步 CPU 时间。",[186,3852,3854],{"id":3853},"转发回复拒绝与静默丢弃的区别","转发、回复、拒绝与静默丢弃的区别",[233,3856,3857,3863,3869,3875],{},[163,3858,3859,3862],{},[41,3860,3861],{},"转发","：适合人工邮箱继续处理，目标地址必须提前验证；",[163,3864,3865,3868],{},[41,3866,3867],{},"回复","：适合自动确认或邮件机器人，但有额外限制；",[163,3870,3871,3874],{},[41,3872,3873],{},"拒绝","：在 SMTP 阶段明确告诉发送方没有接收；",[163,3876,3877,3880],{},[41,3878,3879],{},"丢弃","：规则匹配后不继续处理，也不给发送方业务层反馈。",[16,3882,3883],{},"自动回复不能被当成任意发送接口。官方当前要求：",[233,3885,3886,3889,3892,3895,3898],{},[163,3887,3888],{},"来信必须有有效 DMARC 结果；",[163,3890,3891],{},"每个入站事件只能调用一次回复；",[163,3893,3894],{},"回复收件人必须是原始来信的发件人；",[163,3896,3897],{},"回复使用的发件域必须与接收邮件的域一致；",[163,3899,3900,3903],{},[20,3901,3902],{},"References"," 超过 100 项时会拒绝回复，以避免邮件环路。",[16,3905,3906],{},"这组限制反而是一件好事：它让“回复这封邮件”和“向任意地址发一封新邮件”成为两种不同权限。",[60,3908,3910],{"id":3909},"发送邮件优先使用-workers-binding","发送邮件：优先使用 Workers binding",[16,3912,3913],{},"如果应用本身运行在 Cloudflare Workers，Workers binding 是最自然的选择。它不需要在业务代码里保存 Cloudflare API Token。",[16,3915,3916,3917,3920],{},"先在 ",[20,3918,3919],{},"wrangler.jsonc"," 中声明绑定：",[194,3922,3926],{"className":3923,"code":3924,"language":3925,"meta":200,"style":200},"language-jsonc shiki shiki-themes github-dark github-dark","{\n  \"send_email\": [\n    {\n      \"name\": \"EMAIL\",\n      \"allowed_sender_addresses\": [\n        \"noreply@example.com\"\n      ]\n    }\n  ],\n  \"vars\": {\n    \"EMAIL_FROM\": \"noreply@example.com\"\n  }\n}\n","jsonc",[20,3927,3928,3933,3938,3943,3948,3953,3958,3963,3967,3972,3977,3982,3986],{"__ignoreMap":200},[306,3929,3930],{"class":308,"line":309},[306,3931,3932],{},"{\n",[306,3934,3935],{"class":308,"line":323},[306,3936,3937],{},"  \"send_email\": [\n",[306,3939,3940],{"class":308,"line":363},[306,3941,3942],{},"    {\n",[306,3944,3945],{"class":308,"line":380},[306,3946,3947],{},"      \"name\": \"EMAIL\",\n",[306,3949,3950],{"class":308,"line":391},[306,3951,3952],{},"      \"allowed_sender_addresses\": [\n",[306,3954,3955],{"class":308,"line":402},[306,3956,3957],{},"        \"noreply@example.com\"\n",[306,3959,3960],{"class":308,"line":593},[306,3961,3962],{},"      ]\n",[306,3964,3965],{"class":308,"line":599},[306,3966,3415],{},[306,3968,3969],{"class":308,"line":610},[306,3970,3971],{},"  ],\n",[306,3973,3974],{"class":308,"line":620},[306,3975,3976],{},"  \"vars\": {\n",[306,3978,3979],{"class":308,"line":626},[306,3980,3981],{},"    \"EMAIL_FROM\": \"noreply@example.com\"\n",[306,3983,3984],{"class":308,"line":637},[306,3985,941],{},[306,3987,3988],{"class":308,"line":647},[306,3989,1030],{},[16,3991,3992,3995],{},[20,3993,3994],{},"allowed_sender_addresses"," 不只是文档，它是运行时权限边界。即使某个接口被误用，这个绑定也不能伪造其他发件地址。",[16,3997,3998],{},"还可以用以下配置进一步约束收件人：",[233,4000,4001,4007,4013],{},[163,4002,4003,4006],{},[20,4004,4005],{},"destination_address","：固定为一个目标地址；",[163,4008,4009,4012],{},[20,4010,4011],{},"allowed_destination_addresses","：只允许一组目标地址；",[163,4014,4015,4017],{},[20,4016,3994],{},"：限制可使用的发件地址。",[16,4019,4020,4021,4023],{},"用于系统告警的 Worker 最适合固定 ",[20,4022,4005],{},"；用于用户验证码的服务则通常限制发件人，并在应用层校验收件人和频率。",[186,4025,4027],{"id":4026},"在-worker-中发送","在 Worker 中发送",[194,4029,4031],{"className":516,"code":4030,"language":518,"meta":200,"style":200},"interface Env {\n  EMAIL: SendEmail\n}\n\nexport default {\n  async fetch(_request, env): Promise\u003CResponse> {\n    const result = await env.EMAIL.send({\n      from: {\n        email: 'noreply@example.com',\n        name: 'Example App',\n      },\n      to: 'reader@example.net',\n      subject: '验证你的邮箱',\n      text: '你的验证码是 482913，5 分钟内有效。',\n      html: '\u003Cp>你的验证码是 \u003Cstrong>482913\u003C\u002Fstrong>，5 分钟内有效。\u003C\u002Fp>',\n    })\n\n    return Response.json({\n      messageId: result.messageId,\n    })\n  },\n} satisfies ExportedHandler\u003CEnv>\n",[20,4032,4033,4041,4049,4053,4057,4065,4094,4115,4120,4130,4140,4145,4155,4165,4175,4185,4189,4193,4206,4211,4215,4219],{"__ignoreMap":200},[306,4034,4035,4037,4039],{"class":308,"line":309},[306,4036,1074],{"class":326},[306,4038,1077],{"class":343},[306,4040,1080],{"class":330},[306,4042,4043,4045,4047],{"class":308,"line":323},[306,4044,1085],{"class":879},[306,4046,882],{"class":326},[306,4048,1090],{"class":343},[306,4050,4051],{"class":308,"line":363},[306,4052,1030],{"class":330},[306,4054,4055],{"class":308,"line":380},[306,4056,540],{"emptyLinePlaceholder":539},[306,4058,4059,4061,4063],{"class":308,"line":391},[306,4060,327],{"class":326},[306,4062,3275],{"class":326},[306,4064,1080],{"class":330},[306,4066,4067,4069,4072,4074,4077,4079,4081,4083,4085,4087,4089,4092],{"class":308,"line":402},[306,4068,3282],{"class":326},[306,4070,4071],{"class":343}," fetch",[306,4073,764],{"class":330},[306,4075,4076],{"class":879},"_request",[306,4078,888],{"class":330},[306,4080,3295],{"class":879},[306,4082,3298],{"class":330},[306,4084,882],{"class":326},[306,4086,3303],{"class":343},[306,4088,3306],{"class":330},[306,4090,4091],{"class":343},"Response",[306,4093,3312],{"class":330},[306,4095,4096,4098,4101,4103,4105,4107,4109,4111,4113],{"class":308,"line":593},[306,4097,3317],{"class":326},[306,4099,4100],{"class":312}," result",[306,4102,551],{"class":326},[306,4104,679],{"class":326},[306,4106,1106],{"class":330},[306,4108,1109],{"class":312},[306,4110,1000],{"class":330},[306,4112,1114],{"class":343},[306,4114,560],{"class":330},[306,4116,4117],{"class":308,"line":599},[306,4118,4119],{"class":330},"      from: {\n",[306,4121,4122,4125,4128],{"class":308,"line":610},[306,4123,4124],{"class":330},"        email: ",[306,4126,4127],{"class":319},"'noreply@example.com'",[306,4129,571],{"class":330},[306,4131,4132,4135,4138],{"class":308,"line":620},[306,4133,4134],{"class":330},"        name: ",[306,4136,4137],{"class":319},"'Example App'",[306,4139,571],{"class":330},[306,4141,4142],{"class":308,"line":626},[306,4143,4144],{"class":330},"      },\n",[306,4146,4147,4150,4153],{"class":308,"line":637},[306,4148,4149],{"class":330},"      to: ",[306,4151,4152],{"class":319},"'reader@example.net'",[306,4154,571],{"class":330},[306,4156,4157,4160,4163],{"class":308,"line":647},[306,4158,4159],{"class":330},"      subject: ",[306,4161,4162],{"class":319},"'验证你的邮箱'",[306,4164,571],{"class":330},[306,4166,4167,4170,4173],{"class":308,"line":658},[306,4168,4169],{"class":330},"      text: ",[306,4171,4172],{"class":319},"'你的验证码是 482913，5 分钟内有效。'",[306,4174,571],{"class":330},[306,4176,4177,4180,4183],{"class":308,"line":664},[306,4178,4179],{"class":330},"      html: ",[306,4181,4182],{"class":319},"'\u003Cp>你的验证码是 \u003Cstrong>482913\u003C\u002Fstrong>，5 分钟内有效。\u003C\u002Fp>'",[306,4184,571],{"class":330},[306,4186,4187],{"class":308,"line":669},[306,4188,3817],{"class":330},[306,4190,4191],{"class":308,"line":690},[306,4192,540],{"emptyLinePlaceholder":539},[306,4194,4195,4198,4201,4204],{"class":308,"line":701},[306,4196,4197],{"class":326},"    return",[306,4199,4200],{"class":330}," Response.",[306,4202,4203],{"class":343},"json",[306,4205,560],{"class":330},[306,4207,4208],{"class":308,"line":712},[306,4209,4210],{"class":330},"      messageId: result.messageId,\n",[306,4212,4213],{"class":308,"line":723},[306,4214,3817],{"class":330},[306,4216,4217],{"class":308,"line":734},[306,4218,623],{"class":330},[306,4220,4221,4223,4225,4227,4229,4231],{"class":308,"line":745},[306,4222,3556],{"class":330},[306,4224,3559],{"class":326},[306,4226,3562],{"class":343},[306,4228,3306],{"class":330},[306,4230,3567],{"class":343},[306,4232,3570],{"class":330},[16,4234,4235,4236,4238],{},"Workers binding 成功时返回一个 ",[20,4237,3017],{},"。这个结果代表 Cloudflare 接受了发送请求，不应该被解释为用户一定已经在收件箱里看到邮件。最终投递、延迟、退信和抑制状态还要结合 Email Service 日志与指标。",[186,4240,4242],{"id":4241},"在-nuxt-服务端-api-中发送","在 Nuxt 服务端 API 中发送",[16,4244,4245],{},"Nuxt 部署到 Workers 后，同样可以从服务端路由读取 binding：",[194,4247,4249],{"className":516,"code":4248,"language":518,"meta":200,"style":200},"import { z } from 'zod'\n\nconst schema = z.object({\n  email: z.string().trim().email().max(254),\n})\n\nexport default defineEventHandler(async (event) => {\n  const { email } = schema.parse(await readBody(event))\n  const env = event.context.cloudflare.env as {\n    EMAIL: SendEmail\n    EMAIL_FROM: string\n  }\n\n  \u002F\u002F 实际项目还应在这里执行同源校验、IP\u002F邮箱限流，\n  \u002F\u002F 并在 D1 中原子领取一次发送资格。\n  const result = await env.EMAIL.send({\n    from: {\n      email: env.EMAIL_FROM,\n      name: 'Example App',\n    },\n    to: email,\n    subject: '验证邮箱',\n    text: '验证码将在 5 分钟后失效。',\n    html: '\u003Cp>验证码将在 \u003Cstrong>5 分钟\u003C\u002Fstrong>后失效。\u003C\u002Fp>',\n  })\n\n  return {\n    ok: true,\n    messageId: result.messageId,\n  }\n})\n",[20,4250,4251,4263,4267,4284,4315,4319,4323,4349,4380,4397,4406,4415,4419,4423,4429,4434,4454,4459,4469,4478,4483,4488,4498,4508,4518,4523,4527,4534,4543,4549,4554],{"__ignoreMap":200},[306,4252,4253,4255,4258,4260],{"class":308,"line":309},[306,4254,525],{"class":326},[306,4256,4257],{"class":330}," { z } ",[306,4259,531],{"class":326},[306,4261,4262],{"class":319}," 'zod'\n",[306,4264,4265],{"class":308,"line":323},[306,4266,540],{"emptyLinePlaceholder":539},[306,4268,4269,4271,4274,4276,4279,4282],{"class":308,"line":363},[306,4270,545],{"class":326},[306,4272,4273],{"class":312}," schema",[306,4275,551],{"class":326},[306,4277,4278],{"class":330}," z.",[306,4280,4281],{"class":343},"object",[306,4283,560],{"class":330},[306,4285,4286,4289,4292,4295,4298,4300,4303,4305,4308,4310,4313],{"class":308,"line":380},[306,4287,4288],{"class":330},"  email: z.",[306,4290,4291],{"class":343},"string",[306,4293,4294],{"class":330},"().",[306,4296,4297],{"class":343},"trim",[306,4299,4294],{"class":330},[306,4301,4302],{"class":343},"email",[306,4304,4294],{"class":330},[306,4306,4307],{"class":343},"max",[306,4309,764],{"class":330},[306,4311,4312],{"class":312},"254",[306,4314,3479],{"class":330},[306,4316,4317],{"class":308,"line":391},[306,4318,661],{"class":330},[306,4320,4321],{"class":308,"line":402},[306,4322,540],{"emptyLinePlaceholder":539},[306,4324,4325,4327,4329,4332,4334,4337,4339,4342,4344,4347],{"class":308,"line":593},[306,4326,327],{"class":326},[306,4328,3275],{"class":326},[306,4330,4331],{"class":343}," defineEventHandler",[306,4333,764],{"class":330},[306,4335,4336],{"class":326},"async",[306,4338,906],{"class":330},[306,4340,4341],{"class":879},"event",[306,4343,3354],{"class":330},[306,4345,4346],{"class":326},"=>",[306,4348,1080],{"class":330},[306,4350,4351,4354,4357,4359,4362,4365,4368,4370,4372,4374,4377],{"class":308,"line":599},[306,4352,4353],{"class":326},"  const",[306,4355,4356],{"class":330}," { ",[306,4358,4302],{"class":312},[306,4360,4361],{"class":330}," } ",[306,4363,4364],{"class":326},"=",[306,4366,4367],{"class":330}," schema.",[306,4369,3767],{"class":343},[306,4371,764],{"class":330},[306,4373,1103],{"class":326},[306,4375,4376],{"class":343}," readBody",[306,4378,4379],{"class":330},"(event))\n",[306,4381,4382,4384,4387,4389,4392,4395],{"class":308,"line":610},[306,4383,4353],{"class":326},[306,4385,4386],{"class":312}," env",[306,4388,551],{"class":326},[306,4390,4391],{"class":330}," event.context.cloudflare.env ",[306,4393,4394],{"class":326},"as",[306,4396,1080],{"class":330},[306,4398,4399,4402,4404],{"class":308,"line":620},[306,4400,4401],{"class":879},"    EMAIL",[306,4403,882],{"class":326},[306,4405,1090],{"class":343},[306,4407,4408,4411,4413],{"class":308,"line":626},[306,4409,4410],{"class":879},"    EMAIL_FROM",[306,4412,882],{"class":326},[306,4414,3260],{"class":312},[306,4416,4417],{"class":308,"line":637},[306,4418,941],{"class":330},[306,4420,4421],{"class":308,"line":647},[306,4422,540],{"emptyLinePlaceholder":539},[306,4424,4425],{"class":308,"line":658},[306,4426,4428],{"class":4427},"sJ8bj","  \u002F\u002F 实际项目还应在这里执行同源校验、IP\u002F邮箱限流，\n",[306,4430,4431],{"class":308,"line":664},[306,4432,4433],{"class":4427},"  \u002F\u002F 并在 D1 中原子领取一次发送资格。\n",[306,4435,4436,4438,4440,4442,4444,4446,4448,4450,4452],{"class":308,"line":669},[306,4437,4353],{"class":326},[306,4439,4100],{"class":312},[306,4441,551],{"class":326},[306,4443,679],{"class":326},[306,4445,1106],{"class":330},[306,4447,1109],{"class":312},[306,4449,1000],{"class":330},[306,4451,1114],{"class":343},[306,4453,560],{"class":330},[306,4455,4456],{"class":308,"line":690},[306,4457,4458],{"class":330},"    from: {\n",[306,4460,4461,4464,4467],{"class":308,"line":701},[306,4462,4463],{"class":330},"      email: env.",[306,4465,4466],{"class":312},"EMAIL_FROM",[306,4468,571],{"class":330},[306,4470,4471,4474,4476],{"class":308,"line":712},[306,4472,4473],{"class":330},"      name: ",[306,4475,4137],{"class":319},[306,4477,571],{"class":330},[306,4479,4480],{"class":308,"line":723},[306,4481,4482],{"class":330},"    },\n",[306,4484,4485],{"class":308,"line":734},[306,4486,4487],{"class":330},"    to: email,\n",[306,4489,4490,4493,4496],{"class":308,"line":745},[306,4491,4492],{"class":330},"    subject: ",[306,4494,4495],{"class":319},"'验证邮箱'",[306,4497,571],{"class":330},[306,4499,4500,4503,4506],{"class":308,"line":750},[306,4501,4502],{"class":330},"    text: ",[306,4504,4505],{"class":319},"'验证码将在 5 分钟后失效。'",[306,4507,571],{"class":330},[306,4509,4510,4513,4516],{"class":308,"line":755},[306,4511,4512],{"class":330},"    html: ",[306,4514,4515],{"class":319},"'\u003Cp>验证码将在 \u003Cstrong>5 分钟\u003C\u002Fstrong>后失效。\u003C\u002Fp>'",[306,4517,571],{"class":330},[306,4519,4520],{"class":308,"line":773},[306,4521,4522],{"class":330},"  })\n",[306,4524,4525],{"class":308,"line":779},[306,4526,540],{"emptyLinePlaceholder":539},[306,4528,4529,4532],{"class":308,"line":790},[306,4530,4531],{"class":326},"  return",[306,4533,1080],{"class":330},[306,4535,4536,4539,4541],{"class":308,"line":800},[306,4537,4538],{"class":330},"    ok: ",[306,4540,588],{"class":312},[306,4542,571],{"class":330},[306,4544,4546],{"class":308,"line":4545},29,[306,4547,4548],{"class":330},"    messageId: result.messageId,\n",[306,4550,4552],{"class":308,"line":4551},30,[306,4553,941],{"class":330},[306,4555,4557],{"class":308,"line":4556},31,[306,4558,661],{"class":330},[16,4560,4561],{},"不要把这个示例直接变成一个匿名、无限频率、可自由填写主题和正文的公开 API。那会把自己的域名变成垃圾邮件跳板。",[16,4563,4564],{},[219,4565],{"alt":4566,"src":4567},"事务邮件从 Worker 生成，经过多层身份认证后投递到不同收件系统","\u002Fmedia\u002Fposts\u002Fcloudflare-email-service-send-receive-guide\u002Foutbound-delivery-v1.webp",[16,4569,4570],{},[226,4571,4572],{},"SPF、DKIM 与 DMARC 解决的是“谁有权发送、内容是否被修改、失败时如何处置”，并不保证每封邮件都进入主收件箱。",[60,4574,4576],{"id":4575},"rest-api-与认证-smtp-什么时候更合适","REST API 与认证 SMTP 什么时候更合适",[16,4578,4579],{},"Cloudflare 目前提供三种发送入口：",[64,4581,4582,4595],{},[67,4583,4584],{},[70,4585,4586,4589,4592],{},[73,4587,4588],{},"方式",[73,4590,4591],{},"最适合",[73,4593,4594],{},"凭证与边界",[80,4596,4597,4608,4618],{},[70,4598,4599,4602,4605],{},[85,4600,4601],{},"Workers binding",[85,4603,4604],{},"已运行在 Workers 的 Nuxt、Hono、Agent",[85,4606,4607],{},"不在代码中保存 API Token，可通过 binding 限制发件人与收件人",[70,4609,4610,4612,4615],{},[85,4611,1195],{},[85,4613,4614],{},"运行在其他云、CI 或后端平台的程序",[85,4616,4617],{},"使用最小权限 API Token，响应会按收件人区分 delivered、queued、permanent bounce",[70,4619,4620,4622,4625],{},[85,4621,1203],{},[85,4623,4624],{},"已经使用 Nodemailer、PHPMailer、JavaMail 等 SMTP 客户端的旧系统",[85,4626,4627,4629],{},[20,4628,35],{},"，仅隐式 TLS，密码是有 Email Sending 权限的 API Token",[16,4631,4632],{},"认证 SMTP 是 2026 年 6 月加入的 Beta 能力。当前只支持 465 端口的 SMTPS，不提供 587 端口 STARTTLS，也不是可以匿名使用的 25 端口 relay。",[16,4634,4635],{},"SMTP Token 一旦泄漏，持有者可以使用账户中已接入的发件域发送邮件。因此：",[233,4637,4638,4641,4646,4649,4652],{},[163,4639,4640],{},"优先使用 account-owned token；",[163,4642,4643,4644,168],{},"权限只给 ",[20,4645,132],{},[163,4647,4648],{},"不写进仓库、镜像或前端环境变量；",[163,4650,4651],{},"不同系统使用不同 Token，便于单独吊销；",[163,4653,4654],{},"能用 Worker binding 的场景，不要为了“熟悉 SMTP”额外引入长期凭证。",[60,4656,4657],{"id":4657},"必须知道的限制",[16,4659,4660],{},"截至 2026 年 7 月，官方平台限制中最值得在代码里提前防守的是：",[64,4662,4663,4677],{},[67,4664,4665],{},[70,4666,4667,4670,4674],{},[73,4668,4669],{},"限制",[73,4671,4673],{"align":4672},"right","当前值",[73,4675,4676],{},"实现影响",[80,4678,4679,4690,4700,4711,4722,4733,4743,4754,4765],{},[70,4680,4681,4684,4687],{},[85,4682,4683],{},"每域 Email Routing 规则",[85,4685,4686],{"align":4672},"200",[85,4688,4689],{},"大量租户不要为每个人创建独立静态规则",[70,4691,4692,4695,4697],{},[85,4693,4694],{},"每账户已验证目标地址",[85,4696,4686],{"align":4672},[85,4698,4699],{},"转发到多邮箱前先规划账户级目标",[70,4701,4702,4705,4708],{},[85,4703,4704],{},"入站邮件大小",[85,4706,4707],{"align":4672},"25 MiB",[85,4709,4710],{},"更大邮件在 SMTP 阶段被拒绝",[70,4712,4713,4716,4719],{},[85,4714,4715],{},"单封出站收件人数",[85,4717,4718],{"align":4672},"50",[85,4720,4721],{},"To、Cc、Bcc 合计",[70,4723,4724,4727,4730],{},[85,4725,4726],{},"普通出站邮件总大小",[85,4728,4729],{"align":4672},"5 MiB",[85,4731,4732],{},"包含附件与 MIME 编码开销",[70,4734,4735,4738,4740],{},[85,4736,4737],{},"发往已验证目标的总大小",[85,4739,4707],{"align":4672},[85,4741,4742],{},"仅适用于已验证目标地址",[70,4744,4745,4748,4751],{},[85,4746,4747],{},"自定义头总大小",[85,4749,4750],{"align":4672},"16 KB",[85,4752,4753],{},"不要把业务数据塞进 Header",[70,4755,4756,4759,4762],{},[85,4757,4758],{},"单个 Zone 的发送\u002F路由域名总数",[85,4760,4761],{"align":4672},"30",[85,4763,4764],{},"根域与子域都计入",[70,4766,4767,4773,4776],{},[85,4768,4769,4770,4772],{},"回复的 ",[20,4771,3902],{}," 项数",[85,4774,4775],{"align":4672},"100",[85,4777,4778,4779,4782],{},"超过后 ",[20,4780,4781],{},"reply()"," 会失败",[16,4784,4785],{},"附件会经过 Base64 和 MIME 封装。3 MiB 的原始附件不等于最终邮件仍是 3 MiB，所以应用限制应给编码、正文和邮件头预留空间。",[16,4787,4788],{},"平台还会根据账户信誉、投递表现和使用历史调整每日发送额度。不要假设“每月额度未用完”就等于某一秒可以无限并发发送。",[60,4790,4791],{"id":4791},"验证码与密码重置应该怎样做",[16,4793,4794],{},"本博客的注册和密码重置邮件采用的是下面这条链路：",[194,4796,4799],{"className":4797,"code":4798,"language":199,"meta":200},[197],"用户提交邮箱\n  → 同源检查\n  → IP 与邮箱限流\n  → 生成 6 位验证码\n  → D1 只保存加盐哈希、用途、过期时间和尝试次数\n  → EMAIL binding 发送\n  → 验证成功后原子标记已使用\n",[20,4800,4798],{"__ignoreMap":200},[16,4802,4803],{},"关键策略包括：",[233,4805,4806,4809,4812,4815,4818,4821,4824],{},[163,4807,4808],{},"同一邮箱 5 分钟只能发送一封；",[163,4810,4811],{},"验证码 5 分钟后失效；",[163,4813,4814],{},"验证码只能使用一次；",[163,4816,4817],{},"D1 不保存验证码明文；",[163,4819,4820],{},"连续输错达到阈值后停止验证；",[163,4822,4823],{},"邮件发送失败时释放本次占位，避免用户被一个未送达的验证码锁住；",[163,4825,4826],{},"重置密码后撤销已有登录会话，而不是只替换密码哈希。",[16,4828,4829,4830,4833],{},"限流要在数据库中“原子领取资格”，不能写成“先查询上次发送时间，再插入一行”。两个并发请求可能同时通过查询。使用唯一键配合 ",[20,4831,4832],{},"INSERT ... ON CONFLICT ... WHERE","，才能让数据库决定谁获得发送资格。",[16,4835,4836],{},"邮件接口的响应也不应暴露“这个邮箱是否已经注册”。否则攻击者可以批量枚举用户。",[60,4838,4839],{"id":4839},"一个更完整的生产架构",[16,4841,4842],{},"当邮件从 Demo 进入生产，推荐把职责拆开：",[194,4844,4847],{"className":4845,"code":4846,"language":199,"meta":200},[197],"HTTP 请求 \u002F 业务事件\n  → 鉴权、同源、参数校验、限流、幂等\n  → Queue\n  → 邮件模板渲染\n  → EMAIL binding\n  → messageId \u002F 错误码写入 D1\n  → Email Service 日志与告警\n",[20,4848,4846],{"__ignoreMap":200},[16,4850,4851],{},"入站链路则可以是：",[194,4853,4856],{"className":4854,"code":4855,"language":199,"meta":200},[197],"support@yourdomain.com\n  → Email Routing\n  → Email Worker\n  → 认证与大小检查\n  → MIME 解析\n  → R2 保存附件 \u002F D1 建立工单\n  → Queue 做耗时处理\n  → 转发人工邮箱或发送受约束回复\n",[20,4857,4855],{"__ignoreMap":200},[186,4859,4861],{"id":4860},"d1-建议记录什么","D1 建议记录什么",[16,4863,4864],{},"可以记录：",[233,4866,4867,4870,4873,4877,4880,4883,4886],{},[163,4868,4869],{},"业务事件 ID 与幂等键；",[163,4871,4872],{},"模板 ID 和模板版本；",[163,4874,4875,168],{},[20,4876,3017],{},[163,4878,4879],{},"收件人数，不一定要保存完整地址；",[163,4881,4882],{},"accepted \u002F failed 等应用侧状态；",[163,4884,4885],{},"Cloudflare 错误码；",[163,4887,4888],{},"创建、发送和最后更新时间。",[16,4890,4891],{},"不建议默认记录：",[233,4893,4894,4897,4900,4903,4906,4909],{},[163,4895,4896],{},"验证码明文；",[163,4898,4899],{},"密码重置 Token；",[163,4901,4902],{},"完整邮件正文；",[163,4904,4905],{},"附件内容；",[163,4907,4908],{},"SMTP\u002FAPI Token；",[163,4910,4911],{},"为调试方便长期保留的原始来信。",[16,4913,4914],{},"日志本身也可能成为新的隐私数据源。",[186,4916,4918],{"id":4917},"不要把-email-routing-的-dropped-当成发送失败","不要把 Email Routing 的 “Dropped” 当成发送失败",[16,4920,4921,4922,4924,4925,4928],{},"官方特别说明：通过 ",[20,4923,1063],{}," binding 发出的邮件，在 Email Routing 汇总页面可能显示为 ",[41,4926,4927],{},"dropped","，即使邮件实际已经成功投递。",[16,4930,4931,4932,4934],{},"出站结果应该查看 Email Sending 的 Metrics 与 Logs，并用 ",[20,4933,3017],{}," 关联应用日志。Routing 页面描述的是入站路由处理，不是完整的出站投递状态机。",[60,4936,4937],{"id":4937},"本地怎么测试",[16,4939,4940],{},"Wrangler 已支持本地触发 Email Worker。运行：",[194,4942,4944],{"className":300,"code":4943,"language":302,"meta":200,"style":200},"pnpm exec wrangler dev\n",[20,4945,4946],{"__ignoreMap":200},[306,4947,4948,4950,4953,4956],{"class":308,"line":309},[306,4949,472],{"class":343},[306,4951,4952],{"class":319}," exec",[306,4954,4955],{"class":319}," wrangler",[306,4957,4958],{"class":319}," dev\n",[16,4960,4961],{},"然后向本地入口发送一封原始邮件：",[194,4963,4965],{"className":300,"code":4964,"language":302,"meta":200,"style":200},"curl -X POST \\\n  'http:\u002F\u002Flocalhost:8787\u002Fcdn-cgi\u002Fhandler\u002Femail?from=sender@example.net&to=support@example.com' \\\n  -H 'Content-Type: text\u002Fplain' \\\n  --data-binary $'From: sender@example.net\\nTo: support@example.com\\nSubject: Local test\\n\\nHello from local development.'\n",[20,4966,4967,4979,4986,4996],{"__ignoreMap":200},[306,4968,4969,4971,4974,4977],{"class":308,"line":309},[306,4970,344],{"class":343},[306,4972,4973],{"class":312}," -X",[306,4975,4976],{"class":319}," POST",[306,4978,353],{"class":312},[306,4980,4981,4984],{"class":308,"line":323},[306,4982,4983],{"class":319},"  'http:\u002F\u002Flocalhost:8787\u002Fcdn-cgi\u002Fhandler\u002Femail?from=sender@example.net&to=support@example.com'",[306,4985,353],{"class":312},[306,4987,4988,4991,4994],{"class":308,"line":363},[306,4989,4990],{"class":312},"  -H",[306,4992,4993],{"class":319}," 'Content-Type: text\u002Fplain'",[306,4995,353],{"class":312},[306,4997,4998,5001,5004,5007,5010,5012,5015,5018],{"class":308,"line":380},[306,4999,5000],{"class":312},"  --data-binary",[306,5002,5003],{"class":319}," $'From: sender@example.net",[306,5005,5006],{"class":312},"\\n",[306,5008,5009],{"class":319},"To: support@example.com",[306,5011,5006],{"class":312},[306,5013,5014],{"class":319},"Subject: Local test",[306,5016,5017],{"class":312},"\\n\\n",[306,5019,5020],{"class":319},"Hello from local development.'\n",[16,5022,5023],{},"本地发送 binding 会把文本和 HTML 保存成可检查的文件，适合验证模板。二进制附件的本地模拟仍有单独限制，涉及附件时应补一轮远程测试。",[16,5025,5026],{},"至少覆盖这些用例：",[233,5028,5029,5032,5035,5038,5041,5044,5047,5050,5053,5056],{},[163,5030,5031],{},"正常文本和 HTML；",[163,5033,5034],{},"非 ASCII 主题与发件人显示名；",[163,5036,5037],{},"重复请求与幂等；",[163,5039,5040],{},"超过发送冷却时间前的重复验证码；",[163,5042,5043],{},"大正文与附件边界；",[163,5045,5046],{},"Header 换行注入；",[163,5048,5049],{},"未验证发件域或受限收件人；",[163,5051,5052],{},"退信、抑制、额度和频率错误；",[163,5054,5055],{},"入站无匹配地址、Catch-all 和拒绝路径；",[163,5057,5058],{},"HTML 邮件中的脚本、远程资源与危险附件名。",[60,5060,5062],{"id":5061},"它适合什么不适合什么","它适合什么，不适合什么",[16,5064,5065],{},"适合：",[233,5067,5068,5071,5074,5077,5080,5083],{},[163,5069,5070],{},"注册验证、魔法链接、密码重置；",[163,5072,5073],{},"订单、账单、部署、监控通知；",[163,5075,5076],{},"个人域名邮件转发；",[163,5078,5079],{},"联系表单与支持工单；",[163,5081,5082],{},"邮件触发的 Agent 或自动化流程；",[163,5084,5085],{},"希望少维护一台 SMTP 服务的 Cloudflare 应用。",[16,5087,5088],{},"不应直接拿来做：",[233,5090,5091,5094,5097,5100,5103],{},[163,5092,5093],{},"未经同意的营销群发；",[163,5095,5096],{},"没有退订、投诉和抑制管理的 Newsletter；",[163,5098,5099],{},"匿名开放的“帮我发邮件”接口；",[163,5101,5102],{},"需要 IMAP 文件夹、日历、通讯录和多人邮箱协作的企业邮箱；",[163,5104,5105],{},"把任何来信正文直接交给有高权限工具的 Agent。",[16,5107,5108],{},"Cloudflare 替你维护了邮件入口与投递基础设施，但发件人信誉、用户授权、内容安全、频率控制和数据最小化仍然是应用自己的责任。",[60,5110,5111],{"id":5111},"最后的判断",[16,5113,5114],{},"Cloudflare Email Service 最有价值的地方，不是“又多了一个 SMTP 厂商”，而是它让邮件和 Worker、D1、R2、Queue、Agents 处于同一运行边界。",[16,5116,5117],{},"接收邮件不再只能转发到人类邮箱，它可以成为一个受约束的事件入口；发送邮件也不再需要为验证码单独接入一个外部 SDK 和密钥体系。",[16,5119,5120],{},"如果项目本来就部署在 Workers，优先从这条最小链路开始：",[160,5122,5123,5129,5132,5135,5138],{},[163,5124,5125,5126,168],{},"Email Routing 接收 ",[20,5127,5128],{},"support@",[163,5130,5131],{},"Worker binding 发送验证码和密码重置；",[163,5133,5134],{},"D1 实现冷却、一次性验证和最小日志；",[163,5136,5137],{},"sender allowlist 限制 binding 权限；",[163,5139,5140],{},"用 Email Sending 日志核对投递，而不是只看接口返回成功。",[16,5142,5143],{},"等确实需要从外部平台发送时，再引入 REST API；只有现有系统已经深度依赖 SMTP 客户端时，才使用认证 SMTP。",[60,5145,1613],{"id":1613},[233,5147,5148,5155,5161,5168,5175,5182,5189,5196,5202,5207,5213,5220,5227],{},[163,5149,5150],{},[46,5151,5154],{"href":5152,"rel":5153},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Femail-service\u002F",[50],"Cloudflare Email Service 概览",[163,5156,5157],{},[46,5158,5160],{"href":48,"rel":5159},[50],"接入 Email Sending",[163,5162,5163],{},[46,5164,5167],{"href":5165,"rel":5166},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Femail-service\u002Fget-started\u002Froute-emails\u002F",[50],"接入 Email Routing",[163,5169,5170],{},[46,5171,5174],{"href":5172,"rel":5173},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Femail-service\u002Fconfiguration\u002Fdomains\u002F",[50],"域名与 DNS 配置",[163,5176,5177],{},[46,5178,5181],{"href":5179,"rel":5180},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Femail-service\u002Fconfiguration\u002Fsend-bindings\u002F",[50],"发送 binding 权限配置",[163,5183,5184],{},[46,5185,5188],{"href":5186,"rel":5187},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Femail-service\u002Fapi\u002Fsend-emails\u002Fworkers-api\u002F",[50],"Workers 发送 API",[163,5190,5191],{},[46,5192,5195],{"href":5193,"rel":5194},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Femail-service\u002Fapi\u002Froute-emails\u002Femail-handler\u002F",[50],"Email Worker 接收 API",[163,5197,5198],{},[46,5199,1195],{"href":5200,"rel":5201},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Femail-service\u002Fapi\u002Fsend-emails\u002Frest-api\u002F",[50],[163,5203,5204],{},[46,5205,1203],{"href":55,"rel":5206},[50],[163,5208,5209],{},[46,5210,5212],{"href":1327,"rel":5211},[50],"平台限制",[163,5214,5215],{},[46,5216,5219],{"href":5217,"rel":5218},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Femail-service\u002Fplatform\u002Fpricing\u002F",[50],"价格说明",[163,5221,5222],{},[46,5223,5226],{"href":5224,"rel":5225},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Femail-service\u002Fconcepts\u002Femail-authentication\u002F",[50],"邮件认证与投递",[163,5228,5229],{},[46,5230,5233],{"href":5231,"rel":5232},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Femail-service\u002Flocal-development\u002Frouting\u002F",[50],"本地测试 Email Worker",[1641,5235,5236],{},"html pre.shiki code .sOPea, html code.shiki .sOPea{--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .sFR8T, html code.shiki .sFR8T{--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .suv1-, html code.shiki .suv1-{--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .s-3mD, html code.shiki .s-3mD{--shiki-default:#FFAB70;--shiki-dark:#FFAB70}html pre.shiki code .s8ozJ, html code.shiki .s8ozJ{--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .s4wv1, html code.shiki .s4wv1{--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":200,"searchDepth":363,"depth":363,"links":5238},[5239,5240,5244,5249,5255,5259,5260,5261,5262,5266,5267,5268,5269],{"id":2963,"depth":323,"text":2964},{"id":3049,"depth":323,"text":3050,"children":5241},[5242,5243],{"id":3056,"depth":363,"text":3057},{"id":3088,"depth":363,"text":3089},{"id":3115,"depth":323,"text":3116,"children":5245},[5246,5247],{"id":3122,"depth":363,"text":3122},{"id":3142,"depth":363,"text":5248},"发送邮件使用 cf-bounce 记录",{"id":3199,"depth":323,"text":3200,"children":5250},[5251,5253,5254],{"id":3224,"depth":363,"text":5252},"email() 是入站邮件入口",{"id":3692,"depth":363,"text":3693},{"id":3853,"depth":363,"text":3854},{"id":3909,"depth":323,"text":3910,"children":5256},[5257,5258],{"id":4026,"depth":363,"text":4027},{"id":4241,"depth":363,"text":4242},{"id":4575,"depth":323,"text":4576},{"id":4657,"depth":323,"text":4657},{"id":4791,"depth":323,"text":4791},{"id":4839,"depth":323,"text":4839,"children":5263},[5264,5265],{"id":4860,"depth":363,"text":4861},{"id":4917,"depth":363,"text":4918},{"id":4937,"depth":323,"text":4937},{"id":5061,"depth":323,"text":5062},{"id":5111,"depth":323,"text":5111},{"id":1613,"depth":323,"text":1613},"截至 2026 年 7 月，系统介绍 Cloudflare Email Service 如何接收、转发、解析、回复和发送事务邮件，并给出 Nuxt、Workers、D1 与安全限流实践。",{"src":5272,"alt":5273,"caption":5274,"width":5275,"height":5276},"\u002Fmedia\u002Fposts\u002Fcloudflare-email-service-send-receive-guide\u002Fcover-v1.webp","邮件经过全球边缘网络中的 Worker 节点完成接收、认证与发送","Cloudflare Email Service 正在把邮件从一台需要维护的服务器，变成一种可以由代码处理的事件",1600,900,{},"\u002Fblog\u002Fcloudflare-email-service-send-receive-guide","2026-07-28",{"title":2902,"description":5270},"blog\u002Fcloudflare-email-service-send-receive-guide",[1678,1680,5283,5284],"Workers","Serverless",{"src":5286,"poster":5272,"title":5287,"description":5288,"duration":5289,"width":5290,"height":5291},"\u002Fmedia\u002Fvideos\u002Fcloudflare-email-service-explained\u002Fcloudflare-email-service-explained-v1.mp4","Cloudflare Email Service 收发实战：11 分钟配置讲解","从 DNS、Email Routing、Email Worker 和 send_email binding，一路讲到 REST、SMTP、Nuxt、D1、五分钟验证码与生产检查。",661,1920,1080,"zwVK7CWRUUu0g0CQd16Yfb4nLJKXBlJDs5p51n9c49g",{"id":5294,"title":5295,"accent":5296,"body":5297,"description":6986,"draft":1663,"experience":1664,"extension":1665,"featured":539,"image":6987,"meta":6991,"navigation":539,"path":6992,"publishedAt":6993,"readingTime":701,"seo":6994,"stem":6995,"tags":6996,"updatedAt":1664,"video":1664,"__hash__":7000,"contentLocale":4,"isFallback":1663},"posts_zh\u002Fblog\u002Fvue-3-6-rc-upgrade-guide.md","Vue 3.6 RC 升级全景：从 3.5 到 Vapor Mode 与 alien-signals","mint",{"type":13,"value":5298,"toc":6947},[5299,5305,5318,5321,5330,5334,5474,5477,5482,5486,5489,5524,5527,5533,5537,5544,5547,5660,5663,5669,5674,5677,5680,5693,5699,5702,5705,5722,5725,5728,5745,5762,5765,5769,5772,5775,5783,5786,5790,5793,5918,5921,5949,5977,5980,5984,5987,6037,6040,6044,6051,6112,6115,6118,6124,6130,6133,6137,6140,6154,6163,6210,6217,6220,6232,6235,6239,6242,6379,6386,6392,6395,6482,6485,6519,6522,6535,6538,6542,6545,6548,6551,6555,6558,6562,6565,6568,6571,6575,6578,6592,6599,6616,6619,6645,6648,6652,6655,6675,6678,6682,6685,6708,6711,6715,6718,6721,6741,6744,6748,6751,6755,6758,6762,6765,6769,6772,6775,6857,6859,6862,6870,6873,6880,6883,6886,6889,6939,6944],[16,5300,5301,5302,58],{},"如果只看版本号，Vue 3.6 像是一次普通的次版本升级。但真正读完发布说明后，我的感受是：",[41,5303,5304],{},"它更像 Vue 为下一阶段准备的一次“换发动机”",[16,5306,5307,5308,1036,5311,1036,5314,5317],{},"一边是基于 alien-signals 重构的响应式内核，公开的 ",[20,5309,5310],{},"ref",[20,5312,5313],{},"computed",[20,5315,5316],{},"watch"," 用法基本不变；另一边是正式进入 RC 阶段的 Vapor Mode，它让 Vue 单文件组件可以绕过虚拟 DOM，编译为更直接的 DOM 更新逻辑。",[16,5319,5320],{},"不过，先把最重要的版本信息放在前面：",[28,5322,5323],{},[16,5324,5325,5326,5329],{},"截至 2026 年 7 月 26 日，Vue 3.6 的最新版本是 ",[41,5327,5328],{},"3.6.0-rc.2","。RC 是候选发布版，不是稳定版。本文适合用来评估、试验和准备迁移，不代表所有生产项目都应该立即升级。",[60,5331,5333],{"id":5332},"先看结论36-改变了什么","先看结论：3.6 改变了什么",[64,5335,5336,5352],{},[67,5337,5338],{},[70,5339,5340,5343,5346,5349],{},[73,5341,5342],{},"对比维度",[73,5344,5345],{},"Vue 3.5",[73,5347,5348],{},"Vue 3.6 RC",[73,5350,5351],{},"对现有项目的实际影响",[80,5353,5354,5368,5382,5396,5410,5424,5442,5460],{},[70,5355,5356,5359,5362,5365],{},[85,5357,5358],{},"响应式系统",[85,5360,5361],{},"3.5 已做过内部优化",[85,5363,5364],{},"基于 alien-signals 大幅重构",[85,5366,5367],{},"常规 Composition API 写法通常不需要重写，但应回归复杂副作用与调度逻辑",[70,5369,5370,5373,5376,5379],{},[85,5371,5372],{},"默认渲染模式",[85,5374,5375],{},"Virtual DOM",[85,5377,5378],{},"仍然是 Virtual DOM",[85,5380,5381],{},"单纯升级依赖不会让应用自动进入 Vapor",[70,5383,5384,5387,5390,5393],{},[85,5385,5386],{},"Vapor Mode",[85,5388,5389],{},"尚未作为 3.5 的正式能力交付",[85,5391,5392],{},"功能集进入 RC，100% 显式启用",[85,5394,5395],{},"可以从单个性能敏感区域开始试验",[70,5397,5398,5401,5404,5407],{},[85,5399,5400],{},"应用体积",[85,5402,5403],{},"默认包含 VDOM runtime",[85,5405,5406],{},"纯 Vapor 应用可不引入 VDOM runtime",[85,5408,5409],{},"只有纯 Vapor 或边界清晰的场景，体积收益才容易兑现",[70,5411,5412,5415,5418,5421],{},[85,5413,5414],{},"API 覆盖",[85,5416,5417],{},"完整 Vue 3 API 生态",[85,5419,5420],{},"Vapor 支持 Vue API 的一个子集",[85,5422,5423],{},"Options API、实例代理相关能力不能直接迁移",[70,5425,5426,5429,5432,5439],{},[85,5427,5428],{},"VDOM \u002F Vapor 混用",[85,5430,5431],{},"不适用",[85,5433,5434,5435,5438],{},"通过 ",[20,5436,5437],{},"vaporInteropPlugin"," 互操作",[85,5440,5441],{},"标准 props、事件、slots 已覆盖，但复杂嵌套仍需测试",[70,5443,5444,5447,5450,5457],{},[85,5445,5446],{},"事件委托",[85,5448,5449],{},"原生监听语义",[85,5451,5452,5453,5456],{},"rc.2 起默认直接绑定；",[20,5454,5455],{},".delegate"," 才显式委托",[85,5458,5459],{},"不要沿用 rc.1 的默认委托认知",[70,5461,5462,5465,5468,5471],{},[85,5463,5464],{},"适合生产吗",[85,5466,5467],{},"稳定版",[85,5469,5470],{},"当前仍是预发布版",[85,5472,5473],{},"生产项目应等待稳定版和上层生态明确支持",[16,5475,5476],{},"一句话概括：",[16,5478,5479],{},[41,5480,5481],{},"Vue 3.5 的亮点更接近“开发体验和 SSR 能力升级”，Vue 3.6 的核心则是“响应式内核与渲染架构升级”。",[60,5483,5485],{"id":5484},"从-35-到-36变化重心已经不同","从 3.5 到 3.6：变化重心已经不同",[16,5487,5488],{},"Vue 3.5 没有破坏性变化，它带来的能力大多能直接被业务开发感知：",[233,5490,5491,5494,5500,5506,5512,5515,5521],{},[163,5492,5493],{},"响应式 Props 解构正式稳定；",[163,5495,5496,5499],{},[20,5497,5498],{},"useTemplateRef()"," 改善模板引用的组织方式；",[163,5501,5502,5505],{},[20,5503,5504],{},"onWatcherCleanup()"," 让 watcher 清理逻辑更自然；",[163,5507,5508,5511],{},[20,5509,5510],{},"useId()"," 提供 SSR 友好的稳定 ID；",[163,5513,5514],{},"异步组件支持懒激活（Lazy Hydration）；",[163,5516,5517,5520],{},[20,5518,5519],{},"data-allow-mismatch"," 可以声明允许的 hydration 差异；",[163,5522,5523],{},"Deferred Teleport 与自定义元素能力得到增强。",[16,5525,5526],{},"3.6 当然也包含大量修复，但它最值得关注的两件事——alien-signals 与 Vapor——都位于框架的底层。你可能不会在业务代码里多写一个新 API，却会面对新的性能特征、编译产物和运行时边界。",[16,5528,5529,5530],{},"这也决定了升级思路：",[41,5531,5532],{},"不要只检查“代码能不能编译”，还要检查响应式行为、SSR hydration、组件互操作和真实性能。",[60,5534,5536],{"id":5535},"变化一响应式内核切换到-alien-signals","变化一：响应式内核切换到 alien-signals",[16,5538,5539,5540,5543],{},"Vue 3.6 对 ",[20,5541,5542],{},"@vue\u002Freactivity"," 做了大规模重构，其基础来自 StackBlitz 开源的 alien-signals。",[16,5545,5546],{},"对于绝大多数业务开发者，下面这些代码仍然是熟悉的 Vue：",[194,5548,5550],{"className":516,"code":5549,"language":518,"meta":200,"style":200},"import { computed, ref, watchEffect } from 'vue'\n\nconst price = ref(99)\nconst count = ref(2)\nconst total = computed(() => price.value * count.value)\n\nwatchEffect(() => {\n  console.log(total.value)\n})\n",[20,5551,5552,5564,5568,5587,5605,5631,5635,5646,5656],{"__ignoreMap":200},[306,5553,5554,5556,5559,5561],{"class":308,"line":309},[306,5555,525],{"class":326},[306,5557,5558],{"class":330}," { computed, ref, watchEffect } ",[306,5560,531],{"class":326},[306,5562,5563],{"class":319}," 'vue'\n",[306,5565,5566],{"class":308,"line":323},[306,5567,540],{"emptyLinePlaceholder":539},[306,5569,5570,5572,5575,5577,5580,5582,5585],{"class":308,"line":363},[306,5571,545],{"class":326},[306,5573,5574],{"class":312}," price",[306,5576,551],{"class":326},[306,5578,5579],{"class":343}," ref",[306,5581,764],{"class":330},[306,5583,5584],{"class":312},"99",[306,5586,936],{"class":330},[306,5588,5589,5591,5594,5596,5598,5600,5603],{"class":308,"line":380},[306,5590,545],{"class":326},[306,5592,5593],{"class":312}," count",[306,5595,551],{"class":326},[306,5597,5579],{"class":343},[306,5599,764],{"class":330},[306,5601,5602],{"class":312},"2",[306,5604,936],{"class":330},[306,5606,5607,5609,5612,5614,5617,5620,5622,5625,5628],{"class":308,"line":391},[306,5608,545],{"class":326},[306,5610,5611],{"class":312}," total",[306,5613,551],{"class":326},[306,5615,5616],{"class":343}," computed",[306,5618,5619],{"class":330},"(() ",[306,5621,4346],{"class":326},[306,5623,5624],{"class":330}," price.value ",[306,5626,5627],{"class":326},"*",[306,5629,5630],{"class":330}," count.value)\n",[306,5632,5633],{"class":308,"line":402},[306,5634,540],{"emptyLinePlaceholder":539},[306,5636,5637,5640,5642,5644],{"class":308,"line":593},[306,5638,5639],{"class":343},"watchEffect",[306,5641,5619],{"class":330},[306,5643,4346],{"class":326},[306,5645,1080],{"class":330},[306,5647,5648,5651,5653],{"class":308,"line":599},[306,5649,5650],{"class":330},"  console.",[306,5652,3442],{"class":343},[306,5654,5655],{"class":330},"(total.value)\n",[306,5657,5658],{"class":308,"line":610},[306,5659,661],{"class":330},[16,5661,5662],{},"变化主要发生在内部：依赖如何建立、状态如何标记、计算属性何时失效、effect 如何沿依赖图传播，以及这些关系如何被清理和复用。",[16,5664,5665],{},[219,5666],{"alt":5667,"src":5668},"由活跃与休眠路径组成的细粒度响应式依赖网络","\u002Fmedia\u002Fposts\u002Fvue-3-6-rc-upgrade-guide\u002Freactivity-core-v1.webp",[16,5670,5671],{},[226,5672,5673],{},"这张图表达的是依赖传播的概念，不是 Vue 内部数据结构的逐字段示意。",[186,5675,5676],{"id":5676},"性能数字应该怎样理解",[16,5678,5679],{},"Vue 核心仓库的 alien-signals 合并 PR 给出过两类微基准结果：",[233,5681,5682,5690],{},[163,5683,5684,5685,1036,5687,5689],{},"在大量 ",[20,5686,5310],{},[20,5688,5313],{}," 和 effect 实例的构造场景中，示例内存从约 2.3 MB 降至约 2.0 MB，约减少 13%；",[163,5691,5692],{},"在“一个源变化后，大量 computed 被读取”的特定压力场景中，性能提升可以超过 30 倍。",[16,5694,5695,5696],{},"第二个数字很亮眼，但不能直接翻译成“Vue 3.6 应用快 30 倍”。它证明的是：",[41,5697,5698],{},"新的依赖传播算法在某些过去成本会随规模显著上升的图结构中，复杂度表现更好。",[16,5700,5701],{},"真实页面还包含 DOM、布局、网络、组件库、序列化和业务计算。升级前后的判断应基于自己项目的 flame chart、交互延迟、内存曲线和打包结果，而不是套用框架微基准。",[186,5703,5704],{"id":5704},"哪些项目最可能感受到收益",[233,5706,5707,5710,5713,5716,5719],{},[163,5708,5709],{},"大量 computed 相互依赖的数据看板；",[163,5711,5712],{},"高频修改、筛选和派生状态很多的编辑器；",[163,5714,5715],{},"长时间运行、创建和销毁大量 effect 的工作台；",[163,5717,5718],{},"表单字段多、验证规则和联动关系复杂的应用；",[163,5720,5721],{},"基于 Vue 响应式原语构建的状态管理或工具库。",[16,5723,5724],{},"普通内容页也能得到内部改进，但用户未必会感知到明显差异。",[186,5726,5727],{"id":5727},"谁需要格外谨慎",[16,5729,5730,5731,1036,5733,1036,5736,1036,5738,5740,5741,5744],{},"如果代码只使用公开的 ",[20,5732,5310],{},[20,5734,5735],{},"reactive",[20,5737,5313],{},[20,5739,5316],{}," 与 ",[20,5742,5743],{},"effectScope","，迁移风险相对可控。真正需要警惕的是：",[233,5746,5747,5750,5753,5756,5759],{},[163,5748,5749],{},"直接读取或修改 Vue 响应式内部字段；",[163,5751,5752],{},"依赖未公开的 effect、Dep 或 scheduler 实现细节；",[163,5754,5755],{},"自行 monkey patch 响应式函数；",[163,5757,5758],{},"对 watcher 执行顺序、同步刷新和清理时机有隐含假设；",[163,5760,5761],{},"库代码同时支持多个 Vue 次版本，却没有覆盖这些边界测试。",[16,5763,5764],{},"内部重构的意义，正是 Vue 可以在保持公开 API 的同时更换实现。反过来说，依赖内部实现的代码也最容易在这类升级中暴露问题。",[60,5766,5768],{"id":5767},"变化二vapor-mode-不再只是实验概念","变化二：Vapor Mode 不再只是实验概念",[16,5770,5771],{},"Vapor Mode 是 Vue SFC 的一种新编译模式。传统 Vue 模板会生成 VNode，由 Virtual DOM runtime 对比前后两棵树，再把差异更新到真实 DOM；Vapor 则让编译器提前识别动态部分，生成更直接、粒度更细的更新逻辑。",[16,5773,5774],{},"它的目标有两个：",[160,5776,5777,5780],{},[163,5778,5779],{},"降低应用的基础包体积；",[163,5781,5782],{},"减少运行时创建、遍历和比较 VNode 的成本。",[16,5784,5785],{},"官方发布说明称，Vapor 已在第三方 js-framework-benchmark 中达到与 Solid、Svelte 5 相近的级别。这个结论同样应该按“基准场景”理解，而不是把不同框架、不同应用的真实性能压缩成一条排名。",[186,5787,5789],{"id":5788},"vapor-不会在升级后自动开启","Vapor 不会在升级后自动开启",[16,5791,5792],{},"Vue 3.6 依然默认使用 VDOM。要让一个 SFC 使用 Vapor，需要显式标记：",[194,5794,5798],{"className":5795,"code":5796,"language":5797,"meta":200,"style":200},"language-vue shiki shiki-themes github-dark github-dark","\u003Cscript setup vapor lang=\"ts\">\nimport { ref } from 'vue'\n\nconst count = ref(0)\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cbutton @click=\"count++\">\n    Count: {{ count }}\n  \u003C\u002Fbutton>\n\u003C\u002Ftemplate>\n","vue",[20,5799,5800,5824,5835,5839,5856,5865,5869,5878,5896,5901,5910],{"__ignoreMap":200},[306,5801,5802,5804,5808,5811,5814,5817,5819,5822],{"class":308,"line":309},[306,5803,3306],{"class":330},[306,5805,5807],{"class":5806},"sxg3X","script",[306,5809,5810],{"class":343}," setup",[306,5812,5813],{"class":343}," vapor",[306,5815,5816],{"class":343}," lang",[306,5818,4364],{"class":330},[306,5820,5821],{"class":319},"\"ts\"",[306,5823,3570],{"class":330},[306,5825,5826,5828,5831,5833],{"class":308,"line":323},[306,5827,525],{"class":326},[306,5829,5830],{"class":330}," { ref } ",[306,5832,531],{"class":326},[306,5834,5563],{"class":319},[306,5836,5837],{"class":308,"line":363},[306,5838,540],{"emptyLinePlaceholder":539},[306,5840,5841,5843,5845,5847,5849,5851,5854],{"class":308,"line":380},[306,5842,545],{"class":326},[306,5844,5593],{"class":312},[306,5846,551],{"class":326},[306,5848,5579],{"class":343},[306,5850,764],{"class":330},[306,5852,5853],{"class":312},"0",[306,5855,936],{"class":330},[306,5857,5858,5861,5863],{"class":308,"line":391},[306,5859,5860],{"class":330},"\u003C\u002F",[306,5862,5807],{"class":5806},[306,5864,3570],{"class":330},[306,5866,5867],{"class":308,"line":402},[306,5868,540],{"emptyLinePlaceholder":539},[306,5870,5871,5873,5876],{"class":308,"line":593},[306,5872,3306],{"class":330},[306,5874,5875],{"class":5806},"template",[306,5877,3570],{"class":330},[306,5879,5880,5883,5886,5889,5891,5894],{"class":308,"line":599},[306,5881,5882],{"class":330},"  \u003C",[306,5884,5885],{"class":5806},"button",[306,5887,5888],{"class":343}," @click",[306,5890,4364],{"class":330},[306,5892,5893],{"class":319},"\"count++\"",[306,5895,3570],{"class":330},[306,5897,5898],{"class":308,"line":610},[306,5899,5900],{"class":330},"    Count: {{ count }}\n",[306,5902,5903,5906,5908],{"class":308,"line":620},[306,5904,5905],{"class":330},"  \u003C\u002F",[306,5907,5885],{"class":5806},[306,5909,3570],{"class":330},[306,5911,5912,5914,5916],{"class":308,"line":626},[306,5913,5860],{"class":330},[306,5915,5875],{"class":5806},[306,5917,3570],{"class":330},[16,5919,5920],{},"官方还支持两个等价入口：",[194,5922,5924],{"className":5795,"code":5923,"language":5797,"meta":200,"style":200},"\u003Cscript vapor>\n\u002F\u002F \u003Cscript setup vapor> 的简写\n\u003C\u002Fscript>\n",[20,5925,5926,5936,5941],{"__ignoreMap":200},[306,5927,5928,5930,5932,5934],{"class":308,"line":309},[306,5929,3306],{"class":330},[306,5931,5807],{"class":5806},[306,5933,5813],{"class":343},[306,5935,3570],{"class":330},[306,5937,5938],{"class":308,"line":323},[306,5939,5940],{"class":4427},"\u002F\u002F \u003Cscript setup vapor> 的简写\n",[306,5942,5943,5945,5947],{"class":308,"line":363},[306,5944,5860],{"class":330},[306,5946,5807],{"class":5806},[306,5948,3570],{"class":330},[194,5950,5952],{"className":5795,"code":5951,"language":5797,"meta":200,"style":200},"\u003Ctemplate vapor>\n  \u003C!-- 整个 SFC 使用 Vapor 编译 -->\n\u003C\u002Ftemplate>\n",[20,5953,5954,5964,5969],{"__ignoreMap":200},[306,5955,5956,5958,5960,5962],{"class":308,"line":309},[306,5957,3306],{"class":330},[306,5959,5875],{"class":5806},[306,5961,5813],{"class":343},[306,5963,3570],{"class":330},[306,5965,5966],{"class":308,"line":323},[306,5967,5968],{"class":4427},"  \u003C!-- 整个 SFC 使用 Vapor 编译 -->\n",[306,5970,5971,5973,5975],{"class":308,"line":363},[306,5972,5860],{"class":330},[306,5974,5875],{"class":5806},[306,5976,3570],{"class":330},[16,5978,5979],{},"也就是说，团队可以升级到 3.6 后继续运行现有 VDOM 应用，再有选择地迁移某个组件或页面。",[186,5981,5983],{"id":5982},"纯-vapor-应用","纯 Vapor 应用",[16,5985,5986],{},"如果应用完全由 Vapor 组件组成，可以使用：",[194,5988,5990],{"className":516,"code":5989,"language":518,"meta":200,"style":200},"import { createVaporApp } from 'vue'\nimport App from '.\u002FApp.vue'\n\ncreateVaporApp(App).mount('#app')\n",[20,5991,5992,6003,6015,6019],{"__ignoreMap":200},[306,5993,5994,5996,5999,6001],{"class":308,"line":309},[306,5995,525],{"class":326},[306,5997,5998],{"class":330}," { createVaporApp } ",[306,6000,531],{"class":326},[306,6002,5563],{"class":319},[306,6004,6005,6007,6010,6012],{"class":308,"line":323},[306,6006,525],{"class":326},[306,6008,6009],{"class":330}," App ",[306,6011,531],{"class":326},[306,6013,6014],{"class":319}," '.\u002FApp.vue'\n",[306,6016,6017],{"class":308,"line":363},[306,6018,540],{"emptyLinePlaceholder":539},[306,6020,6021,6024,6027,6030,6032,6035],{"class":308,"line":380},[306,6022,6023],{"class":343},"createVaporApp",[306,6025,6026],{"class":330},"(App).",[306,6028,6029],{"class":343},"mount",[306,6031,764],{"class":330},[306,6033,6034],{"class":319},"'#app'",[306,6036,936],{"class":330},[16,6038,6039],{},"这种模式不需要引入 Virtual DOM runtime，最容易获得基础包体积收益。它更适合小型新应用、交互小工具，或可以明确约束技术栈的独立页面。",[186,6041,6043],{"id":6042},"在现有-vdom-应用中使用-vapor","在现有 VDOM 应用中使用 Vapor",[16,6045,6046,6047,6050],{},"现有 ",[20,6048,6049],{},"createApp()"," 项目需要安装互操作插件：",[194,6052,6054],{"className":516,"code":6053,"language":518,"meta":200,"style":200},"import { createApp, vaporInteropPlugin } from 'vue'\nimport App from '.\u002FApp.vue'\n\ncreateApp(App)\n  .use(vaporInteropPlugin)\n  .mount('#app')\n",[20,6055,6056,6067,6077,6081,6089,6100],{"__ignoreMap":200},[306,6057,6058,6060,6063,6065],{"class":308,"line":309},[306,6059,525],{"class":326},[306,6061,6062],{"class":330}," { createApp, vaporInteropPlugin } ",[306,6064,531],{"class":326},[306,6066,5563],{"class":319},[306,6068,6069,6071,6073,6075],{"class":308,"line":323},[306,6070,525],{"class":326},[306,6072,6009],{"class":330},[306,6074,531],{"class":326},[306,6076,6014],{"class":319},[306,6078,6079],{"class":308,"line":363},[306,6080,540],{"emptyLinePlaceholder":539},[306,6082,6083,6086],{"class":308,"line":380},[306,6084,6085],{"class":343},"createApp",[306,6087,6088],{"class":330},"(App)\n",[306,6090,6091,6094,6097],{"class":308,"line":391},[306,6092,6093],{"class":330},"  .",[306,6095,6096],{"class":343},"use",[306,6098,6099],{"class":330},"(vaporInteropPlugin)\n",[306,6101,6102,6104,6106,6108,6110],{"class":308,"line":402},[306,6103,6093],{"class":330},[306,6105,6029],{"class":343},[306,6107,764],{"class":330},[306,6109,6034],{"class":319},[306,6111,936],{"class":330},[16,6113,6114],{},"反过来，Vapor 应用也能安装这个插件以容纳 VDOM 组件，但这样会重新带入 VDOM runtime，削弱体积优势。",[16,6116,6117],{},"Render Function 与 JSX 编写的组件仍然属于 VDOM 组件。在纯 Vapor 应用中使用它们，同样需要互操作。",[16,6119,6120],{},[219,6121],{"alt":6122,"src":6123},"现有 VDOM 区域通过明确的互操作边界连接到独立 Vapor 区域","\u002Fmedia\u002Fposts\u002Fvue-3-6-rc-upgrade-guide\u002Fvapor-boundaries-v1.webp",[16,6125,6126,6127],{},"我的建议与官方一致：",[41,6128,6129],{},"把两种渲染模式组织成边界清楚的区域，而不是在每一层组件中来回套娃。",[16,6131,6132],{},"例如，把一个高频更新的数据画布做成 Vapor 页面或子树，周围的后台导航、弹窗和成熟组件库继续使用 VDOM。这样的收益、故障范围和回退路径都更容易测量。",[60,6134,6136],{"id":6135},"rc2-最容易踩坑的变化事件委托改为显式启用","rc.2 最容易踩坑的变化：事件委托改为显式启用",[16,6138,6139],{},"如果你在 rc.1 阶段已经试过 Vapor，这一节尤其重要。",[16,6141,6142,6143,6146,6147,6150,6151,6153],{},"rc.1 的说明中，符合条件的事件会自动委托到 ",[20,6144,6145],{},"document","。但这与原生 DOM 和标准 Vue 的行为存在差异：如果祖先元素提前调用 ",[20,6148,6149],{},"stopPropagation()","，事件到不了 ",[20,6152,6145],{},"，子元素被委托的 handler 也不会执行。",[16,6155,6156,6157,6159,6160,6162],{},"从 ",[41,6158,5328],{}," 开始，Vapor 默认把 DOM 监听器直接绑定到元素。只有明确希望使用文档级委托时，才添加 Vapor 专用的 ",[20,6161,5455],{}," 修饰符：",[194,6164,6166],{"className":5795,"code":6165,"language":5797,"meta":200,"style":200},"\u003Cbutton @click.delegate=\"onClick\">\n  保存\n\u003C\u002Fbutton>\n",[20,6167,6168,6197,6202],{"__ignoreMap":200},[306,6169,6170,6172,6174,6177,6180,6182,6185,6187,6190,6193,6195],{"class":308,"line":309},[306,6171,3306],{"class":330},[306,6173,5885],{"class":5806},[306,6175,6176],{"class":330}," @",[306,6178,6179],{"class":343},"click",[306,6181,1000],{"class":330},[306,6183,6184],{"class":343},"delegate",[306,6186,4364],{"class":330},[306,6188,6189],{"class":319},"\"",[306,6191,6192],{"class":330},"onClick",[306,6194,6189],{"class":319},[306,6196,3570],{"class":330},[306,6198,6199],{"class":308,"line":323},[306,6200,6201],{"class":330},"  保存\n",[306,6203,6204,6206,6208],{"class":308,"line":363},[306,6205,5860],{"class":330},[306,6207,5885],{"class":5806},[306,6209,3570],{"class":330},[16,6211,6212,6213,6216],{},"同时，旧的 ",[20,6214,6215],{},"compilerOptions.eventDelegation"," 已被移除。",[16,6218,6219],{},"迁移时应该做两件事：",[160,6221,6222,6227],{},[163,6223,6224,6225,168],{},"删除已有的 ",[20,6226,6215],{},[163,6228,6229,6230,58],{},"只对数量大、事件稳定，并且传播路径已验证的监听器使用 ",[20,6231,5455],{},[16,6233,6234],{},"这不是一个适合全局机械替换的优化。事件是否值得委托，要结合节点数量、生命周期和传播语义判断。",[60,6236,6238],{"id":6237},"vapor-与标准-vue-的兼容边界","Vapor 与标准 Vue 的兼容边界",[16,6240,6241],{},"Vapor 的目标不是在第一天覆盖 Vue 的所有历史能力。官方明确说明，它支持现有 Vue API 的一个子集；依赖 VNode 或组件公开实例代理的功能，目前不能直接使用。",[64,6243,6244,6256],{},[67,6245,6246],{},[70,6247,6248,6250,6253],{},[73,6249,3589],{},[73,6251,6252],{},"VDOM 组件",[73,6254,6255],{},"Vapor 组件",[80,6257,6258,6269,6279,6290,6306,6317,6329,6341,6368],{},[70,6259,6260,6263,6266],{},[85,6261,6262],{},"Composition API",[85,6264,6265],{},"支持",[85,6267,6268],{},"主要编程方式",[70,6270,6271,6274,6276],{},[85,6272,6273],{},"Options API",[85,6275,6265],{},[85,6277,6278],{},"不支持",[70,6280,6281,6286,6288],{},[85,6282,6283],{},[20,6284,6285],{},"app.config.globalProperties",[85,6287,6265],{},[85,6289,6278],{},[70,6291,6292,6297,6300],{},[85,6293,6294],{},[20,6295,6296],{},"getCurrentInstance()",[85,6298,6299],{},"返回当前实例",[85,6301,6302,6303],{},"返回 ",[20,6304,6305],{},"null",[70,6307,6308,6311,6314],{},[85,6309,6310],{},"Render Function \u002F JSX",[85,6312,6313],{},"原生 VDOM",[85,6315,6316],{},"仍是 VDOM，混用需要 interop",[70,6318,6319,6324,6326],{},[85,6320,6321],{},[20,6322,6323],{},"v-memo",[85,6325,6265],{},[85,6327,6328],{},"不适用 \u002F 不支持",[70,6330,6331,6337,6339],{},[85,6332,6333,6336],{},[20,6334,6335],{},"@vue:xxx"," 元素生命周期事件",[85,6338,6265],{},[85,6340,6278],{},[70,6342,6343,6346,6349],{},[85,6344,6345],{},"组件模板引用",[85,6347,6348],{},"暴露公开实例能力",[85,6350,6351,6352,1036,6355,1036,6358,1036,6361,1036,6364,6367],{},"不暴露 ",[20,6353,6354],{},"$el",[20,6356,6357],{},"$props",[20,6359,6360],{},"$attrs",[20,6362,6363],{},"$slots",[20,6365,6366],{},"$refs"," 等属性",[70,6369,6370,6373,6376],{},[85,6371,6372],{},"自定义指令",[85,6374,6375],{},"标准指令生命周期对象",[85,6377,6378],{},"使用 Vapor 专用函数接口",[186,6380,6382,6385],{"id":6381},"slotsdefault-不能再当作无副作用探测",[20,6383,6384],{},"slots.default()"," 不能再当作无副作用探测",[16,6387,6388,6389,6391],{},"在 VDOM 世界中，一些组件会先调用 ",[20,6390,6384],{},"，检查返回内容，再决定渲染 fallback。Vapor 中调用 slot 可能直接创建 Block 和 DOM、注册响应式 effect，或在 hydration 时认领已有 SSR DOM。",[16,6393,6394],{},"因此不要这样写：",[194,6396,6398],{"className":5795,"code":6397,"language":5797,"meta":200,"style":200},"\u003Cscript setup vapor>\nimport { useSlots } from 'vue'\n\nconst slots = useSlots()\nconst content = slots.default?.()\nconst showFallback = !content\n\u003C\u002Fscript>\n",[20,6399,6400,6412,6423,6427,6441,6459,6474],{"__ignoreMap":200},[306,6401,6402,6404,6406,6408,6410],{"class":308,"line":309},[306,6403,3306],{"class":330},[306,6405,5807],{"class":5806},[306,6407,5810],{"class":343},[306,6409,5813],{"class":343},[306,6411,3570],{"class":330},[306,6413,6414,6416,6419,6421],{"class":308,"line":323},[306,6415,525],{"class":326},[306,6417,6418],{"class":330}," { useSlots } ",[306,6420,531],{"class":326},[306,6422,5563],{"class":319},[306,6424,6425],{"class":308,"line":363},[306,6426,540],{"emptyLinePlaceholder":539},[306,6428,6429,6431,6434,6436,6439],{"class":308,"line":380},[306,6430,545],{"class":326},[306,6432,6433],{"class":312}," slots",[306,6435,551],{"class":326},[306,6437,6438],{"class":343}," useSlots",[306,6440,3331],{"class":330},[306,6442,6443,6445,6448,6450,6453,6456],{"class":308,"line":391},[306,6444,545],{"class":326},[306,6446,6447],{"class":312}," content",[306,6449,551],{"class":326},[306,6451,6452],{"class":330}," slots.",[306,6454,6455],{"class":343},"default",[306,6457,6458],{"class":330},"?.()\n",[306,6460,6461,6463,6466,6468,6471],{"class":308,"line":402},[306,6462,545],{"class":326},[306,6464,6465],{"class":312}," showFallback",[306,6467,551],{"class":326},[306,6469,6470],{"class":326}," !",[306,6472,6473],{"class":330},"content\n",[306,6475,6476,6478,6480],{"class":308,"line":593},[306,6477,5860],{"class":330},[306,6479,5807],{"class":5806},[306,6481,3570],{"class":330},[16,6483,6484],{},"让模板负责 slot 的实际渲染，通常更安全：",[194,6486,6488],{"className":5795,"code":6487,"language":5797,"meta":200,"style":200},"\u003Ctemplate>\n  \u003Cslot \u002F>\n\u003C\u002Ftemplate>\n",[20,6489,6490,6498,6511],{"__ignoreMap":200},[306,6491,6492,6494,6496],{"class":308,"line":309},[306,6493,3306],{"class":330},[306,6495,5875],{"class":5806},[306,6497,3570],{"class":330},[306,6499,6500,6502,6505,6509],{"class":308,"line":323},[306,6501,5882],{"class":330},[306,6503,6504],{"class":5806},"slot",[306,6506,6508],{"class":6507},"sX7ps"," \u002F",[306,6510,3570],{"class":330},[306,6512,6513,6515,6517],{"class":308,"line":363},[306,6514,5860],{"class":330},[306,6516,5875],{"class":5806},[306,6518,3570],{"class":330},[186,6520,6521],{"id":6521},"自定义指令需要单独迁移",[16,6523,6524,6525,1036,6528,1036,6531,6534],{},"Vapor 自定义指令接收节点和响应式 getter，并且可以返回 cleanup 函数。它不是把 VDOM 指令的 ",[20,6526,6527],{},"mounted",[20,6529,6530],{},"updated",[20,6532,6533],{},"unmounted"," 对象原样搬过去。",[16,6536,6537],{},"如果项目或组件库依赖大量自定义指令，应该把它们列成独立迁移项，逐个验证响应式更新、清理和 SSR 行为。",[60,6539,6541],{"id":6540},"nuxtrouterpinia-和组件库应该怎么看","Nuxt、Router、Pinia 和组件库应该怎么看",[16,6543,6544],{},"Vue 核心可用，不代表上层框架已经自动完成 Vapor 集成。",[16,6546,6547],{},"Vue 官方的 Vapor Roadmap 单独列出了 Router、Pinia、Nuxt、DevTools 和 Vue Test Utils 等生态工作，其中 Nuxt 还依赖 SSR hydration 能力。对真实项目来说，编译器能否识别 Vapor、服务端产物如何生成、客户端如何 hydration、开发工具怎样检查组件，都是完整链路的一部分。",[16,6549,6550],{},"因此我会把项目分成两类：",[186,6552,6554],{"id":6553},"普通-vue-vite-应用","普通 Vue \u002F Vite 应用",[16,6556,6557],{},"可以在分支中升级 3.6 RC，先验证现有 VDOM 模式，再挑一个叶子页面试用 Vapor。依赖关系比较直接，问题也更容易归因。",[186,6559,6561],{"id":6560},"nuxtssr-或重度组件库应用","Nuxt、SSR 或重度组件库应用",[16,6563,6564],{},"不要仅通过 package override 强行把底层 Vue 换成 RC，就把它视为完成 Vapor 升级。应该先确认当前框架、构建插件和 SSR 链路明确支持对应版本，再进行 hydration、路由切换、异步组件和组件库互操作测试。",[16,6566,6567],{},"即使暂时不使用 Vapor，测试 Vue 3.6 的新响应式内核仍然有价值；只是这类试验最好留在独立分支或测试环境。",[60,6569,6570],{"id":6570},"一套更稳妥的升级路线",[186,6572,6574],{"id":6573},"第一阶段只升级依赖不启用-vapor","第一阶段：只升级依赖，不启用 Vapor",[16,6576,6577],{},"普通 Vue \u002F Vite 项目可以在实验分支中安装 RC：",[194,6579,6581],{"className":300,"code":6580,"language":302,"meta":200,"style":200},"pnpm add vue@3.6.0-rc.2\n",[20,6582,6583],{"__ignoreMap":200},[306,6584,6585,6587,6589],{"class":308,"line":309},[306,6586,472],{"class":343},[306,6588,475],{"class":319},[306,6590,6591],{"class":319}," vue@3.6.0-rc.2\n",[16,6593,6594,6595,6598],{},"如果项目直接依赖 ",[20,6596,6597],{},"@vue\u002Fserver-renderer","，让它与 Vue 保持同一版本：",[194,6600,6602],{"className":300,"code":6601,"language":302,"meta":200,"style":200},"pnpm add vue@3.6.0-rc.2 @vue\u002Fserver-renderer@3.6.0-rc.2\n",[20,6603,6604],{"__ignoreMap":200},[306,6605,6606,6608,6610,6613],{"class":308,"line":309},[306,6607,472],{"class":343},[306,6609,475],{"class":319},[306,6611,6612],{"class":319}," vue@3.6.0-rc.2",[306,6614,6615],{"class":319}," @vue\u002Fserver-renderer@3.6.0-rc.2\n",[16,6617,6618],{},"然后先验证原有 VDOM 应用：",[233,6620,6621,6624,6627,6630,6633,6636,6639,6642],{},[163,6622,6623],{},"单元测试与组件测试；",[163,6625,6626],{},"首屏 SSR 与客户端 hydration；",[163,6628,6629],{},"watch、computed、effectScope 的复杂场景；",[163,6631,6632],{},"快速路由切换后的副作用清理；",[163,6634,6635],{},"自定义指令、Teleport、Suspense 和异步组件；",[163,6637,6638],{},"开发与生产构建；",[163,6640,6641],{},"浏览器控制台 warning 和错误；",[163,6643,6644],{},"关键交互的耗时与内存基线。",[16,6646,6647],{},"如果这一阶段失败，问题大概率来自新响应式实现或 RC 回归，而不是 Vapor，定位会简单得多。",[186,6649,6651],{"id":6650},"第二阶段选择一个边界清楚的区域","第二阶段：选择一个边界清楚的区域",[16,6653,6654],{},"合适的试点通常同时满足：",[233,6656,6657,6660,6663,6666,6669,6672],{},[163,6658,6659],{},"使用 Composition API；",[163,6661,6662],{},"很少依赖组件实例；",[163,6664,6665],{},"不依赖复杂的 VDOM 组件库；",[163,6667,6668],{},"更新频率高，确实存在性能压力；",[163,6670,6671],{},"有独立路由或明确的组件树边界；",[163,6673,6674],{},"有可重复的性能测试。",[16,6676,6677],{},"先记录迁移前的 JavaScript 体积、交互延迟、CPU 时间和内存，再启用 Vapor。没有基线，就很容易把“技术上成功运行”误判成“值得迁移”。",[186,6679,6681],{"id":6680},"第三阶段验证互操作与-ssr","第三阶段：验证互操作与 SSR",[16,6683,6684],{},"至少覆盖这些组合：",[160,6686,6687,6690,6693,6696,6699,6702,6705],{},[163,6688,6689],{},"VDOM 父组件嵌套 Vapor 子组件；",[163,6691,6692],{},"Vapor 父组件嵌套 VDOM 子组件；",[163,6694,6695],{},"props、emit 与双向绑定；",[163,6697,6698],{},"默认 slot、具名 slot 与作用域 slot；",[163,6700,6701],{},"Suspense、异步组件与错误边界；",[163,6703,6704],{},"服务端输出、hydration 和 mismatch 恢复；",[163,6706,6707],{},"组件卸载后的 watcher、事件与指令 cleanup。",[16,6709,6710],{},"rc.2 的修复列表中有相当多内容正好集中在 hydration、Suspense、slot anchor 和 VDOM interop。这说明这些区域正在快速收敛，也说明它们是当前最值得测试的地方。",[186,6712,6714],{"id":6713},"第四阶段等稳定版再决定生产节奏","第四阶段：等稳定版再决定生产节奏",[16,6716,6717],{},"RC 的意义是功能集已经接近确定，接下来集中发现回归和边缘问题；它不等于生态已经完全稳定。",[16,6719,6720],{},"生产项目至少应该同时满足：",[233,6722,6723,6726,6729,6732,6735,6738],{},[163,6724,6725],{},"Vue 3.6 正式版已发布；",[163,6727,6728],{},"使用的框架和构建工具明确支持；",[163,6730,6731],{},"核心组件库通过互操作验证；",[163,6733,6734],{},"DevTools、测试工具和 SSR 链路可用；",[163,6736,6737],{},"性能收益足以覆盖迁移与维护成本；",[163,6739,6740],{},"有随时退回 VDOM 的方案。",[60,6742,6743],{"id":6743},"不建议做的四件事",[186,6745,6747],{"id":6746},"_1-不要把-rc-当成普通补丁版","1. 不要把 RC 当成普通补丁版",[16,6749,6750],{},"预发布阶段的行为仍可能调整。rc.1 到 rc.2 的事件委托变化，就是最直接的例子。",[186,6752,6754],{"id":6753},"_2-不要为了用上-vapor迁移全部组件","2. 不要为了“用上 Vapor”迁移全部组件",[16,6756,6757],{},"导航、静态内容、低频设置页未必能获得有意义的收益。优先迁移性能瓶颈，而不是追求迁移比例。",[186,6759,6761],{"id":6760},"_3-不要把微基准数字当作应用承诺","3. 不要把微基准数字当作应用承诺",[16,6763,6764],{},"“超过 30 倍”来自特定响应式压力场景；“与 Solid、Svelte 5 同级”来自第三方基准。它们适合证明技术方向，不适合替代项目测量。",[186,6766,6768],{"id":6767},"_4-不要忽略生态边界","4. 不要忽略生态边界",[16,6770,6771],{},"Vue 核心、SFC 编译器、Vite 插件、Nuxt、SSR、Router、Pinia、组件库、DevTools 和测试工具共同组成开发体验。只让其中一个包进入 RC 版本，不代表整条链路已经升级完毕。",[60,6773,6774],{"id":6774},"我的项目类型建议",[64,6776,6777,6789],{},[67,6778,6779],{},[70,6780,6781,6784,6787],{},[73,6782,6783],{},"项目类型",[73,6785,6786],{},"现在是否值得试",[73,6788,1392],{},[80,6790,6791,6802,6813,6824,6835,6846],{},[70,6792,6793,6796,6799],{},[85,6794,6795],{},"小型新工具、内部实验",[85,6797,6798],{},"值得",[85,6800,6801],{},"可以尝试纯 Vapor，重点测包体积与交互",[70,6803,6804,6807,6810],{},[85,6805,6806],{},"已有 Vue SPA",[85,6808,6809],{},"值得在分支试",[85,6811,6812],{},"先保持 VDOM，再迁移单个性能敏感页面",[70,6814,6815,6818,6821],{},[85,6816,6817],{},"Nuxt \u002F SSR 站点",[85,6819,6820],{},"适合研究，生产宜等待",[85,6822,6823],{},"先确认框架支持，重点测 hydration",[70,6825,6826,6829,6832],{},[85,6827,6828],{},"组件库",[85,6830,6831],{},"应尽早做兼容测试",[85,6833,6834],{},"检查实例代理、slots、自定义指令和 interop",[70,6836,6837,6840,6843],{},[85,6838,6839],{},"大型数据看板 \u002F 编辑器",[85,6841,6842],{},"很值得压测",[85,6844,6845],{},"单独比较 alien-signals 与 Vapor 各自带来的收益",[70,6847,6848,6851,6854],{},[85,6849,6850],{},"内容型官网",[85,6852,6853],{},"优先级较低",[85,6855,6856],{},"等稳定版和生态成熟，避免为有限收益增加复杂度",[60,6858,5111],{"id":5111},[16,6860,6861],{},"Vue 3.6 RC 最有价值的地方，不是提供了多少个可以立刻写进业务的新 API，而是同时推进了两个长期问题：",[233,6863,6864,6867],{},[163,6865,6866],{},"Vue 的响应式系统能否在更复杂的依赖图中保持高效；",[163,6868,6869],{},"Vue 的模板能否在保留熟悉开发体验的同时，摆脱必须依赖虚拟 DOM 的成本。",[16,6871,6872],{},"alien-signals 回答第一个问题，Vapor 尝试回答第二个问题。",[16,6874,6875,6876,6879],{},"对于现有项目，我认为最合理的策略是：",[41,6877,6878],{},"先把 3.6 当作响应式内核升级来测试，再把 Vapor 当作可选的局部编译模式来评估。"," 两件事分开验证，才能知道性能变化来自哪里，也能把风险控制在清晰的边界内。",[16,6881,6882],{},"Vue 3.6 还没有到“所有人今天就应该升级”的阶段，但它已经到了“值得所有 Vue 团队开始理解”的阶段。",[6884,6885],"hr",{},[60,6887,6888],{"id":6888},"资料说明",[233,6890,6891,6899,6907,6915,6923,6931],{},[163,6892,6893,6898],{},[46,6894,6897],{"href":6895,"rel":6896},"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fblob\u002Fminor\u002FCHANGELOG.md",[50],"Vue 3.6 minor 分支 Changelog","：3.6.0-rc.1 的 Vapor 总览、兼容边界，以及 rc.2 的事件委托变更。",[163,6900,6901,6906],{},[46,6902,6905],{"href":6903,"rel":6904},"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Freleases\u002Ftag\u002Fv3.6.0-rc.2",[50],"Vue 3.6.0-rc.2 Release","：本文发布时的最新候选版本。",[163,6908,6909,6914],{},[46,6910,6913],{"href":6911,"rel":6912},"https:\u002F\u002Fblog.vuejs.org\u002Fposts\u002Fvue-3-5",[50],"Vue 3.5 发布说明","：Vue 3.5 的响应式优化、SSR 与开发者 API 基线。",[163,6916,6917,6922],{},[46,6918,6921],{"href":6919,"rel":6920},"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fpull\u002F12349",[50],"Vue core：alien-signals 重构 PR","：响应式实现、内存与特定压力场景微基准。",[163,6924,6925,6930],{},[46,6926,6929],{"href":6927,"rel":6928},"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fissues\u002F13687",[50],"Vapor Mode Roadmap","：核心能力与 Vue 生态集成路线。",[163,6932,6933,6938],{},[46,6934,6937],{"href":6935,"rel":6936},"https:\u002F\u002Fgithub.com\u002Fkrausest\u002Fjs-framework-benchmark",[50],"js-framework-benchmark","：Vue 官方发布说明引用的第三方前端框架基准。",[28,6940,6941],{},[16,6942,6943],{},"本文中的升级建议是我基于官方资料和工程经验做出的判断，文章由 AI 协助资料整理与语言校对；具体项目仍应以自身测试结果为准。",[1641,6945,6946],{},"html pre.shiki code .sOPea, html code.shiki .sOPea{--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .suv1-, html code.shiki .suv1-{--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .s4wv1, html code.shiki .s4wv1{--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html pre.shiki code .s8ozJ, html code.shiki .s8ozJ{--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .sFR8T, html code.shiki .sFR8T{--shiki-default:#B392F0;--shiki-dark:#B392F0}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sxg3X, html code.shiki .sxg3X{--shiki-default:#85E89D;--shiki-dark:#85E89D}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sX7ps, html code.shiki .sX7ps{--shiki-default:#FDAEB7;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic}",{"title":200,"searchDepth":363,"depth":363,"links":6948},[6949,6950,6951,6956,6961,6962,6967,6971,6977,6983,6984,6985],{"id":5332,"depth":323,"text":5333},{"id":5484,"depth":323,"text":5485},{"id":5535,"depth":323,"text":5536,"children":6952},[6953,6954,6955],{"id":5676,"depth":363,"text":5676},{"id":5704,"depth":363,"text":5704},{"id":5727,"depth":363,"text":5727},{"id":5767,"depth":323,"text":5768,"children":6957},[6958,6959,6960],{"id":5788,"depth":363,"text":5789},{"id":5982,"depth":363,"text":5983},{"id":6042,"depth":363,"text":6043},{"id":6135,"depth":323,"text":6136},{"id":6237,"depth":323,"text":6238,"children":6963},[6964,6966],{"id":6381,"depth":363,"text":6965},"slots.default() 不能再当作无副作用探测",{"id":6521,"depth":363,"text":6521},{"id":6540,"depth":323,"text":6541,"children":6968},[6969,6970],{"id":6553,"depth":363,"text":6554},{"id":6560,"depth":363,"text":6561},{"id":6570,"depth":323,"text":6570,"children":6972},[6973,6974,6975,6976],{"id":6573,"depth":363,"text":6574},{"id":6650,"depth":363,"text":6651},{"id":6680,"depth":363,"text":6681},{"id":6713,"depth":363,"text":6714},{"id":6743,"depth":323,"text":6743,"children":6978},[6979,6980,6981,6982],{"id":6746,"depth":363,"text":6747},{"id":6753,"depth":363,"text":6754},{"id":6760,"depth":363,"text":6761},{"id":6767,"depth":363,"text":6768},{"id":6774,"depth":323,"text":6774},{"id":5111,"depth":323,"text":5111},{"id":6888,"depth":323,"text":6888},"截至 Vue 3.6.0-rc.2，系统对比 Vue 3.5 与 3.6 的响应式内核、Vapor Mode、事件委托、互操作边界和渐进升级策略。",{"src":6988,"alt":6989,"caption":6990,"width":5275,"height":5276},"\u002Fmedia\u002Fposts\u002Fvue-3-6-rc-upgrade-guide\u002Fcover-v1.webp","Vue 3.5 的虚拟 DOM 组件树经过晶体通道转向 Vue 3.6 的细粒度信号网络","Vue 3.6 RC 更像一次编译器与运行时的架构换挡，而不是一份新 API 清单",{},"\u002Fblog\u002Fvue-3-6-rc-upgrade-guide","2026-07-26",{"title":5295,"description":6986},"blog\u002Fvue-3-6-rc-upgrade-guide",[6997,6998,6999],"Vue","JavaScript","前端工程","L8sIMfVAKUKqWTcVSvsseRJeOmYbzw7h958oo1EPLCo",{"id":7002,"title":7003,"accent":7004,"body":7005,"description":7239,"draft":1663,"experience":7240,"extension":1665,"featured":539,"image":7245,"meta":7249,"navigation":539,"path":7250,"publishedAt":7251,"readingTime":599,"seo":7252,"stem":7253,"tags":7254,"updatedAt":1664,"video":1664,"__hash__":7258,"contentLocale":4,"isFallback":1663},"posts_zh\u002Fblog\u002Ffrom-gpt-4-invite-to-agents.md","从一封 GPT‑4 邀请邮件，到智能体时代","violet",{"type":13,"value":7006,"toc":7231},[7007,7010,7013,7018,7025,7031,7034,7037,7041,7044,7047,7053,7056,7059,7062,7065,7079,7082,7099,7105,7108,7111,7115,7118,7121,7135,7138,7152,7155,7159,7162,7165,7182,7185,7189,7192,7195,7198,7201,7203,7205],[16,7008,7009],{},"今天整理旧邮件时，我重新翻到了一封 2023 年 3 月 18 日的邮件。",[16,7011,7012],{},"邮件的标题很直接：",[28,7014,7015],{},[16,7016,7017],{},"You’re invited to use the OpenAI GPT-4 API.",[16,7019,7020,7021,7024],{},"正文告诉我，已经可以通过 API 使用拥有 ",[41,7022,7023],{},"8K context"," 的 GPT‑4。",[16,7026,7027],{},[219,7028],{"alt":7029,"src":7030},"2023 年 GPT‑4 API 邀请邮件，正文说明可使用 8K context","\u002Fmedia\u002Fposts\u002Ffrom-gpt-4-invite-to-agents\u002Femail-invite-v1.webp",[16,7032,7033],{},"那时 GPT‑4 才刚发布几天。API 需要申请、等待，再由 OpenAI 逐步邀请开发者。官方发布资料里，标准 GPT‑4 的上下文长度是 8,192 tokens，同时有限开放 32,768-token 版本。对当时的我来说，一次 API 访问资格本身，就像一扇刚刚推开的小门。",[16,7035,7036],{},"三年多以后再看，这封邮件已经不像一封产品通知，更像一枚时间胶囊。",[60,7038,7040],{"id":7039},"数字变大只是最容易看到的变化","数字变大，只是最容易看到的变化",[16,7042,7043],{},"早期开发者熟悉的是 4K、8K 量级的上下文。写提示词时，我们会计算还剩多少空间，主动裁剪资料，把长文拆成小段，再想办法让下一次请求接住上一次的结果。",[16,7045,7046],{},"到了 2026 年，百万级上下文已经进入主流前沿模型。OpenAI 当前模型目录列出的 GPT‑5.6 系列拥有约 1.05M context window；Google 的长上下文文档也说明，多款 Gemini 模型支持 1M 或更多 tokens。",[16,7048,7049],{},[219,7050],{"alt":7051,"src":7052},"从 4K、8K 到 1M 以上，上下文从短纸条延伸为多模态资料长廊","\u002Fmedia\u002Fposts\u002Ffrom-gpt-4-invite-to-agents\u002Fcontext-window-v1.webp",[16,7054,7055],{},"这不只是“能塞进更多文字”。代码、图片、音频、视频和大批文档可以共同成为一次任务的背景。以前必须先切碎、索引和摘要的资料，现在有更多机会被放进同一个工作上下文。",[16,7057,7058],{},"但更长的上下文不等于完美记忆，也不等于模型会自动抓住重点。资料越多，噪声、成本、延迟和错误关联也可能越多。真正重要的能力，仍然是选择什么信息、以什么顺序提供，以及如何验证模型是否理解了关键约束。",[60,7060,7061],{"id":7061},"真正改变的是交互单位",[16,7063,7064],{},"三年前，我最常做的是“提问—回答”：",[160,7066,7067,7070,7073,7076],{},[163,7068,7069],{},"写一段提示词；",[163,7071,7072],{},"等待模型生成；",[163,7074,7075],{},"复制结果；",[163,7077,7078],{},"自己完成剩余工作。",[16,7080,7081],{},"今天，越来越多任务变成了“目标—上下文—行动—验证”：",[160,7083,7084,7087,7090,7093,7096],{},[163,7085,7086],{},"说明最终目标与完成标准；",[163,7088,7089],{},"提供代码库、文档和当前状态；",[163,7091,7092],{},"允许模型调用搜索、文件、终端或其他工具；",[163,7094,7095],{},"让它执行一连串相互依赖的步骤；",[163,7097,7098],{},"用测试、构建、差异检查或人工评审确认结果。",[16,7100,7101],{},[219,7102],{"alt":7103,"src":7104},"聊天框扩展为包含文档、代码、浏览器、工具调用和人工确认的智能体工作流","\u002Fmedia\u002Fposts\u002Ffrom-gpt-4-invite-to-agents\u002Fagent-workflow-v1.webp",[16,7106,7107],{},"OpenAI 当前的模型与 Responses API 文档已经把函数调用、网页搜索、文件搜索和计算机操作列为可用工具。模型不再只告诉我们“可以怎么做”，而是开始在获得授权的范围内参与真实工作过程。",[16,7109,7110],{},"这也是我认为“聊天模型”和“智能体”之间最重要的区别：前者交付一段内容，后者推进一个状态会变化的任务。",[60,7112,7114],{"id":7113},"程序员的工作没有消失而是向两端移动","程序员的工作没有消失，而是向两端移动",[16,7116,7117],{},"当模型可以读代码、改文件、运行命令和检查结果时，程序员并不是突然变得不重要。相反，我们的注意力开始向任务的两端移动。",[16,7119,7120],{},"在行动之前，需要把问题定义得更清楚：",[233,7122,7123,7126,7129,7132],{},[163,7124,7125],{},"目标是什么，什么不在范围内；",[163,7127,7128],{},"哪些资料可信，哪些只是参考；",[163,7130,7131],{},"哪些权限可以授予，哪些操作必须确认；",[163,7133,7134],{},"失败时应该停止、重试，还是回滚。",[16,7136,7137],{},"在行动之后，需要把结果验证得更扎实：",[233,7139,7140,7143,7146,7149],{},[163,7141,7142],{},"代码能否通过测试与构建；",[163,7144,7145],{},"页面是否真的符合预期，而不只是生成成功；",[163,7147,7148],{},"外部状态是否已经改变；",[163,7150,7151],{},"结果能否被解释、复现和审计。",[16,7153,7154],{},"三年前，我们努力学习怎样写出一个更好的提示词。今天，我们还要学习怎样设计上下文、工具、权限、检查点和验收标准。",[60,7156,7158],{"id":7157},"能力可以委托责任不能外包","能力可以委托，责任不能外包",[16,7160,7161],{},"智能体越能执行真实操作，边界就越重要。",[16,7163,7164],{},"让模型总结一篇文章，与让它修改生产数据库，不是同一种授权；让它提出邮件草稿，与让它直接发送给客户，也不是同一种风险。一个成熟的智能体系统，不应该只追求“能做多少”，还应该明确：",[233,7166,7167,7170,7173,7176,7179],{},[163,7168,7169],{},"默认只读还是默认可写；",[163,7171,7172],{},"哪些动作需要人在执行前确认；",[163,7174,7175],{},"如何限制目标、目录、账号和数据范围；",[163,7177,7178],{},"如何保存必要的操作记录；",[163,7180,7181],{},"发生错误时怎样停止并恢复。",[16,7183,7184],{},"模型可以承担越来越多行动，但判断和责任并不会因此消失。最终仍然需要有人回答：为什么要这样做，依据是什么，风险由谁承担，结果是否可以接受。",[60,7186,7188],{"id":7187},"再过三年我们会怎样看今天","再过三年，我们会怎样看今天",[16,7190,7191],{},"这封旧邮件如今很像一张偶然保存下来的车票。",[16,7193,7194],{},"2023 年，我申请的是一次访问模型的机会。2026 年，我们已经在学习如何让模型理解一个长期目标、使用工具、跨越多个步骤，并在人的监督下完成工作。",[16,7196,7197],{},"让我感慨的不是某一个参数增长了多少倍，而是时间被压缩了。许多曾经只存在于想象中的能力，没有在遥远的未来慢慢到来，而是在几次版本更新之间，悄悄变成了日常。",[16,7199,7200],{},"也许再过三年回头看，今天让我们惊叹的百万级上下文和智能体，也会像这封邮件一样，成为未来刚刚开始时留下的一件旧物。",[6884,7202],{},[60,7204,6888],{"id":6888},[233,7206,7207,7215,7223],{},[163,7208,7209,7214],{},[46,7210,7213],{"href":7211,"rel":7212},"https:\u002F\u002Fopenai.com\u002Findex\u002Fgpt-4-research\u002F",[50],"OpenAI：GPT‑4 发布资料","：GPT‑4 于 2023 年 3 月发布，标准上下文为 8,192 tokens，并有限开放 32,768-token 版本。",[163,7216,7217,7222],{},[46,7218,7221],{"href":7219,"rel":7220},"https:\u002F\u002Fdevelopers.openai.com\u002Fapi\u002Fdocs\u002Fmodels",[50],"OpenAI：模型目录","：截至本文发布时，前沿 GPT‑5.6 系列列出约 1.05M context window，并支持多类工具。",[163,7224,7225,7230],{},[46,7226,7229],{"href":7227,"rel":7228},"https:\u002F\u002Fai.google.dev\u002Fgemini-api\u002Fdocs\u002Flong-context",[50],"Google AI：Long context","：介绍 Gemini 模型的 1M+ 长上下文能力、使用场景与限制。",{"title":200,"searchDepth":363,"depth":363,"links":7232},[7233,7234,7235,7236,7237,7238],{"id":7039,"depth":323,"text":7040},{"id":7061,"depth":323,"text":7061},{"id":7113,"depth":323,"text":7114},{"id":7157,"depth":323,"text":7158},{"id":7187,"depth":323,"text":7188},{"id":6888,"depth":323,"text":6888},"2023 年，我等来的是一次 8K context 的 API 访问资格；三年后，我们开始学习如何向智能体交代目标、权限、边界与验收标准。",{"src":7241,"title":7242,"description":7243,"duration":7244,"hasAudio":539},"\u002Fexperiences\u002Ffrom-gpt-4-invite-to-agents","从 GPT-4 邀请到智能体时代 · 六幕互动叙事","用 2 分 26 秒回看上下文、智能体工作流，以及能力与责任的变化。",146,{"src":7246,"alt":7247,"caption":7248,"width":5275,"height":5276},"\u002Fmedia\u002Fposts\u002Ffrom-gpt-4-invite-to-agents\u002Fcover-v1.webp","一封来自 2023 年的邮件从玻璃时间胶囊延伸为现代智能体工作空间","三年前申请访问模型，三年后学习与模型共同工作",{},"\u002Fblog\u002Ffrom-gpt-4-invite-to-agents","2026-07-25",{"title":7003,"description":7239},"blog\u002Ffrom-gpt-4-invite-to-agents",[7255,7256,7257],"AI","GPT-4","智能体","P0HWhOcEJwLRdrvZlwkQks0C-1y9Q98neHDcWHX6Xow",{"id":7260,"title":7261,"accent":11,"body":7262,"description":7697,"draft":1663,"experience":1664,"extension":1665,"featured":539,"image":7698,"meta":7702,"navigation":539,"path":7703,"publishedAt":7704,"readingTime":599,"seo":7705,"stem":7706,"tags":7707,"updatedAt":7709,"video":1664,"__hash__":7710,"contentLocale":4,"isFallback":1663},"posts_zh\u002Fblog\u002Fedge-stateful-apps.md","在边缘构建有状态应用",{"type":13,"value":7263,"toc":7689},[7264,7270,7273,7276,7279,7329,7332,7337,7340,7343,7346,7416,7419,7501,7504,7507,7510,7587,7601,7604,7607,7610,7613,7624,7627,7630,7633,7636,7650,7653,7657,7660,7663,7680,7686],[16,7265,7266,7267],{},"边缘计算最吸引人的承诺，是让代码离用户更近。但当应用开始保存账号、会话和留言，问题就不再只是“把函数部署到更多节点”，而是：",[41,7268,7269],{},"状态究竟在哪里，谁对它负责，失败时系统会怎样退化？",[16,7271,7272],{},"这篇文章用一个带登录和留言的博客作为最小案例，梳理在 Cloudflare Workers 与 D1 上构建有状态应用时真正重要的设计边界。",[60,7274,7275],{"id":7275},"先画清楚状态地图",[16,7277,7278],{},"一个看起来很小的博客，至少有三类完全不同的状态：",[64,7280,7281,7294],{},[67,7282,7283],{},[70,7284,7285,7288,7291],{},[73,7286,7287],{},"状态",[73,7289,7290],{},"来源",[73,7292,7293],{},"生命周期",[80,7295,7296,7307,7318],{},[70,7297,7298,7301,7304],{},[85,7299,7300],{},"文章正文",[85,7302,7303],{},"Git 中的 Markdown",[85,7305,7306],{},"随部署更新",[70,7308,7309,7312,7315],{},[85,7310,7311],{},"用户与留言",[85,7313,7314],{},"D1",[85,7316,7317],{},"长期持久化",[70,7319,7320,7323,7326],{},[85,7321,7322],{},"登录会话",[85,7324,7325],{},"D1 + Cookie",[85,7327,7328],{},"有明确过期时间",[16,7330,7331],{},"文章内容适合跟着代码版本走。它需要评审、回滚和构建时校验，却不需要读者在运行时修改。用户数据则刚好相反：它必须脱离部署生命周期存在。",[28,7333,7334],{},[16,7335,7336],{},"选择数据库之前，先判断一份数据应该跟随代码版本，还是跟随用户行为。",[16,7338,7339],{},"这个区分会自然导向一个简单架构：Nuxt Content 负责 Markdown，D1 负责运行时业务数据，Worker 负责把两者组合成一次请求。",[60,7341,7342],{"id":7342},"绑定比网络请求更重要",[16,7344,7345],{},"Worker 访问 D1 时不需要自己保存 API Token，也不应该在运行时调用 Cloudflare REST API。D1 通过 binding 直接进入运行时：",[194,7347,7350],{"className":7348,"code":7349,"filename":3919,"language":4203,"meta":200,"style":200},"language-json shiki shiki-themes github-dark github-dark","{\n  \"d1_databases\": [\n    {\n      \"binding\": \"DB\",\n      \"database_name\": \"signal-log-db\",\n      \"database_id\": \"\u003CYOUR_DATABASE_ID>\"\n    }\n  ]\n}\n",[20,7351,7352,7356,7364,7368,7381,7393,7403,7407,7412],{"__ignoreMap":200},[306,7353,7354],{"class":308,"line":309},[306,7355,3932],{"class":330},[306,7357,7358,7361],{"class":308,"line":323},[306,7359,7360],{"class":312},"  \"d1_databases\"",[306,7362,7363],{"class":330},": [\n",[306,7365,7366],{"class":308,"line":363},[306,7367,3942],{"class":330},[306,7369,7370,7373,7376,7379],{"class":308,"line":380},[306,7371,7372],{"class":312},"      \"binding\"",[306,7374,7375],{"class":330},": ",[306,7377,7378],{"class":319},"\"DB\"",[306,7380,571],{"class":330},[306,7382,7383,7386,7388,7391],{"class":308,"line":391},[306,7384,7385],{"class":312},"      \"database_name\"",[306,7387,7375],{"class":330},[306,7389,7390],{"class":319},"\"signal-log-db\"",[306,7392,571],{"class":330},[306,7394,7395,7398,7400],{"class":308,"line":402},[306,7396,7397],{"class":312},"      \"database_id\"",[306,7399,7375],{"class":330},[306,7401,7402],{"class":319},"\"\u003CYOUR_DATABASE_ID>\"\n",[306,7404,7405],{"class":308,"line":593},[306,7406,3415],{"class":330},[306,7408,7409],{"class":308,"line":599},[306,7410,7411],{"class":330},"  ]\n",[306,7413,7414],{"class":308,"line":610},[306,7415,1030],{"class":330},[16,7417,7418],{},"在服务端路由里，数据库对象来自当前请求上下文。每一条 SQL 都使用 prepared statement：",[194,7420,7422],{"className":516,"code":7421,"language":518,"meta":200,"style":200},"const comment = await db\n  .prepare(`\n    SELECT id, body, created_at\n    FROM comments\n    WHERE post_slug = ?1 AND status = 'published'\n    ORDER BY created_at DESC\n    LIMIT 100\n  `)\n  .bind(postSlug)\n  .all()\n",[20,7423,7424,7438,7450,7455,7460,7465,7470,7475,7482,7492],{"__ignoreMap":200},[306,7425,7426,7428,7431,7433,7435],{"class":308,"line":309},[306,7427,545],{"class":326},[306,7429,7430],{"class":312}," comment",[306,7432,551],{"class":326},[306,7434,679],{"class":326},[306,7436,7437],{"class":330}," db\n",[306,7439,7440,7442,7445,7447],{"class":308,"line":323},[306,7441,6093],{"class":330},[306,7443,7444],{"class":343},"prepare",[306,7446,764],{"class":330},[306,7448,7449],{"class":319},"`\n",[306,7451,7452],{"class":308,"line":363},[306,7453,7454],{"class":319},"    SELECT id, body, created_at\n",[306,7456,7457],{"class":308,"line":380},[306,7458,7459],{"class":319},"    FROM comments\n",[306,7461,7462],{"class":308,"line":391},[306,7463,7464],{"class":319},"    WHERE post_slug = ?1 AND status = 'published'\n",[306,7466,7467],{"class":308,"line":402},[306,7468,7469],{"class":319},"    ORDER BY created_at DESC\n",[306,7471,7472],{"class":308,"line":593},[306,7473,7474],{"class":319},"    LIMIT 100\n",[306,7476,7477,7480],{"class":308,"line":599},[306,7478,7479],{"class":319},"  `",[306,7481,936],{"class":330},[306,7483,7484,7486,7489],{"class":308,"line":610},[306,7485,6093],{"class":330},[306,7487,7488],{"class":343},"bind",[306,7490,7491],{"class":330},"(postSlug)\n",[306,7493,7494,7496,7499],{"class":308,"line":620},[306,7495,6093],{"class":330},[306,7497,7498],{"class":343},"all",[306,7500,3331],{"class":330},[16,7502,7503],{},"参数绑定不是代码风格问题，而是输入边界的一部分。文章路径、邮箱、昵称和留言正文都来自用户，任何一个值都不应该被拼接进 SQL。",[60,7505,7506],{"id":7506},"会话只存不可逆摘要",[16,7508,7509],{},"登录成功后，浏览器拿到一个随机会话令牌；数据库只保存它的 SHA-256 摘要。这样即使会话表意外暴露，原始令牌也不能被直接拿去登录。",[194,7511,7513],{"className":516,"code":7512,"language":518,"meta":200,"style":200},"const token = createRandomToken()\nconst tokenHash = await sha256(token)\n\nawait db\n  .prepare('INSERT INTO sessions (token_hash, user_id, expires_at) VALUES (?1, ?2, ?3)')\n  .bind(tokenHash, userId, expiresAt)\n  .run()\n",[20,7514,7515,7529,7546,7550,7556,7569,7578],{"__ignoreMap":200},[306,7516,7517,7519,7522,7524,7527],{"class":308,"line":309},[306,7518,545],{"class":326},[306,7520,7521],{"class":312}," token",[306,7523,551],{"class":326},[306,7525,7526],{"class":343}," createRandomToken",[306,7528,3331],{"class":330},[306,7530,7531,7533,7536,7538,7540,7543],{"class":308,"line":323},[306,7532,545],{"class":326},[306,7534,7535],{"class":312}," tokenHash",[306,7537,551],{"class":326},[306,7539,679],{"class":326},[306,7541,7542],{"class":343}," sha256",[306,7544,7545],{"class":330},"(token)\n",[306,7547,7548],{"class":308,"line":363},[306,7549,540],{"emptyLinePlaceholder":539},[306,7551,7552,7554],{"class":308,"line":380},[306,7553,1103],{"class":326},[306,7555,7437],{"class":330},[306,7557,7558,7560,7562,7564,7567],{"class":308,"line":391},[306,7559,6093],{"class":330},[306,7561,7444],{"class":343},[306,7563,764],{"class":330},[306,7565,7566],{"class":319},"'INSERT INTO sessions (token_hash, user_id, expires_at) VALUES (?1, ?2, ?3)'",[306,7568,936],{"class":330},[306,7570,7571,7573,7575],{"class":308,"line":402},[306,7572,6093],{"class":330},[306,7574,7488],{"class":343},[306,7576,7577],{"class":330},"(tokenHash, userId, expiresAt)\n",[306,7579,7580,7582,7585],{"class":308,"line":593},[306,7581,6093],{"class":330},[306,7583,7584],{"class":343},"run",[306,7586,3331],{"class":330},[16,7588,7589,7590,7593,7594,7597,7598,58],{},"Cookie 使用 ",[20,7591,7592],{},"HttpOnly","，让前端 JavaScript 无法读取；使用 ",[20,7595,7596],{},"SameSite=Lax"," 降低跨站请求风险；生产环境再加上 ",[20,7599,7600],{},"Secure",[16,7602,7603],{},"这并不意味着系统从此“绝对安全”。更准确的说法是：我们让每一层只持有它完成工作所需的最少能力。",[60,7605,7606],{"id":7606},"一致性要围绕用户动作设计",[16,7608,7609],{},"D1 是关系数据库，但边缘应用仍然需要明确哪些流程要求强一致。",[16,7611,7612],{},"对博客来说，关键动作很少：",[160,7614,7615,7618,7621],{},[163,7616,7617],{},"注册后应当立刻能够登录；",[163,7619,7620],{},"留言成功后应当立刻在当前页面出现；",[163,7622,7623],{},"退出后旧会话应当立刻失效。",[16,7625,7626],{},"这些流程都应该从同一个主数据库完成写入和紧随其后的读取。文章列表、公开留言列表等纯读取场景，才有更大的缓存空间。",[16,7628,7629],{},"不要从“哪里可以缓存”出发，而要从“用户刚刚完成了什么承诺”出发。",[60,7631,7632],{"id":7632},"失败也需要产品设计",[16,7634,7635],{},"边缘运行时会失败，数据库也可能暂时不可用。区别只在于，我们有没有把失败变成用户能理解的状态。",[233,7637,7638,7641,7644,7647],{},[163,7639,7640],{},"登录失败时，不区分“邮箱不存在”和“密码错误”，避免暴露账号枚举信息；",[163,7642,7643],{},"留言写入失败时，保留输入内容，让用户可以重试；",[163,7645,7646],{},"内容查询失败时，返回真正的错误状态，而不是空白页面；",[163,7648,7649],{},"所有服务端错误使用结构化日志，但不把内部 SQL 或堆栈发给浏览器。",[16,7651,7652],{},"系统可靠性不是“永远不出错”，而是错误发生时边界仍然完整。",[60,7654,7656],{"id":7655},"最后让架构保持无聊","最后：让架构保持无聊",[16,7658,7659],{},"这个方案没有消息队列、没有分布式锁，也没有额外的身份服务。不是因为那些工具没有价值，而是当前问题还没有提出相应要求。",[16,7661,7662],{},"一个长期可维护的个人站，最好的基础通常是：",[233,7664,7665,7668,7671,7674,7677],{},[163,7666,7667],{},"内容跟着 Git；",[163,7669,7670],{},"数据进入 D1；",[163,7672,7673],{},"密钥留在 Worker secret；",[163,7675,7676],{},"输入在边界验证；",[163,7678,7679],{},"失败清楚、可观察、可恢复。",[16,7681,7682,7683],{},"当流量或产品需求真正变化时，再让架构跟着证据演进。",[41,7684,7685],{},"边缘已经足够快，工程的任务是让它继续简单。",[1641,7687,7688],{},"html pre.shiki code .suv1-, html code.shiki .suv1-{--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .s8ozJ, html code.shiki .s8ozJ{--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .s4wv1, html code.shiki .s4wv1{--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sOPea, html code.shiki .sOPea{--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .sFR8T, html code.shiki .sFR8T{--shiki-default:#B392F0;--shiki-dark:#B392F0}",{"title":200,"searchDepth":363,"depth":363,"links":7690},[7691,7692,7693,7694,7695,7696],{"id":7275,"depth":323,"text":7275},{"id":7342,"depth":323,"text":7342},{"id":7506,"depth":323,"text":7506},{"id":7606,"depth":323,"text":7606},{"id":7632,"depth":323,"text":7632},{"id":7655,"depth":323,"text":7656},"Cloudflare Workers 很快，D1 很近，但真正困难的是把状态、一致性与失败边界设计清楚。",{"src":7699,"alt":7700,"caption":7701,"width":5275,"height":5276},"\u002Fmedia\u002Fposts\u002Fedge-stateful-apps\u002Fcover-v1.webp","边缘节点围绕中心数据库形成有状态应用拓扑","状态、边缘节点与一致性边界",{},"\u002Fblog\u002Fedge-stateful-apps","2026-07-21",{"title":7261,"description":7697},"blog\u002Fedge-stateful-apps",[1678,7314,7708],"工程","2026-07-24","lxftjEas_P44g5EWHmgEyDudWlFsheAV8hSZv5xBmGw",{"id":7712,"title":7713,"accent":5296,"body":7714,"description":8098,"draft":1663,"experience":1664,"extension":1665,"featured":1663,"image":8099,"meta":8103,"navigation":539,"path":8104,"publishedAt":8105,"readingTime":402,"seo":8106,"stem":8107,"tags":8108,"updatedAt":1664,"video":1664,"__hash__":8111,"contentLocale":4,"isFallback":1663},"posts_zh\u002Fblog\u002Fnuxt-4-architecture-subtraction.md","Nuxt 4 的架构减法",{"type":13,"value":7715,"toc":8090},[7716,7719,7725,7729,7732,7783,7786,7793,7796,7799,7923,7926,7944,7947,7950,7953,7956,7959,7964,7968,7975,7981,7984,7987,7990,7993,8058,8061,8064,8067,8084,8087],[16,7717,7718],{},"很多 Nuxt 项目变复杂，并不是因为业务真的复杂，而是因为我们太早把所有可能性都放进了架构。",[16,7720,7721,7722,58],{},"个人博客是一个很好的校准器：页面数量有限、内容结构稳定、交互集中在少数区域。它提醒我们，框架最有价值的部分往往不是增加能力，而是",[41,7723,7724],{},"删除不必要的选择",[60,7726,7728],{"id":7727},"从默认-ssr-开始","从默认 SSR 开始",[16,7730,7731],{},"技术文章需要被搜索引擎理解，也需要在弱网下尽早出现正文。Nuxt 默认的服务端渲染刚好符合这个目标。",[194,7733,7736],{"className":516,"code":7734,"filename":7735,"language":518,"meta":200,"style":200},"export default defineNuxtConfig({\n  modules: ['@nuxt\u002Fcontent'],\n  nitro: {\n    preset: 'cloudflare',\n  },\n})\n","nuxt.config.ts",[20,7737,7738,7749,7760,7765,7775,7779],{"__ignoreMap":200},[306,7739,7740,7742,7744,7747],{"class":308,"line":309},[306,7741,327],{"class":326},[306,7743,3275],{"class":326},[306,7745,7746],{"class":343}," defineNuxtConfig",[306,7748,560],{"class":330},[306,7750,7751,7754,7757],{"class":308,"line":323},[306,7752,7753],{"class":330},"  modules: [",[306,7755,7756],{"class":319},"'@nuxt\u002Fcontent'",[306,7758,7759],{"class":330},"],\n",[306,7761,7762],{"class":308,"line":363},[306,7763,7764],{"class":330},"  nitro: {\n",[306,7766,7767,7770,7773],{"class":308,"line":380},[306,7768,7769],{"class":330},"    preset: ",[306,7771,7772],{"class":319},"'cloudflare'",[306,7774,571],{"class":330},[306,7776,7777],{"class":308,"line":391},[306,7778,623],{"class":330},[306,7780,7781],{"class":308,"line":402},[306,7782,661],{"class":330},[16,7784,7785],{},"这里没有把整个站改成 SPA，也没有为每个页面单独发明数据加载方式。页面在服务端完成第一次查询和渲染，客户端接管后继续使用同一套路由。",[16,7787,7788,7789,7792],{},"如果某一小块必须依赖浏览器 API，再局部使用 ",[20,7790,7791],{},"ClientOnly","。不要为了一个客户端组件牺牲整页的语义和首屏。",[60,7794,7795],{"id":7795},"内容查询留在页面边界",[16,7797,7798],{},"首页、文章列表和文章详情需要的数据粒度不同。查询应该贴近消费它的页面：",[194,7800,7802],{"className":516,"code":7801,"language":518,"meta":200,"style":200},"const { data: posts } = await useAsyncData('latest-posts', () => {\n  return queryCollection('posts')\n    .where('draft', '=', false)\n    .order('publishedAt', 'DESC')\n    .limit(6)\n    .all()\n})\n",[20,7803,7804,7839,7853,7878,7897,7911,7919],{"__ignoreMap":200},[306,7805,7806,7808,7810,7813,7815,7818,7820,7822,7824,7827,7829,7832,7835,7837],{"class":308,"line":309},[306,7807,545],{"class":326},[306,7809,4356],{"class":330},[306,7811,7812],{"class":879},"data",[306,7814,7375],{"class":330},[306,7816,7817],{"class":312},"posts",[306,7819,4361],{"class":330},[306,7821,4364],{"class":326},[306,7823,679],{"class":326},[306,7825,7826],{"class":343}," useAsyncData",[306,7828,764],{"class":330},[306,7830,7831],{"class":319},"'latest-posts'",[306,7833,7834],{"class":330},", () ",[306,7836,4346],{"class":326},[306,7838,1080],{"class":330},[306,7840,7841,7843,7846,7848,7851],{"class":308,"line":323},[306,7842,4531],{"class":326},[306,7844,7845],{"class":343}," queryCollection",[306,7847,764],{"class":330},[306,7849,7850],{"class":319},"'posts'",[306,7852,936],{"class":330},[306,7854,7855,7858,7861,7863,7866,7868,7871,7873,7876],{"class":308,"line":363},[306,7856,7857],{"class":330},"    .",[306,7859,7860],{"class":343},"where",[306,7862,764],{"class":330},[306,7864,7865],{"class":319},"'draft'",[306,7867,888],{"class":330},[306,7869,7870],{"class":319},"'='",[306,7872,888],{"class":330},[306,7874,7875],{"class":312},"false",[306,7877,936],{"class":330},[306,7879,7880,7882,7885,7887,7890,7892,7895],{"class":308,"line":380},[306,7881,7857],{"class":330},[306,7883,7884],{"class":343},"order",[306,7886,764],{"class":330},[306,7888,7889],{"class":319},"'publishedAt'",[306,7891,888],{"class":330},[306,7893,7894],{"class":319},"'DESC'",[306,7896,936],{"class":330},[306,7898,7899,7901,7904,7906,7909],{"class":308,"line":391},[306,7900,7857],{"class":330},[306,7902,7903],{"class":343},"limit",[306,7905,764],{"class":330},[306,7907,7908],{"class":312},"6",[306,7910,936],{"class":330},[306,7912,7913,7915,7917],{"class":308,"line":402},[306,7914,7857],{"class":330},[306,7916,7498],{"class":343},[306,7918,3331],{"class":330},[306,7920,7921],{"class":308,"line":593},[306,7922,661],{"class":330},[16,7924,7925],{},"这样做有几个直接好处：",[233,7927,7928,7935,7938,7941],{},[163,7929,7930,7931,7934],{},"数据参与 SSR，不需要 ",[20,7932,7933],{},"onMounted"," 后再闪烁加载；",[163,7936,7937],{},"查询键明确，Nuxt 可以在 hydration 时复用 payload；",[163,7939,7940],{},"页面知道自己需要什么，不必经过一层通用 store；",[163,7942,7943],{},"草稿过滤与排序规则出现在真正相关的位置。",[16,7945,7946],{},"只有当多个页面共享了同一段复杂业务状态时，才值得抽成 composable 或 store。",[60,7948,7949],{"id":7949},"组件只抽取稳定重复",[16,7951,7952],{},"“看起来可能复用”不是组件边界。真正稳定的重复通常同时包含结构、语义和行为。",[16,7954,7955],{},"这个博客里，文章卡片是合适的组件：它在首页和索引页重复出现，拥有固定的元数据、标题、摘要、标签和跳转行为。",[16,7957,7958],{},"相反，首页 Hero 即使很大，也未必需要拆成五个小组件。它只在一个页面出现，内部元素共同完成一段视觉叙事。过早拆分只会让样式关系跨文件跳转。",[28,7960,7961],{},[16,7962,7963],{},"组件不是文件整理工具，而是可独立理解和复用的 UI 契约。",[60,7965,7967],{"id":7966},"服务端-api-保持窄接口","服务端 API 保持窄接口",[16,7969,7970,7971,7974],{},"登录与留言属于运行时数据，放在 ",[20,7972,7973],{},"server\u002Fapi","。每个 endpoint 只做一件事：",[194,7976,7979],{"className":7977,"code":7978,"language":199,"meta":200},[197],"POST \u002Fapi\u002Fauth\u002Fregister\nPOST \u002Fapi\u002Fauth\u002Flogin\nPOST \u002Fapi\u002Fauth\u002Flogout\nGET  \u002Fapi\u002Fauth\u002Fsession\nGET  \u002Fapi\u002Fcomments\u002F:post\nPOST \u002Fapi\u002Fcomments\u002F:post\n",[20,7980,7978],{"__ignoreMap":200},[16,7982,7983],{},"窄接口使授权规则更容易检查。读取留言是公开的，创建留言需要会话；退出只销毁当前令牌，不需要触碰用户资料。",[16,7985,7986],{},"如果一个 endpoint 同时处理读取、写入、登录和格式转换，它就已经失去了清晰边界。",[60,7988,7989],{"id":7989},"把状态放回正确的位置",[16,7991,7992],{},"Vue 的响应式状态很方便，但不是所有状态都应该进入 JavaScript：",[64,7994,7995,8004],{},[67,7996,7997],{},[70,7998,7999,8001],{},[73,8000,7287],{},[73,8002,8003],{},"最合适的位置",[80,8005,8006,8014,8024,8032,8043,8050],{},[70,8007,8008,8011],{},[85,8009,8010],{},"当前文章",[85,8012,8013],{},"路由",[70,8015,8016,8019],{},[85,8017,8018],{},"搜索词",[85,8020,8021,8022],{},"页面 ",[20,8023,5310],{},[70,8025,8026,8029],{},[85,8027,8028],{},"标签筛选",[85,8030,8031],{},"URL query + 页面状态",[70,8033,8034,8037],{},[85,8035,8036],{},"登录用户",[85,8038,8039,8040],{},"SSR 安全的 ",[20,8041,8042],{},"useState",[70,8044,8045,8047],{},[85,8046,7300],{},[85,8048,8049],{},"Nuxt Content",[70,8051,8052,8055],{},[85,8053,8054],{},"会话真实性",[85,8056,8057],{},"服务端 D1",[16,8059,8060],{},"当状态位置正确时，大量“同步逻辑”会自然消失。我们不再需要监听路由去修复 store，也不需要用 localStorage 假装登录状态可靠。",[60,8062,8063],{"id":8063},"减法不是功能更少",[16,8065,8066],{},"好的架构减法不会让用户少得到什么。相反，它把复杂度预算留给真正有感知的部分：",[233,8068,8069,8072,8075,8078,8081],{},[163,8070,8071],{},"更清晰的排版；",[163,8073,8074],{},"更快的首屏；",[163,8076,8077],{},"更可靠的登录；",[163,8079,8080],{},"更舒服的移动端阅读；",[163,8082,8083],{},"更容易坚持的 Markdown 写作流程。",[16,8085,8086],{},"Nuxt 4 提供了足够多的能力。项目成熟的标志，不是把它们全部用上，而是知道哪些能力可以暂时不用。",[1641,8088,8089],{},"html pre.shiki code .sOPea, html code.shiki .sOPea{--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .sFR8T, html code.shiki .sFR8T{--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .suv1-, html code.shiki .suv1-{--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .s4wv1, html code.shiki .s4wv1{--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-3mD, html code.shiki .s-3mD{--shiki-default:#FFAB70;--shiki-dark:#FFAB70}html pre.shiki code .s8ozJ, html code.shiki .s8ozJ{--shiki-default:#79B8FF;--shiki-dark:#79B8FF}",{"title":200,"searchDepth":363,"depth":363,"links":8091},[8092,8093,8094,8095,8096,8097],{"id":7727,"depth":323,"text":7728},{"id":7795,"depth":323,"text":7795},{"id":7949,"depth":323,"text":7949},{"id":7966,"depth":323,"text":7967},{"id":7989,"depth":323,"text":7989},{"id":8063,"depth":323,"text":8063},"一个内容型站点不需要复杂前端状态。让路由、服务端渲染和内容查询各自回到最擅长的位置。",{"src":8100,"alt":8101,"caption":8102,"width":5275,"height":5276},"\u002Fmedia\u002Fposts\u002Fnuxt-4-architecture-subtraction\u002Fcover-v1.webp","复杂模块逐步收敛为清晰精简的 Nuxt 应用核心","用架构减法换取清晰边界",{},"\u002Fblog\u002Fnuxt-4-architecture-subtraction","2026-07-16",{"title":7713,"description":8098},"blog\u002Fnuxt-4-architecture-subtraction",[8109,6997,8110],"Nuxt","架构","Czr5nd2U1OiyR7yB5-ruM4B8FGFAAjFQQISDmWg9Fis",{"id":8113,"title":8114,"accent":7004,"body":8115,"description":8463,"draft":1663,"experience":1664,"extension":1665,"featured":1663,"image":8464,"meta":8468,"navigation":539,"path":8469,"publishedAt":8470,"readingTime":593,"seo":8471,"stem":8472,"tags":8473,"updatedAt":1664,"video":1664,"__hash__":8475,"contentLocale":4,"isFallback":1663},"posts_zh\u002Fblog\u002Fobservable-ai-systems.md","AI 系统的可观测性，不只是 Token 账单",{"type":13,"value":8116,"toc":8450},[8117,8120,8125,8128,8131,8134,8138,8141,8145,8148,8152,8155,8159,8162,8165,8169,8176,8290,8293,8296,8313,8316,8319,8322,8376,8379,8382,8385,8402,8405,8408,8411,8414,8428,8431,8435,8438,8444,8447],[16,8118,8119],{},"传统服务出问题时，我们习惯查看状态码、延迟和错误堆栈。AI 应用出问题时，这些指标仍然重要，却经常不足以回答最关键的问题：",[16,8121,8122],{},[41,8123,8124],{},"模型为什么做出了这个选择？",[16,8126,8127],{},"答案不在某一条日志里，而在从用户目标、上下文组装、模型决策、工具执行到最终结果的整条链路中。",[60,8129,8130],{"id":8130},"先区分四种失败",[16,8132,8133],{},"把所有失败都记成“模型效果不好”，团队就无法采取具体行动。至少应该区分：",[186,8135,8137],{"id":8136},"_1-输入失败","1. 输入失败",[16,8139,8140],{},"用户目标不完整，或系统没有拿到完成任务所需的上下文。此时升级模型通常没有帮助。",[186,8142,8144],{"id":8143},"_2-推理失败","2. 推理失败",[16,8146,8147],{},"上下文正确，但模型选择了错误路径、遗漏约束或过早得出结论。",[186,8149,8151],{"id":8150},"_3-工具失败","3. 工具失败",[16,8153,8154],{},"模型选择正确，实际执行却因为权限、网络、参数或外部状态失败。",[186,8156,8158],{"id":8157},"_4-交付失败","4. 交付失败",[16,8160,8161],{},"内部工作已经完成，但最终回答没有把结果、限制和下一步说清楚。",[16,8163,8164],{},"这四类问题需要不同的修复者：产品、提示设计、平台工程或交互设计。可观测性的第一份价值，是让问题回到正确的人手里。",[60,8166,8168],{"id":8167},"一次运行需要共同的-trace","一次运行需要共同的 Trace",[16,8170,8171,8172,8175],{},"每个用户请求都应该有稳定的 ",[20,8173,8174],{},"trace_id","，贯穿模型调用和工具执行：",[194,8177,8179],{"className":516,"code":8178,"language":518,"meta":200,"style":200},"type AgentEvent = {\n  traceId: string\n  spanId: string\n  parentSpanId?: string\n  type: 'model' | 'tool' | 'handoff' | 'result'\n  startedAt: string\n  durationMs?: number\n  status: 'running' | 'ok' | 'error'\n}\n",[20,8180,8181,8193,8202,8211,8221,8247,8256,8266,8286],{"__ignoreMap":200},[306,8182,8183,8186,8189,8191],{"class":308,"line":309},[306,8184,8185],{"class":326},"type",[306,8187,8188],{"class":343}," AgentEvent",[306,8190,551],{"class":326},[306,8192,1080],{"class":330},[306,8194,8195,8198,8200],{"class":308,"line":323},[306,8196,8197],{"class":879},"  traceId",[306,8199,882],{"class":326},[306,8201,3260],{"class":312},[306,8203,8204,8207,8209],{"class":308,"line":363},[306,8205,8206],{"class":879},"  spanId",[306,8208,882],{"class":326},[306,8210,3260],{"class":312},[306,8212,8213,8216,8219],{"class":308,"line":380},[306,8214,8215],{"class":879},"  parentSpanId",[306,8217,8218],{"class":326},"?:",[306,8220,3260],{"class":312},[306,8222,8223,8226,8228,8231,8234,8237,8239,8242,8244],{"class":308,"line":391},[306,8224,8225],{"class":879},"  type",[306,8227,882],{"class":326},[306,8229,8230],{"class":319}," 'model'",[306,8232,8233],{"class":326}," |",[306,8235,8236],{"class":319}," 'tool'",[306,8238,8233],{"class":326},[306,8240,8241],{"class":319}," 'handoff'",[306,8243,8233],{"class":326},[306,8245,8246],{"class":319}," 'result'\n",[306,8248,8249,8252,8254],{"class":308,"line":402},[306,8250,8251],{"class":879},"  startedAt",[306,8253,882],{"class":326},[306,8255,3260],{"class":312},[306,8257,8258,8261,8263],{"class":308,"line":593},[306,8259,8260],{"class":879},"  durationMs",[306,8262,8218],{"class":326},[306,8264,8265],{"class":312}," number\n",[306,8267,8268,8271,8273,8276,8278,8281,8283],{"class":308,"line":599},[306,8269,8270],{"class":879},"  status",[306,8272,882],{"class":326},[306,8274,8275],{"class":319}," 'running'",[306,8277,8233],{"class":326},[306,8279,8280],{"class":319}," 'ok'",[306,8282,8233],{"class":326},[306,8284,8285],{"class":319}," 'error'\n",[306,8287,8288],{"class":308,"line":610},[306,8289,1030],{"class":330},[16,8291,8292],{},"工具调用是模型决策的外部表现。只记录工具返回值而不记录调用原因，会失去决策上下文；只保存提示词而不保存工具结果，又无法判断执行是否符合预期。",[16,8294,8295],{},"需要注意的是，观测并不意味着无限制收集。原始提示词可能包含个人信息、凭据或业务数据。更稳妥的做法是：",[233,8297,8298,8301,8304,8307,8310],{},[163,8299,8300],{},"默认记录结构化元数据；",[163,8302,8303],{},"对正文做字段级脱敏；",[163,8305,8306],{},"为调试样本设置短保留期；",[163,8308,8309],{},"允许用户和业务明确选择不进入训练或分析；",[163,8311,8312],{},"不在日志中保存密钥与完整身份信息。",[60,8314,8315],{"id":8315},"质量指标必须贴近任务",[16,8317,8318],{},"Token、延迟和模型错误率是平台指标，但它们不能代表用户是否得到结果。",[16,8320,8321],{},"一个代码修复 Agent 更有意义的指标可能是：",[64,8323,8324,8334],{},[67,8325,8326],{},[70,8327,8328,8331],{},[73,8329,8330],{},"层级",[73,8332,8333],{},"指标示例",[80,8335,8336,8344,8352,8360,8368],{},[70,8337,8338,8341],{},[85,8339,8340],{},"结果",[85,8342,8343],{},"受影响测试是否通过",[70,8345,8346,8349],{},[85,8347,8348],{},"过程",[85,8350,8351],{},"是否读取了相关约束文件",[70,8353,8354,8357],{},[85,8355,8356],{},"安全",[85,8358,8359],{},"是否修改了任务范围外文件",[70,8361,8362,8365],{},[85,8363,8364],{},"效率",[85,8366,8367],{},"有效工具调用 \u002F 总工具调用",[70,8369,8370,8373],{},[85,8371,8372],{},"交付",[85,8374,8375],{},"是否提供可验证的文件或提交",[16,8377,8378],{},"“有效工具调用”比单纯调用次数更有价值。一次准确的搜索可能替代十次猜测式读取。",[60,8380,8381],{"id":8381},"把评估变成日常反馈",[16,8383,8384],{},"离线 benchmark 能比较模型版本，却不能覆盖你的真实工作流。更可持续的评估集来自生产中的高价值样本：",[160,8386,8387,8390,8393,8396,8399],{},[163,8388,8389],{},"收集明确成功与明确失败的运行；",[163,8391,8392],{},"删除隐私信息，保留决策结构；",[163,8394,8395],{},"为失败标注类型，而不是只打一个总分；",[163,8397,8398],{},"在提示、模型或工具变化时重新运行；",[163,8400,8401],{},"同时比较结果质量、成本和延迟。",[16,8403,8404],{},"评估集不需要一开始就很大。二十个边界清楚、能稳定复现的案例，通常比两千个来源模糊的样本更能指导工程。",[60,8406,8407],{"id":8407},"面向人的可观测性",[16,8409,8410],{},"最后一层观测不在仪表盘，而在产品界面。",[16,8412,8413],{},"当 Agent 正在读取文件、等待授权或执行部署时，用户需要知道它处于什么阶段。好的状态更新不是倾倒内部思维，而是给出：",[233,8415,8416,8419,8422,8425],{},[163,8417,8418],{},"当前正在完成什么；",[163,8420,8421],{},"已经确认了什么；",[163,8423,8424],{},"是否需要用户采取行动；",[163,8426,8427],{},"哪些风险仍未关闭。",[16,8429,8430],{},"这类可见性会直接改变用户对系统可靠性的判断。一个需要几十秒完成的任务，如果过程清楚，往往比十秒后突然返回不透明结果更让人放心。",[60,8432,8434],{"id":8433},"可解释不等于暴露全部过程","可解释，不等于暴露全部过程",[16,8436,8437],{},"AI 系统不需要把每一个内部 token 展示给用户，也不应该把敏感上下文原样写入日志。真正需要的是一条足够清楚的因果链：",[194,8439,8442],{"className":8440,"code":8441,"language":199,"meta":200},[197],"用户目标\n  → 使用了哪些上下文\n  → 选择了什么工具\n  → 工具返回了什么状态\n  → 最终交付了什么结果\n",[20,8443,8441],{"__ignoreMap":200},[16,8445,8446],{},"当这条链路可以被查询、比较和复现时，团队才有能力把“偶尔很聪明”变成“长期可信赖”。",[1641,8448,8449],{},"html pre.shiki code .sOPea, html code.shiki .sOPea{--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .sFR8T, html code.shiki .sFR8T{--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .suv1-, html code.shiki .suv1-{--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .s-3mD, html code.shiki .s-3mD{--shiki-default:#FFAB70;--shiki-dark:#FFAB70}html pre.shiki code .s8ozJ, html code.shiki .s8ozJ{--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .s4wv1, html code.shiki .s4wv1{--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":200,"searchDepth":363,"depth":363,"links":8451},[8452,8458,8459,8460,8461,8462],{"id":8130,"depth":323,"text":8130,"children":8453},[8454,8455,8456,8457],{"id":8136,"depth":363,"text":8137},{"id":8143,"depth":363,"text":8144},{"id":8150,"depth":363,"text":8151},{"id":8157,"depth":363,"text":8158},{"id":8167,"depth":323,"text":8168},{"id":8315,"depth":323,"text":8315},{"id":8381,"depth":323,"text":8381},{"id":8407,"depth":323,"text":8407},{"id":8433,"depth":323,"text":8434},"从提示词到工具调用，再到用户可见结果，AI 应用需要一条能解释“为什么”的观测链路。",{"src":8465,"alt":8466,"caption":8467,"width":5275,"height":5276},"\u002Fmedia\u002Fposts\u002Fobservable-ai-systems\u002Fcover-v1.webp","AI 请求从输入经过模型和工具节点形成完整观测链路","从提示词到结果的可解释 Trace",{},"\u002Fblog\u002Fobservable-ai-systems","2026-07-09",{"title":8114,"description":8463},"blog\u002Fobservable-ai-systems",[7255,8474,7708],"可观测性","HwqaP2v2TJ3UNRY0hMgfErIMN1Z-buqu7-QNk_P-4Vk",1785464601739]