# @freecorps/ember-email 中文文档

规范地址：https://npmcn.com/package/%40freecorps/ember-email/

版本：0\.7\.3
npm 元数据更新时间：2026-11-12T14:05:43.377Z
中文译文更新时间：2026-11-15T13:20:34.798Z
软件包许可：MIT

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40freecorps/ember-email
原文对照：https://npmcn.com/package/%40freecorps/ember-email/?lang=original
项目主页：https://github.com/freecorps/ember/tree/main/packages/email#readme
源码仓库：https://github.com/freecorps/ember

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：402 kB，31 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- Node.js 要求：\>=22
- 运行依赖：4 个
- 首次发布：2026-11-05T16:51:09.317Z
- 最新版本发布：2026-11-12T14:05:42.978Z
- 近 30 天下载量：4298（2026-10-16 至 2026-11-14，npm 官方下载统计）
- 已发布版本数：20（近 12 个月 20 个）

## 中文简介

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

## 安装

```sh
npm install @freecorps/ember-email
```

也可以使用：`pnpm add @freecorps/ember-email`、`yarn add @freecorps/ember-email`、`bun add @freecorps/ember-email`

## README

# @freecorps/ember-电子邮件

作为电子邮件 [琥珀](https://github.com/freecorps/ember) cloudflare电子邮件服务(发送)和电子邮件 跑步 (接收). 它运行在你自己的Cloudflare账户.

```ts
// 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") },
  },
});
```

```ts
// 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 <hello@acme.com>", 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)`ana@acme.com`或一个 域( 选项)`acme.com`,还有 `@acme.com` 编号 : `*.acme.com`),涵盖其子域; `ana@acme.com` 也匹配 `ana+anything@acme.com`。 。 。 . `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 或密钥:您保持返回状态.

```ts
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: ["support@mail.acme.com"], 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`](https://github.com/freecorps/ember/tree/main/examples/email-lite) 这是最小的应用程序 组件(邮箱、发送、输入). [自由公司/邮件](https://github.com/freecorps/mail) 是一个完整的电子邮件服务 在它上建(Resend-style HTTP API,收件箱,移动应用).

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

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

驾照:麻省理工.

## 常见问题

### @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 和提交记录判断。

