@krizaka /邮件
每个Krizaka产品的邮件:在调用者的事务通过中排队 @krizaka/outbox,每个幂等键传递一次,永远不会发送到被抑制的地址,由管理员选择提供程序。没有供应商SDK,没有环境变量,没有挂钟,没有数据库客户端。
- 它需要已经送达的邮件。 模板是
@krizaka/email’s(这个UI包永远不会导入)。emailPorts(store, clock)给了@krizaka/email它的三个港口(DeliveryLog,SuppressionList,IdempotencyStore)在同一张桌子上,所以是直接的sendEmail并且队列共享一个日志、一个抑制列表和一组键。 - 至少有一次,从未输过。
deliver检查抑制列表,检查密钥,发送,然后才声明密钥:发送和声明再次发送之间的崩溃(重复的重置邮件胜过丢失的邮件);索赔后的再发货是无效的。 - 地址是散列的 (标准格式的SHA-256)在抑制列表和交付日志中。排队的邮件将保留该地址,直到发件箱将其清除。
- 供应商 适配器是通过注入的吗
fetch:mailgunProvider,resendProvider,logProvider(发展,写无体)。providerFor(settings.provider, keys, fetch, write)选择管理员选择的,并拒绝没有密钥的提供者,而不是退回到log. - 反弹和抱怨 通过提供商的签名webhook返回:
parseMailgunWebhook和parseResendWebhook验证签名和年龄,并回答地址抑制(永久失败或硬反弹,投诉,退订)。瞬态反弹被忽略。 - 管理员的设置 (
@krizaka/mail/settings):mail.provider(log,mailgun,resend),mail.from,mail.replyTo,mail.retentionDays. 密钥、Mailgun域和webhook签名密钥留在环境中(MAIL_PROVIDER样式名称,由产品读取并传入)。
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);
表(模式 mail, mailSchema): delivery, suppression, idempotency. 事件: evt.mail.requested.v1 在主题 mail. mailStoreContract (@krizaka/mail/testkit)是每家商店都遵守的合同。
apache - 2.0。