@btravstack/邮件
邮件端口用于
@btravstack/core页:1Mailer申请 取决于记录适配器、SMTP适配器和 每发件的间隔、计数和日志行.
pnpm add @btravstack/mailer @btravstack/core @btravstack/config @btravstack/di unthrown
4个同伴依赖关系,加 nodemailer ——可选的,只有你需要
作 SMTP 适配器. 仪器不需要自己的同伴:
Logger, (中文(简体) ), Tracer 财务报告和审定财务报表 Meter 这取决于是
内核的端口。 。 。 .
节点 >=22。 。 。 .
实例
通知 和诚实的失败臂:
import { Mailer } from "@btravstack/mailer";
import { Port, Provider } from "@btravstack/di";
import type { AsyncResult } from "unthrown";
class Notifier extends Port("ReadmeNotifier")<{
readonly placed: (
to: string,
orderId: string,
) => AsyncResult<void, RetryableError>;
}> {}
export const notifier = Provider(Notifier)({
inject: { mailer: Mailer },
sync: ({ mailer }) => ({
placed: (to, orderId) =>
mailer
.send({
from: "[email protected]",
to: [to],
subject: `order ${orderId} placed`,
text: `Order ${orderId} is on its way.`,
})
// A failed send is YOUR transport's problem, which is why the port
// models it instead of retrying: leaving the delivery un-acked
// hands it to the next worker, on the broker's own budget.
.mapErrCases((matcher) =>
matcher.with(
P.tag("MailNotSent"),
(error) =>
new RetryableError(
`order ${orderId} was not notified: ${error.reason}`,
),
),
),
}),
});
组成——适配器是唯一的决定,作出 这里和其他地方:
import { mailer } from "@btravstack/mailer";
import { smtpMailer } from "@btravstack/mailer/smtp";
export const NotifierApp = Module("NotifierApp")({
imports: [
// Instrumented by default: a span, a counter and a log line per send,
// which is why `observability()` and `otel()` are below. Pass
// Every call is reported through `Observers`. Drop the two modules
// below and this composition is unchanged — it just reports to nobody.
mailer({ adapter: smtpMailer() }),
observability(),
otel(),
],
provides: [notifier],
exports: [Notifier, Logger],
});
在一个测试中,适配器 发送什么,保存一切:
import { mailRecorder, recordingMailerProvider } from "@btravstack/mailer";
import { overridden } from "@btravstack/testing";
const recorder = mailRecorder();
const app = overridden(NotifierApp, [recordingMailerProvider(recorder)]);
// … place an order through the real graph …
expect(recorder.only()).toEqual(
expect.objectContaining({ subject: "order 42 placed" }),
);
选项
| 选项 | 何处 | 这是什么 |
|---|---|---|
adapter | mailer({ adapter }) | 适配器模块提供 MailerBackend 所需数额 |
SMTP_URL | 环境,读作 smtpMailer() | 传输 URL —— 需要, 在图表构建中验证 |
完整表——默认,语义和推理——活在 参考页, (中文(简体) ), 这张名单是详细的.
它决定什么,它没有
决定 每一次的派遣,都交给 任何贡献
Observers ——没有旗帜,也没有选择退出,因为一个图构成没有
观察者只给一个惰性电话 并向任何人报告 它决定:
SMTP适配器提供健康检查—— verify(), (中文(简体) ),
连接和认证,折叠到内核 /healthz 与
无线,而录音适配器则无线
免费报告健康情况。 它决定一个发送是 已接受没有
失败是模拟 MailNotSent 携带信封
永远不要让尸体.
它没有决定 失败后会发生什么——重试属于你
运输,示例回答a RetryableError 所以经纪人的预算
拥有它. Mail 携带整个信封—— cc, (中文(简体) ), bcc, (中文(简体) ), attachments 财务报告和审定财务报表
headers 旁边 to, (中文(简体) ), subject 和两具尸体
微调, 不批量发送, 也没有地址验证—— 每个原因都在进行
参考页, (中文(简体) ),
同样的推理是 AGENTS.md 用于贡献者.
许可证
麻省理工学院