@krizaka/mail

中文文档
0.1.3发布于 2026/11/01Apache-2.0TypeScriptES Module构建来源已验证

Mail for every Krizaka product: queued in the caller's transaction through the outbox, delivered once per idempotency key, never to a suppressed address (stored as a hash); providers (Mailgun, Resend, log) over an injected fetch; bounces and complaints fr

原始使用文档

@krizaka/mail

Mail for every Krizaka product: queued in the caller's transaction through @krizaka/outbox, delivered once per idempotency key, never to a suppressed address, with the provider the admin chose. No vendor SDK, no environment variable, no wall clock, no database client.

  • It takes mail already rendered. Templates are @krizaka/email's (a UI package this one never imports). emailPorts(store, clock) gives @krizaka/email its three ports (DeliveryLog, SuppressionList, IdempotencyStore) over the same tables, so a direct sendEmail and the queue share one log, one suppression list and one set of keys.
  • At least once, never lost. deliver checks the suppression list, checks the key, sends, and only then claims the key: a crash between the send and the claim sends again (a duplicate reset mail beats a lost one); a redelivery after the claim is a no-op.
  • Addresses are hashed (SHA-256 of the normal form) in the suppression list and in the delivery log. The queued message holds the address until the outbox purges it.
  • Providers are adapters over an injected fetch: mailgunProvider, resendProvider, logProvider (development, writes no body). providerFor(settings.provider, keys, fetch, write) picks the one the admin chose and refuses a provider without its key instead of falling back to log.
  • Bounces and complaints come back through the provider's signed webhook: parseMailgunWebhook and parseResendWebhook verify the signature and the age, and answer the addresses to suppress (a permanent failure or hard bounce, a complaint, an unsubscribe). A transient bounce is ignored.
  • The admin's settings (@krizaka/mail/settings): mail.provider (log, mailgun, resend), mail.from, mail.replyTo, mail.retentionDays. Keys, the Mailgun domain and the webhook signing keys stay in the environment (MAIL_PROVIDER-style names, read by the product and passed in).
import { createOutbox, runWorker } from "@krizaka/outbox";
import { createMail, dbMailStore, MAIL_TOPIC, providerFor } from "@krizaka/mail";
import { mailSettings, readMailSettings } from "@krizaka/mail/settings";

const mail = createMail({
  clock,
  outbox: createOutbox({ clock, settings: outboxSettings }),
  settings: () => readMailSettings((key) => settings.get(key)),
  store: dbMailStore(query),
  provider: (s) => providerFor(s.provider, { apiKey: env.MAIL_API_KEY, domain: env.MAIL_DOMAIN }, fetch, console.log),
});

// in the use case's transaction: the mail leaves only if the transaction commits
await mail.queue(tx, { to, subject, text, category: "transactional", template: "password-reset", locale: "fr", idempotencyKey: `reset:${resetId}` });

// in the worker process
await runWorker({ outbox, query, owner, topic: MAIL_TOPIC, deliver: (m) => mail.deliver(m).then(() => undefined), sleep });

// in the webhook route
const suppression = parseMailgunWebhook(rawBody, env.MAIL_WEBHOOK_SIGNING_KEY, nowSeconds);
if (suppression) await mail.suppress(suppression);

Tables (schema mail, mailSchema): delivery, suppression, idempotency. Event: evt.mail.requested.v1 on topic mail. mailStoreContract (@krizaka/mail/testkit) is the contract every store runs.

Apache-2.0.

版本与发布记录

@krizaka/mail 共发布 5 个版本,首次发布于 2026/10/27;过去 12 个月发布了 5 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.1.3latest2026/11/011 天前
0.1.22026/10/304 天前
0.1.12026/10/295 天前
0.1.02026/10/277 天前
0.0.0-stage2026/10/277 天前

关于 @krizaka/mail 的常见问题

@krizaka/mail 是什么?

每个Krizaka产品的邮件:通过发件箱在调用者的事务中排队,每个幂等键发送一次,从不发送到被抑制的地址(作为散列存储);提供商(Mailgun, Resend, log)通过注入获取;反弹和抱怨

如何安装 @krizaka/mail?

在项目目录运行 npm install @krizaka/mail,或使用 pnpm add / yarn add / bun add。

@krizaka/mail 支持 TypeScript 吗?

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

@krizaka/mail 支持 ESM 还是 CommonJS?

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

@krizaka/mail 需要什么版本的 Node.js?

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

@krizaka/mail 可以商用吗?

@krizaka/mail 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@krizaka/mail 还在维护吗?

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