@bitbaum/mail-kit

中文文档
0.2.0发布于 2026/10/22MITTypeScriptES Module零依赖构建来源已验证近 30 天下载 1.1万

The fleet's one email layer: Resend over raw fetch, zero dependencies, honest never-throwing results, sandbox/placeholder guards, and the shared-sender convention encoded once.

原始使用文档
文档目录8 节
  1. Install
  2. Use
  3. One address per app: hello@<host>
  4. Mail to an app
  5. AI triage
  6. Env contract (the whole of it)
  7. Fleet conventions
  8. What this is not

@bitbaum/mail-kit

The fleet's one email layer. One provider (Resend), one env contract, one call shape, zero runtime dependencies.

Exists because the fleet grew ~12 bespoke email implementations across three provider styles, and a dead SMTP credential silenced two apps' outbound mail for months without anyone noticing. Uniformity is the fix.

Install

pnpm add @bitbaum/mail-kit

Use

import { sendMail, isMailConfigured, mailHealth, conventionalFrom } from "@bitbaum/mail-kit";

const result = await sendMail({
  to: "[email protected]",
  subject: "Your invoice",
  html: "<p>…</p>",
  attachments: [{ filename: "invoice.pdf", content: pdfBuffer, contentType: "application/pdf" }],
});

if (!result.sent) {
  // result.error, result.status?, result.retryable — sendMail NEVER throws.
  logger.error("invoice email failed", result);
}
  • sendMail never throws. A thrown email error inside a server action reaches nobody and destroys the form; a { sent: false } you must look at is deliverable. Callers where delivery matters check .sent.
  • isMailConfigured() — gate flows that depend on an email arriving (password reset, invoices). A placeholder key and a production sandbox sender (@resend.dev) count as unconfigured: both look configured and deliver to nobody.
  • mailHealth() — key-validity + domain-status probe for /api/health. A passing probe does not prove delivery; only a real send does.
  • options.idempotencyKey — pass from crons/queues so a retried job cannot double-send (Resend dedupes for 24h).

One address per app: hello@<host>

Every app has one address, on its own host, that a customer can guess: [email protected], and [email protected] once a site has its own domain. appAddress(url) builds it; appAddressFromEnv() reads MAIL_ADDRESS or derives it from APP_URL / NEXT_PUBLIC_SITE_URL / AUTH_URL.

sendMail puts that address in Reply-To on every message (unless the call sets its own, or already sends from it). Mail still leaves through the shared sender until each host can sign its own — but every answer reaches the app.

Mail to an app

The fleet's mail gateway receives for every app host, checks SPF/DKIM/DMARC, and POSTs an InboundMail to /api/mail/inbound, signed with MAIL_INBOUND_SECRET (x-mail-signature: t=…,v1=hmac). The app side is one line:

// app/api/mail/inbound/route.ts
import { inboundHandler } from "@bitbaum/mail-kit";
export const POST = inboundHandler({ onMail: (mail) => saveAndTriage(mail) });

Unsigned, tampered, stale (> 5 min) or oversized deliveries are refused before your code runs; a thrown onMail answers 500 so the gateway retries.

AI triage

triageMail(mail, complete, context) asks a model (any complete() — the ai-forms / ai-kit shape) for kind, urgency, a one-line summary, the sender's language and needsHuman. It never throws: no model, or nonsense back, and the answer comes from plain rules, marked by: "rules". Instructions inside an email are described, never followed.

Env contract (the whole of it)

VarMeaning
RESEND_API_KEYrequired to send (a fleet key: /opt/fleet/env)
RESEND_FROMsender — Name <addr> or bare address (optional if every call passes from)
MAIL_ADDRESSthe app's own address; optional, derived from the app's URL
MAIL_INBOUND_SECRETverifies mail from the gateway (a fleet key: /opt/fleet/env)

Fleet conventions

  • The shared Resend account is on the free tier: 1 verified domain, 100 emails/day, 3,000/month. Hitting the daily cap surfaces as { sent: false, status: 429, retryable: true }.
  • Only loki.orangecat.ch is verified, so every app sends as <app>@fleetcrown.orangecat.ch — conventionalFrom("My App") builds it.
  • A daily canary on the box sends one real email and alerts Telegram when it fails — that, not this package, is what proves delivery keeps working.

What this is not

  • Not a template engine — apps own their HTML.
  • Not a queue — retryable tells a caller whether a retry can help; scheduling one is the caller's decision.
  • Not for bulk — newsletters go through Listmonk.

Part of bitbaum — AI-native products on open infrastructure, built in Zürich. Every package here lists the apps that use it: which apps use mail-kit.

版本与发布记录

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

版本发布日期距今
0.2.0latest2026/10/2217 天前
0.1.12026/09/052 个月前
0.1.02026/09/052 个月前

关于 @bitbaum/mail-kit 的常见问题

@bitbaum/mail-kit 是什么?

The fleet's one email layer: Resend over raw fetch, zero dependencies, honest never-throwing results, sandbox/placeholder guards, and the shared-sender convention encoded once.

如何安装 @bitbaum/mail-kit?

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

@bitbaum/mail-kit 支持 TypeScript 吗?

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

@bitbaum/mail-kit 支持 ESM 还是 CommonJS?

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

@bitbaum/mail-kit 需要什么版本的 Node.js?

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

@bitbaum/mail-kit 可以商用吗?

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

@bitbaum/mail-kit 的下载量有多少?

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

@bitbaum/mail-kit 还在维护吗?

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