@freecorps/ember-email

中文文档
0.7.3发布于 2026/11/12MITTypeScriptES Module构建来源已验证近 30 天下载 4298

Cloudflare上的 Ember 电子邮件组件:通过电子邮件服务、模板、压制和每个项目费率限制、从队列发送事件、寄入邮件(电子邮件查询)分解为邮箱和

中文使用文档
文档目录4 节
  1. 它会做什么
  2. 域数 : @freecorps/ember-email/cloudflare
  3. 离线复制: @freecorps/ember-email/schema
  4. 示例

@freecorps/ember-电子邮件

作为电子邮件 琥珀 cloudflare电子邮件服务(发送)和电子邮件 跑步 (接收). 它运行在你自己的Cloudflare账户.

// ember/ember.config.ts
import { defineApp, createFunctionHandle } from "@freecorps/ember-server";
import { email } from "@freecorps/ember-email";

export default defineApp().use(email, {
  config: {
    domains: ["acme.com"],                                 // inbound domains routed to this Worker
    rateLimit: { rate: 100, period: 60_000 },              // per project
    hooks: { onInbound: createFunctionHandle("support:onInbound") },
  },
});
// ember/users.ts
import { Email } from "@freecorps/ember-email";
import { components, mutation } from "./_generated/server";
const email = new Email(components.email);

export const signup = mutation({
  args: { address: v.string(), name: v.string() },
  handler: (ctx, a) => {
    const id = ctx.db.insert("users", a);
    // Commits (or rolls back) with the insert; delivered by an action right after.
    email.send(ctx, { from: "Acme <[email protected]>", to: a.address, template: { name: "welcome", data: { name: a.name } }, idempotencyKey: `welcome:${id}` });
    return id;
  },
});

它会做什么

  • 发送. lib:send 编号 : email.send(ctx, ...) 是变种: 原子与调用者, 可选 sendAt。它也会 处理每个项目的一能键,每个项目的压制(增加了硬弹跳和投诉) 自动)和每个项目的符号-桶利率限制(已取消) rateLimit 组件,同一交易). 送货经过 send_email 绑定( 部署后添加) 或 REST API, 有重试 。 标题 拒绝提供者被丢弃,保持线程和 X- 头.

  • 模板 储存在硬体中,并使用一个小型的类似芥末的引擎({{var}}, (中文(简体) ), {{{raw}}}, (中文(简体) ), #if编号 :#each(中文(简体) ) 适应性布局(emailLayout:一张灰色页面上的卡. 反应电子邮件也起作用:将其变成动作和 通过 html。 。 。 .

  • 布局 : 交易邮件(有版式的存储模板,通过 lib:send 或一个HTTP API在上面)得到 emailLayout。从收件箱中邮寄一个人(reply, (中文(简体) ), compose邮箱自动复制) conversationLayout 相反:像邮件客户端那样的简单文档,没有背景、卡片、边框或强制 文本颜色(如此暗-mode客户端重新取色),系统字体为14px.

  • 送货活动. 组件拥有一个队列( Q)ember-<project>-<env>-email-events。指向电子邮件服务 用于您发送域的事件订阅 。 事件由 eventId,只移动状态 前进,以及 lib:get 是反应性的.

  • 进入. 部件勾住工人的 email() 处理器 :

    • 它传递邮箱地址(support@, (中文(简体) ), privacy@)和外接的线状符号. 未知地址为 拒绝.
    • 它储存生的 .eml 和文件(R2)中的附件,并用 postal-mime。 。 。 .
    • 它读DKIM,DMARC和SPF 从Cloudflare's Authentication-Results。 。 。 .
    • 它像邮件客户端一样线入信件, 默认情况下在 Reform-To 中没有符号 :
      • 当发送者是线程的联系人或一个联系人时, 信箱发送的电子邮件会加入其线程 该电子邮件的收件者(In-Reply-To / Reference for the Message-ID the supposed it, recorded when (a) 发送成功;
      • (一) 使用联系人的DMARC出入证引用入境电文;
      • 如果线程没有被关闭或垃圾邮件,则在30天内进行同样的接触和接触;
      • 附加地址的代号support+t_…@仍然来自任何发送者的线程.
    • replyToken: true 在安装配置中,将这个符号放入答复的回复中,包括电子邮件和 自动复制机,如前0.5.4.
    • 答复和自动复制人按发件人写的那样引用信件ID和参考资料(只发送邮件组 a) 在谈话中回答一个准确的匹配; 查看忽略了案件.
    • 它处理每小时每个发送者的垃圾邮件和没有回路的自动复制.
    • 它在存储任何东西之前应用发件人规则(以下).
    •  onInbound 钩子在同一个交易中运行(ticket, push).
  • 收件箱查询 用于仪表板和移动应用程序: threads, (中文(简体) ), thread, (中文(简体) ), unread (总数), search (单位:千美元)searchIndex;每个命中都带有线条 status, (中文(简体) ), unreadCount, (中文(简体) ), assignee编号 :assigneeName, (中文(简体) ), snoozedUntil 财务报告和审定财务报表 tags, (中文(简体) ), reply, (中文(简体) ), note, (中文(简体) ), markRead, (中文(简体) ), markUnread, (中文(简体) ), setStatus, (中文(简体) ), deleteThread (谈话及其信息,现在), assign, (中文(简体) ), compose (一个新的谈话) 从邮箱,答案 加入到上面, recent (最新的线条与他们的留言, 对于离线复制品)和 sent (邮箱的Sent视图:它用最近发送的线条写入,用 before) (中文(简体) ). 斯努兹,预定 以下介绍发送和出席情况. reply 财务报告和审定财务报表 compose 将电子邮件构建为邮件客户端 :

    • text (要求)是文本/平面部分,在未给出更丰富的内容时是HTML部分(段落,与 http(s):// 财务报告和审定财务报表 mailto: URL链接).
    • bodyHtml?: string 是作曲家丰富的身体, HTML 碎片。 它被消毒为 p br div span b strong i em u s a ul ol li blockquote code pre,没有属性,但 a[href] (单位:千美元)http:, (中文(简体) ), https:, (中文(简体) ), mailto:);脚本,风格, iframe 等随其内容而去,其他标签会留下文本(sanitizeEmailHtml) (中文(简体) ). 它被保留在 线程信息为 html。 。 。 . html?: string (一整份文件,按原样使用)仍然有效;而不是两者兼而有之.
    • signature?: { text: string; html?: string }: 在尸体之后, -- + 键 text 在文本部分,Gmail的 gmail_signature hTML 部分中的块(html 以同样的方式消毒,或 text (已执行).
    • reply 在 Gmail 下引用最后一条入境信件( 它的前40行) gmail_quote gmail 结构 和苹果邮件崩溃,以及 > 文本部分中的行. quoteHeader?: string 已经是归属线 本地化( e)Em 5 de nov. de 2026, Marina Costa escreveu:; 默认为 On {date}, {name} <{email}> wrote:。 。 。 . reply 财务报告和审定财务报表 compose 带 bcc?: string[] (失明副本,验证并计算如: cc) (中文(简体) ). 这些地址是 提供者作为Bcc,仅在信封中,从不出现在信头或收件人看到的体内. 它们被储存在 线程输出消息( r)bcc)为邮箱自己取景,其中一人的回答会加入线程. reply 财务报告和审定财务报表 compose 带 attachments: [{ filename, contentType, file }],已存储在硬盘中的文件 (装入 ctx.storage.generateUploadUrl) (中文(简体) ). 他们带着电子邮件 留在线条外的信息 (有其大小. 注意提供者的消息大小: 对于一个大文件, 在文本中设置下载链接 (单位:千美元)ctx.storage.getUrl(file, { download, expiresIn })并传递给 link: true。电子邮件中没有 MIME 部分 对于它,信件仍然持有文件(线程列出它,没有收集它).
  • 清单和计数 (准确,指数支持):

    • threads({ mailbox, status?, assignee?, unread?, tag?, paginationOpts? })先说最新消息. assignee 是应用程序用户代号,或 null 给未分配的. unread: true 列出未读信件的线索, false 其他人,他们... tag 是一个标记 ID。 每个过滤器都有索引( 有无索引) status) (中文(简体) ). 当你结合 过滤器,最有选择的选取索引而其他选取过滤其范围,所以页面仍然准确.
    • 每个线程存储 hasUnread (单位:千美元)unreadCount > 0) (中文(简体) ). 每一个改变的路径 unreadCount 写道:, markRead, (中文(简体) ), markUnread。离线复制品可以在上面过滤.
    • counts({ mailbox, assignee? }) 返回时 { all, byStatus, assignee?, tags }。每个块是 { threads, unread, unreadThreads } (单位:千美元)unread 计数消息). 他们来自汇总 写最新,所以一个电话 读取数行的分组,并永远不扫描线程.
  • 标记( 标签),每个邮箱:

    • tags({ mailbox }) 列出顺序. createTag({ mailbox, name, color?, order? }): 名称是每个 邮箱 忽略案件 1.40个字符. color 这是其中之一 TAG_COLORS (单位:千美元)gray ),最多100个标记 每个邮箱。 还有 updateTag({ tagId, name?, color?, order? }) 财务报告和审定财务报表 deleteTag({ tagId })。 。 。 .
    • 线索中包含标签标识符 tags (最多20个)。 换个位置 setTags({ threadId, tags, author? }), (中文(简体) ), addTag({ threadId, tag, author? }) 财务报告和审定财务报表 removeTag(...); 每一个返回已写入的标签.
    • deleteTag 从每个线索中取出标记: 在自己的交易中取出200个线索, 其余在 lib:untag 任务.
    • A 类 threadTags 每行( 线索、 标记) 背起 tag 过滤器和每盘计数。 部件保存这些 与线相接而行.
  • 活动时间表 : activity({ threadId, limit? }),最古老的第一。 该组件写它,与 author 担任演员( r)setStatus, (中文(简体) ), assign, (中文(简体) ), setTags, (中文(简体) ), addTag, (中文(简体) ), removeTag, (中文(简体) ), snooze, (中文(简体) ), cancelScheduled, (中文(简体) ), reply 财务报告和审定财务报表 compose 使用一个;缺席意味着系统)。 Kinds(加上下面的snooze和预定发送的) :

    • status { from, to, messageId? } (单位:千美元)messageId:发送的外出消息移动了它),包括自动更改(一个回答重开一个线程,一个回答使它变成 waiting(三);
    • assigned { from, fromName?, to, toName? }· ;
    • tag_added 编号 : tag_removed { tag, name, color }· ;
    • delivery { emailId, status, previous, messageId?, recipient?, … },因为线程的外部电子邮件到达 sent, (中文(简体) ), delivery_delayed, (中文(简体) ), delivered, (中文(简体) ), bounced, (中文(简体) ), complained, (中文(简体) ), failed, (中文(简体) ), suppressed 或者说 canceled。 。 。 .

    消息和笔记在时间表中不重复:客户端按时间合并.

  • 斯诺兹:

    • snooze({ threadId, until, author? }) 接通了一条线. until 未来时间(ms),最多366天; null 唤起线了.
    • 一条被打耳光的线条 snoozed: true 财务报告和审定财务报表 snoozedUntil。它留下默认值 threads 清单和清单 counts (单位:千美元)all 编号 : byStatus),以及 counts 给它自己的 snoozed 块. threads({ snoozed: true }) 列出 接通了线 很快就醒了 与一个 tag这个名单是最新的.
    • 时 until,计划 lib:wake 任务将线条带回新的,就像Gmail。 它排在榜首 (单位:千美元)lastMessageAt 成为唤醒时间,所以 lastMessageAt 是"最新消息或唤醒",变成未读 (至少一个),设定为 open 除非是垃圾邮件.
    • 传入的信息立刻唤醒了线条. 答复和笔记都放好了. lib:maintain (单位:千美元) 唤醒了那些从未工作过的人.
    • 时间线条目 : snoozed { until, previous? } 财务报告和审定财务报表 unsnoozed { reason: "time" | "inbound" | "manual", until }。 。 。 .
  • 计划发送 :

    • reply 财务报告和审定财务报表 compose 带 sendAt电子邮件是 scheduled,其线程消息立即被存储 scheduledFor 电子邮件一到,就会被清除。 如果它从来没有去(lib:cancel,或 到那时每个被压制的收件人,信件会保持标记 unsent: true 并发送它离开.
    • scheduled({ threadId }) 列出线索的邮件仍在等待, 最快的第一.
    • cancelScheduled({ emailId, author? }) 取消一个仍在使用的电子邮件 scheduled 或者说 queued 并删除其 来自线条的消息。 线程的计数, 上次和片段, 然后来自留下的消息. 删除未留言的由编成的对话(threadDeleted: true) (中文(简体) ).
    • 时间线条目 : scheduled { emailId, messageId, sendAt } 财务报告和审定财务报表 scheduled_canceled。 。 。 .
  • 出席情况 ("Bia正在回答"):

    • setPresence({ threadId, userId, name, state: "viewing" | "replying" | null, ttlMs? }) 是一个心跳,叫它 大约每10秒. null 叶子.
    • 每个( 线索, 用户) 都得到 presence 排队 计划任务删除行 ttlMs 最后的心跳过后 (违约30 s, 5.120 s.). 查询从不读钟,所以 presence({ threadId }) 订阅者看到行走 当它过期的时候.
    • 在TTL的上半期, 心跳改变什么也没有写。 这样成本就维持在一线 每个用户每~15 s 有小写.
  • 从0.6升级: 老线条没有 hasUnread编辑 lib:maintain (每10分钟,限时) 批次),在新索引被回填后填入. 在那之前,这些线条都不见了 unread: true 从 unreadThreads;其他的计数和列表是正确的.

  • 发送规则 每个邮箱或每个邮箱:

    • setSenderRule({ mailbox?, pattern, action, note?, author? })。 。 。 . pattern 是一个地址( a)[email protected]或一个 域( 选项)acme.com,还有 @acme.com 编号 : *.acme.com),涵盖其子域; [email protected] 也匹配 [email protected]。 。 。 . mailbox 缺席或 null每个邮箱。 每(平面,邮箱)一个规则:设置 重新改变它的动作。 每个邮箱最多5000个.
    • action: "reject": 电子邮件( ) 钩子拒绝信件(setReject("Message rejected")在它存储之前 .eml 或附件. "spam":消息在垃圾邮件对话中存档(它从来没有加入一个不是垃圾邮件, 没有自动复制. "allow":从不以每小时的限值来垃圾邮件,它比一个更广泛的规则(阻断一个域, 允许一个地址.
    • 匹配: From, Reform-To 和信封发送器都会按照它们最具体的规则检查 (地址,然后地址不包含 +tag,然后每个域从最长; 邮箱自己的规则之前 每一个邮箱。 "从" allow 决定何时通过DMARC(不得允许) ;否则最强块获胜(reject 结束 spam) (中文(简体) ).
    • senderRules({ mailbox?, action?, paginationOpts? }) 首先列出最新的mailbox: null: 规则 每个信箱, 随同 mailbox: null, (中文(简体) ), removeSenderRule({ ruleId } | { pattern, mailbox? }), (中文(简体) ), matchSender({ mailbox, email, verified? }) → { effective, rules } (该地址的邮件会收到什么) a"被锁"徽章或"解锁"按钮. 每项规则都计算其决定的信息(hits, (中文(简体) ), lastHitAt) (中文(简体) ).
    • 由过滤器作为垃圾邮件存档的入境信件说: messages.spam 实值 { reason: "rule", rule, pattern, action } 或者说 { reason: "rate" }。 。 。 . removeMailbox 取消规则.
  • 保留: upsertMailbox({ retention: { closedDays: 730, spamDays: 30 } }) 保持闭合和垃圾邮件对话 从最新状况变化或消息中计算出来的多天; 日常工作(lib:purge, 04: 00 (英语) UTC)之后删除对话及其信件,并提交 GC 删除其信件 .eml 还有附件 无 retention (或与 retention: null无所删除.

域数 : @freecorps/ember-email/cloudflare

在 Cloudflare 为组件设置一个域并再次去除。 它只需要这样 fetch因此,它运行在一个工人 (带有自己的API令牌秘密)或者在操作员的机器上(在"工人"里没有令牌). 它没有存储, HTTP API 或密钥:您保持返回状态.

import { destroyDomain, emailResources, resolveAccountId, syncDomain } from "@freecorps/ember-email/cloudflare";

const c = { apiToken, accountId: await resolveAccountId(apiToken, { env: process.env }) };
const { worker, eventsQueue } = emailResources("acme", "prod"); // ember-acme-prod, ember-acme-prod-email-events
let d = await syncDomain(c, { name: "mail.acme.com", enableRouting: true }, { prev: null, mailboxes: ["[email protected]"], worker, queue: eventsQueue });
// d.status: "pending" until Email Service finishes onboarding (a minute or two): sync again with { prev: d }.
// d.records: the DNS records involved; d.notes: what is missing or needs the zone owner.
await destroyDomain(c, d); // rules, event subscription, and the sending domain if this created it

它会做什么:

  • 发送中 : 它在电子邮件服务中的域名上。 Cloudflare管理其记录(bounce MX/SPF,DKIM,DMARC).
  • 接收: 它检查电子邮件运行。 对于区上层, 线路必须已经开通了, 因为打开它 重写区MX. 对于一个子域, enableRouting: true 创建路由子域。 没有 API 删除一个 路由子域,所以去除是一个仪表板步骤.
  • 邮箱 : 它在域名上每个邮箱地址 都增加了一条字面规则 \ 您的工人 。 它永远不会改变规则 那条路通往别的地方.
  • 抓起来: 与 catchAll: true (这个域名的邮箱就是它的包罗万象, upsertMailbox({ catchAll: true }), (中文(简体) ), 所以每个地址都找不到邮箱 也没有地址 被送到了它的To。 仅为区顶,因为规则属于区. 它从不接管一切 那条路通往别的地方. destroyDomain,或同步无 catchAll关掉它.
  • 活动 : 它增加了一个 email.sending 事件订阅 ~ 组件队列 .

离线复制: @freecorps/ember-email/schema

emailSchema: 仅用于组成本地复制图案的客户的组件表(没有邮政缩写),以及 TAG_COLORS。 。 。 .

示例

examples/email-lite 这是最小的应用程序 组件(邮箱、发送、输入). 自由公司/邮件 是一个完整的电子邮件服务 在它上建(Resend-style HTTP API,收件箱,移动应用).

所有组件的状态 生活在一个硬(config.shard默认值 global:main) (中文(简体) ).

见 docs/PLAN.md 第16条和 docs/design/contracts.md ("电子邮件")入库.

驾照:麻省理工.

版本与发布记录

@freecorps/ember-email 共发布 20 个版本,首次发布于 2026/11/06;过去 12 个月发布了 20 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.7.3latest2026/11/123 天前
0.7.22026/11/114 天前
0.7.12026/11/114 天前
0.7.02026/11/114 天前
0.6.02026/11/114 天前
0.5.102026/11/114 天前

关于 @freecorps/ember-email 的常见问题

@freecorps/ember-email 是什么?

Cloudflare上的 Ember 电子邮件组件:通过电子邮件服务、模板、压制和每个项目费率限制、从队列发送事件、寄入邮件(电子邮件查询)分解为邮箱和

如何安装 @freecorps/ember-email?

在项目目录运行 npm install @freecorps/ember-email,或使用 pnpm add / yarn add / bun add。

@freecorps/ember-email 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@freecorps/ember-email 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@freecorps/ember-email 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@freecorps/ember-email 可以商用吗?

@freecorps/ember-email 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@freecorps/ember-email 的下载量有多少?

根据 npm 官方下载统计,@freecorps/ember-email 在 2026/10/16 – 2026/11/14 这 30 天内被下载 4,298 次,日均约 143 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@freecorps/ember-email 还在维护吗?

最新版本 0.7.3 发布于 2026/11/12(3 天前);至今共发布 20 个版本,其中过去 12 个月发布了 20 个;首次发布于 2026/11/06。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。