@fungi.computer/carrier

中文文档
0.2.4发布于 2026/11/08MITTypeScriptES Module近 30 天下载 1万

持久电子邮件提交和收件人发送跟踪

中文使用文档
文档目录14 节
  1. 安装或设置
  2. 快开始
  3. 核心概念
  4. 运营商检查
  5. 员额
  6. D1和PostgreSQL上的邮政交易
  7. API 参考
  8. 实例和检查
  9. 云浮持久对象
  10. 另一个供应商
  11. 运营商检查
  12. 检查
  13. 限制和错误
  14. 贡献和许可证

承运人(@fungi.computer/carrier(中文(简体) )

载体可以持久地传递事物 告诉你真相 这个 有两个部分.

电子邮件. 你打电话 send() 有电子邮件和稳定的身份证。 载人犯法 发送到 SQLite,通过您的提供者发送, 重复临时拒绝,以及 记录提供者所说的话。 收据从未超过 证据显示。 如果回复丢失, 电子邮件为 outcome_unknown,以及 载体不会猜到并发送两次.

邮报. 在命名为 Cloudflare 之间打入、点对点信封 持久物体。 你打电话 send 在与本国相同的交易中 更改, Post 将事件交付给地址对象, 重复, 已死亡 信件和专有收据.

都继续跑 监视者 以持久工作为目的。 云飞 适配器电线 电子邮件到持久对象 SQLite、 提醒和电子邮件服务 绑定,所以你的应用程序没有 自己的送货状态机器.

安装或设置

npm install @fungi.computer/carrier @fungi.computer/watchdog

节点 SQLite 快速开始直接导入 Watchdog 的适配器 .

载体仅限无害环境管理。 这取决于 @fungi.computer/watchdog, (中文(简体) ), effect, (中文(简体) ), pg 财务报告和审定财务报表 @cloudflare/workers-types,该 npm 为您安装。 便携式电子邮件 拥有者和 SQLite Post 运行在任何同步 SQLite 控件上, 如持久 对象存储或 node:sqlite 在节点24。 D1 和 PostgreSQL 邮政适配器 需要一个迁移的数据库.

条目:

  • @fungi.computer/carrier 财务报告和审定财务报表 @fungi.computer/carrier/email电子邮件API, createCarrier 及其类型.
  • @fungi.computer/carrier/cloudflare数字 : createCloudflareCarrier 联合国 对象,以及 cloudflareEmailTransport。 。 。 .
  • @fungi.computer/carrier/inspection:只读操作员检查.
  • @fungi.computer/carrier/post: 后超持久对象 SQLite.
  • @fungi.computer/carrier/post-d1 财务报告和审定财务报表 @fungi.computer/carrier/post-postgres数字 : 超过共享的 D1 或 PostgreSQL .

快开始

这通过内存发送一个邮件 node:sqlite 商店和假货 运输。 换一个真正的供应商 换一个商店 部署时存储对象 .

import { DatabaseSync } from "node:sqlite";
import { fromNodeSqlite } from "@fungi.computer/watchdog/node-sqlite";
import { createCarrier } from "@fungi.computer/carrier";

const database = new DatabaseSync(":memory:");
const owner = fromNodeSqlite(database);

const carrier = await createCarrier({
  storage: owner,
  now: Date.now,
  host: {
    // Commit the mutation together with a durable wake. A demo needs no wake.
    commit: async (mutation) => mutation(),
    requestWake: async () => {},
  },
  transport: async (email) => {
    console.log(`provider got "${email.subject}"`);
    return { kind: "provider_accepted", providerMessageId: "demo-1" };
  },
});

const receipt = await carrier.send({
  id: "welcome:user-123:v1",
  from: { email: "[email protected]", name: "Example" },
  to: ["[email protected]"],
  subject: "Welcome",
  text: "Your account is ready.",
});
console.log(receipt.status); // "queued"

await carrier.tick(); // your wake calls this
console.log((await carrier.get(receipt.id))?.status); // "provider_accepted"

database.close();

send() 只接受电子邮件。 提供商的呼叫发生于 tick(),哪个a 真正的主机从它的持久唤醒而来。 Cloudflare适配器为你做这个 从持久对象提醒.

在持久目标中, ctx.storage 已经是 SQLite 的拥有者 .

核心概念

电子邮件和信件. 一个电子邮件有共享内容和一个信封: from, (中文(简体) ), to, (中文(简体) ), cc, (中文(简体) ), bcc, (中文(简体) ), subject, (中文(简体) ), text, (中文(简体) ), html 或两者兼有,或可选 headers。 。 。 . 每个收件人都成为具有自己身份和作用的单独消息. 这个 普通接口是 send() 财务报告和审定财务报表 get()。 。 。 .

验收. send() 在电子邮件、其收件人信件、 已排队工作, 守夜者将一起行动。 A 类 queued 接收意味着持久当地 验收。 这并不意味着提供者接受或发送电子邮件.

重弹. 使用稳定的源ID通过交接重试. 重复的ID与 相同内容返回现有收据。 改变它的内容,信封, 页: 1 CarrierError 与 code: "conflict"。 。 。 . get() 返回时 undefined 当ID不存在时.

发送证据. 承运人在援引 供养. 成功的响应记录 provider_accepted 联 合 国 providerMessageId. 这是一个总的承认。 这不能证明 每个收件人都被接受或送出,所以收件人信件会留下 unconfirmed。失去的响应或中断的发送成为 outcome_unknown。承运人再也不会自动发送电子邮件,并且 不变的ID重放不会重发它 。 在新ID下发送可以重复 交货。 在调度意图安全恢复之前,工作中断.

状态. 一张收据 status 这是其中之一 queued, (中文(简体) ), dispatching, (中文(简体) ), provider_accepted, (中文(简体) ), outcome_unknown, (中文(简体) ), rejected, (中文(简体) ), refused, (中文(简体) ), expired 或者说 superseded终端收据保存身份并发送证据。 承运人 删除存储的内容.

提供商拒绝和重复. Cloudflare运输解码器 绑定文件错误 code接受前的整个请求拒绝是 provider_rejected 有类的证据 :

  • temporary 代码( e)E_RATE_LIMIT_EXCEEDED, (中文(简体) ), E_DAILY_LIMIT_EXCEEDED, (中文(简体) ), E_INTERNAL_SERVER_ERROR返回电子邮件 queued同一拥有者和 指数回放后再发出,直到重试预算 已经花光了.
  • permanent 代码( 发件人、 收件人、 验证、 大小和页眉错误) 结束 作为 rejected。 。 。 .

收条 providerRejection 带最后一个代码和类. E_DELIVERY_FAILED,没有密码的代码和例外,没有密码中止 outcome_unknown,因为有些收件人可能已经被接受.

受受者所盖. 每个承运商商店接受限值的每封电子邮件数 在滚动窗口中正常的收件人地址 。 从未到达的电子邮件 提供者(重复、拒绝、过期或被取代)不计数。 超过帽子, send() 持久地将划界案记录为 refused 永远不要发送它。 A 类 重放那个ID 返回同样的被拒绝的收据.

过期或被取代的电子邮件. 一般电子邮件不需要过期或有效 调用。 拥有过期信件的来源可以提供 sendBefore,一个 以毫秒计的绝对Unix时间戳。 承运人在发送前检查过 记录 expired 期限一过,就不要给供应商打电话。 对于 邀请或其他可替换的通知,施工也可以采用 canSend: async (emailId) => boolean它必须查阅真相的来源: 返回时 false 当真理降临的时候 无. false 记录 superseded。抛出电子邮件后 以后恢复。 这是送货前的检查 不是原子围栏 同步源变化. 载体不创建或验证认证 标志.

运输业. 提供方和储存方的选择是独立的。 运输费 运输商 Email 并尝试一个提供者。 它回来了 { kind: "provider_accepted", providerMessageId } 仅按实际合计 承认, { kind: "provider_rejected", code, class } 仅为 有文件证明拒绝整个请求,或 { kind: "outcome_unknown" } 何时 接受与否不确定。 全权证书和提供者配置 外部运输 send(). 承运人的本地提交文件 ID 不给你 提供者一能。 每个运输工具处理自己的SDK再试验行为和 事件关联.

主持人. createCarrier({ storage, host, transport, now }) 返回时 send, (中文(简体) ), get, (中文(简体) ), tick 财务报告和审定财务报表 hasPendingWork。 。 storage 观察者的结构 SQLite业主. host.commit(mutation) 必须实施突变和持久 解剖学上醒来. host.requestWake() 必须持续安排恢复。 打电话 tick() 从那时起,你应当保持清醒 hasPendingWork() 实值 没错 每个专卖店和运行时间寿命 精确构建一个所有者 A 类 替换可以在其前身停止后开始. createCloudflareCarrier 为持久对象做所有这些.

运营商检查

@fungi.computer/carrier/inspection 用品 createDeliveryInspection(sql) 用于已经允许操作员的宿主。 读取现有存储 不打开一个送货主, 配置一个提供者, 创建表格, 修复工作或更改提醒。 载体不认证主机 来电 不要将这种能力授予模特或来宾.

每次呼叫扫描最多为64行(默认为32行). 继续 after: page.nextCursor 直到光标 null。光标和邮政收据 密钥使用制片人的密钥, 不使用内部 post: 前缀。 过滤 死字母页面可以是空的,仍然有续作. 这些是订的 扫描,而不是同时写作的快照.

初始化存储返回 { status: "available", records, nextCursor }A级 存储时未返回该送出表 { status: "uninitialized" }成果 包括发送状态、尝试、重试时间和现有提供方证据或 死信的理由。 他们省略了邮件地址和事件,电子邮件机构,信头 和接收地址。 已交付 后行只保留其密钥和状态. 电子邮件 attempts 确定供应商拒绝,所以接受或 模棱两可的首次发送是零。 员额 attempts 计算接收器引用. 载人邮箱 attempts 是一个终端后仅接收的证据 结果。 在等待交货时, 监视者 attempt_count 有权威性; 等待检查,从监视者那里读到的数.

员额

监视器应用 使用 Almanac 的后退重试政策 计算:最初2秒,双倍为5分钟上限,最多20分钟 接收器尝试。 同步和SQL适配器共享该默认政策; 明确的 SQL 重试策略会保留自身的延迟 。 SQLite 路线 deadlineMs 使用其有限的寿命,而不是尝试上限。 阿尔马纳克语Name 仅提供算术,而不是第二个排程器或重试所有者.

@fungi.computer/carrier/post 发送打入的点对点信封 名称为持久对象. definePostRoute 每个地址计划和事件 具有持久对象命名空间、名称函数和固定的图案 addressKind 标签。 SQLite 支持的路线可选择最多7天的重试寿命 与 deadlineMs; 邮局在入境时保留其绝对期限并结算 未交付行作为 expired 当最后期限到来时,一去不复返. 最终路线重试直至到期,而不是使用普通尝试 上限。 截止期限保留了现有的重试预算。 每条事件路线 到同一名称空间 共享该标签, 因此事件类型不同 接收者维持生产订单 。 接收器设备 deliver(envelope) 财务报告和审定财务报表 电话 admitPost 因此事件突变和 入院收据共同承诺。 键必须识别整个生成事件 所有发送者到接收器.

构造 createPost 与制作方的结构 SQLite owner键入 routes监视者 transactional 投影、钟表和耐用 scheduleWake。邮局拥有自己的桌子。 打电话 send 在同一交易内 制片人的状态变化 和持久的初醒。 它把信封装上 和观察者一起工作。 每个制片人的活动都达到了 地址顺序。 即将卸任的前任拥有其继任者直至 送还是送? 重放相同的稳定密钥会保留已有的工作或其 完成收据。 保留这些收据的同时 源可以重放.

电线监视犬 retryPolicy 改为 post.retryPolicy 及其执行者 (job, signal, attempt) => post.execute(job, attempt, signal)。监视者 保留接收I/O之前的尝试和倒置期限 相同的工作和信封密钥; wakeState(now) 报告准备情况及下一次报告准备情况 截止日期。 送货回收从不借代理崩溃预算 一个接收器 在丢失回复或收件后,必须承认重复的密钥.

latest(kind, address) 读取最新阶段活动或收悉 收件人不扫描其他收件人。 成员或操作员命令可以 调用 retry(key) 在制作人的交易中 等待着它的清醒 这个 加快已排队的重试而不重设其尝试计数, 或给出一个已死亡 在当前队列后写出新工作,同时保留它准备的事实。 队列 和运行的工作 留在监视犬, 和交付的收据永远不会重新打开.

一条路线可以准备一个线性事实 从它的预示意图 prepare: { event: wireSchema, run(address, intent, signal) }. 筹备和 接收器I/O共享10秒的尝试期限. 员额不变 在寄出之前准备好信封 恢复和失去反应再利用 确切的事实,而原意图 仍然识别重复 send迟到 准备结果在尝试期满后不能发出。 使用 onDelivered 改为 承认与 Post 相同的生产者交易中的域分类账 收讫. 承认失败后,两者都有待重新试验。 拟就的事实 应携带密封赠款,不得携带原始证书.

明确拒绝、用尽预算或路由截止日期 死信封 理由和尝试数. deadLetters 返回时 letters 财务报告和审定财务报表 nextCursor,包括通过载有 只有收据.

D1和PostgreSQL上的邮政交易

@fungi.computer/carrier/post-d1 财务报告和审定财务报表 /post-postgres 绑定普通邮政 地址和事件注册到 Watchdog 的数据库适配器。 这些关系 适配器使用相同的"监视犬"尝试所有者,不支持路由最后期限. 用设置路由的路由构造任一适配器 deadlineMs 掷出 PostError 与 reason: "relational-deadline-unsupported" 在数据库之前 进入。 使用 SQLite 备份的 Post 为截止日期路径 。 应用 postD1Schema 或者说 postPostgresSchema 与匹配的监视者计划一起通过您的 迁移工具.

在 D1 时, post.send(envelope) 返回信封和工作报表。 包含 都一样 database.batch 作为制片人的事实. sendFromSelect 接受 一个可信任的本地 SQLite ?NNN- 捆绑的SELECT产生 post_key, (中文(简体) ), envelope_json, (中文(简体) ), queue_name 财务报告和审定财务报表 job_id,用于创建有条件源事实 在那批货里 邮递SQL post.send(client, envelope) 在两行上写 制作方现有的交易客户端。 既不发送 API 承诺 监制. 互相冲突的重放将整个交易卷回.

电线相同 retryPolicy 和以"SQLite Post"为原生的"三辩"执行人. 主机保留了耐久的克龙或克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克. 准备、有限度的接收电话、答复解析和和解决定 与 SQLite Post 共享。 接收抑制源重放, 失败尝试 以同样的邮政政策重新尝试, 永久拒绝或疲劳成为死亡 信件,以及较早的待发邮件区块,之后邮寄到同一地址。 A 类 接收器可以返回 { status, receipt } jSON 接收元数据。 员额 将它固定在终端结算上,并在交付的收据上显示,或 废信。 这就是电子邮件制作人可以记录 提供者接受,同时 只有在交货时才能伪造全权证书。 这些同步适配器拒绝 同步的 SQLite 接收钩而不是在交易之外运行它们.

purgeD1Post 财务报告和审定财务报表 purgePostgresPost 返回选定的参数化语句 由信任的制作人 SQL 提交 queue_name。把它们都组成相同的 本地批量或交易作为源代码去除 。 他们取出信封 每一次监视者尝试, 包括定居的尝试。 PostgreSQL 锁定所选内容 先是信封,所以重叠的重试不能留下孤儿的工作.

API 参考

生成的引用在 文档/api。它有一页 每个切入点:电子邮件根和 email, (中文(简体) ), cloudflare, (中文(简体) ), inspection, (中文(简体) ), post, (中文(简体) ), post-d1 财务报告和审定财务报表 post-postgres。 。 。 .

实例和检查

云浮持久对象

使用持久对象本身的构造一个载体 存储和电子邮件服务绑定。 转发对象提醒 :

import { DurableObject } from "cloudflare:workers";
import type { SendEmail } from "@cloudflare/workers-types";
import type { Submission } from "@fungi.computer/carrier";
import { createCloudflareCarrier } from "@fungi.computer/carrier/cloudflare";

type Env = { EMAIL: SendEmail };

export class Mailbox extends DurableObject<Env> {
  private readonly carrier;

  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);
    this.carrier = createCloudflareCarrier({
      storage: ctx.storage,
      email: env.EMAIL,
    });
  }

  async send(email: Submission) {
    return (await this.carrier).send(email);
  }

  async get(id: string) {
    return (await this.carrier).get(id);
  }

  async alarm() {
    await (await this.carrier).alarm();
  }
}

配置 SQLite 持久对象命名空间和装订在 主机应用程序. 有约束力的申请 MAILBOX 那么,命名空间可以 提交并检查邮件 :

const mailbox = env.MAILBOX.getByName("team-example");
const receipt = await mailbox.send({
  id: "welcome:user-123:v1",
  from: { email: "[email protected]", name: "Example" },
  to: ["[email protected]", "[email protected]"],
  cc: ["[email protected]"],
  bcc: ["[email protected]"],
  subject: "Welcome",
  text: "Your account is ready.",
  headers: { "List-Unsubscribe": "<https://example.com/unsubscribe/token>" },
});
const current = await mailbox.get(receipt.id);

send() 整个To、CC和BCC至少需要一个接受者。 为可选. 时 至少一个 text 或者说 html 需要时间。 显示名称并可见 To和 CC 角色传给提供者不变。 BCC使用提供者的本地BCC域. 收据将所有收件人地址, 包括BCC, 暴露给信任的来电者,所以 不将整张收据转发给收款人或其他观众。 可选提供者 信头是不可改变提交的一部分。 不同信头的回放 冲突就像有不同内容的回放.

另一个供应商

要使用另一个具有持久对象 SQLite 和提醒的提供者, 请通过 transport 改为 email需要的正是两者之一:

const carrier = await createCloudflareCarrier({
  storage: ctx.storage,
  transport: sendWithConfiguredProvider,
});

运营商检查

const inspection = createDeliveryInspection(ctx.storage.sql);
const emails = inspection.email({ limit: 32 });
const receipts = inspection.post({ view: "receipts", limit: 32 });
const dead = inspection.post({ view: "deadLetters", limit: 32 });

检查

单位测试使用受控I/O、邮购、, 在本地 SQLite 和操作器上回滚、重复录入和恢复 检查。 Cloudflare运行时间测试练习 SQLite,警报,驱逐, 中断发送和 D1 Post 适配器, 不装有活的电子邮件 。 这个 PostgreSQL 证明在私人 Unix 上启动一个孤立的 PostgreSQL 16 个实例 插座并永远停止它。 安装 PostgreSQL 16, 或设置 BOTANICAL_TEST_POSTGRES_ROOT 至其取出之根. D1 和 PostgreSQL 地图 证据包括制作人、信封和工作回滚以及承诺、重放冲突, 重启、工人重叠和围栏租赁到期。 从包 目录 :

pnpm test

这些固定装置没有发送电子邮件 .

限制和错误

错误.

  • CarrierError 有一个 code 联 合 国 invalid_input, (中文(简体) ), conflict 或者说 unavailable。 。 。 . 它的信息从不包含电子邮件机构,令牌或原始提供者错误.
  • 投递 PostError 用于未知路径或相冲突的密钥.
  • 提高检查 DeliveryInspectionError("invalid_query") 错误界限 和消毒者 DeliveryInspectionError("unavailable") 无效存储 证据或存储失败。 检查密钥仅限于256个字符 和提供商的收据 身份证为1 024.

记录. 每个失败、未知或拒绝的电子邮件发送一个 console.error 线条, "Carrier delivery failed",与划界案 id, (中文(简体) ), class,已消毒 cause,结果 status 对于拒绝供货者 attempt。没有记录内容、地址或提供者信息.

政策编号.

  • 收件者上限:每滚动一次,每个正常收件者地址接收5封电子邮件 小时,每家运货店数起.
  • 电子邮件重试:30秒后临时重试,翻倍, 上限为15分钟,最多6次供应商尝试。 那么,电子邮件是 rejected。 。 。 .
  • 复试:2秒,翻倍,5分钟封顶,20多台接收机 尝试.
  • 事后尝试:准备和接收器I/O共用10秒的最后期限.

保证与空白.

  • 根本就没有一劳永逸的承诺. outcome_unknown 最终结果,a 同一邮件的新ID可以复制它.
  • 收件人信件保留 unconfirmed. 承运人不摄取提供者 交付事件或将其与收据联系起来,且不会过期 终端收据.
  • 一次跑一个店主 同一人同时拥有 存储不支持.
  • 测试套件不进行真正的邮件发送, 提供者 MIME 生成 或实际的密闭.

贡献和许可证

运输商持有麻省理工学院执照. 见 自由主义。 。 。 .

版本与发布记录

@fungi.computer/carrier 共发布 8 个版本,首次发布于 2026/10/15;过去 12 个月发布了 8 个版本。

版本发布日期距今
0.2.4latest2026/11/087 天前
0.2.32026/11/087 天前
0.2.22026/11/088 天前
0.2.12026/11/0412 天前
0.2.02026/10/2620 天前
0.1.02026/10/2323 天前

关于 @fungi.computer/carrier 的常见问题

@fungi.computer/carrier 是什么?

持久电子邮件提交和收件人发送跟踪

如何安装 @fungi.computer/carrier?

在项目目录运行 npm install @fungi.computer/carrier,或使用 pnpm add / yarn add / bun add。

@fungi.computer/carrier 支持 TypeScript 吗?

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

@fungi.computer/carrier 支持 ESM 还是 CommonJS?

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

@fungi.computer/carrier 可以商用吗?

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

@fungi.computer/carrier 的下载量有多少?

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

@fungi.computer/carrier 还在维护吗?

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