# @easybits\.cloud/mailmask 中文文档

规范地址：https://npmcn.com/package/%40easybits.cloud/mailmask/

版本：0\.9\.2
npm 元数据更新时间：2026-11-11T21:36:02.041Z
中文译文更新时间：2026-11-14T12:35:34.095Z
软件包许可：MIT

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40easybits.cloud/mailmask
原文对照：https://npmcn.com/package/%40easybits.cloud/mailmask/?lang=original

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：270 kB，10 个文件
- 模块格式：ESM + CJS
- TypeScript 类型：包内自带
- 运行依赖：0 个
- 首次发布：2026-03-04T15:25:50.778Z
- 最新版本发布：2026-11-11T21:36:01.838Z
- 近 30 天下载量：2043（2026-10-13 至 2026-11-11，npm 官方下载统计）
- 已发布版本数：31（近 12 个月 31 个）

## 中文简介

MailMask 电子邮件别名/转发 API 的官方 SDK

## 安装

```sh
npm install @easybits.cloud/mailmask
```

也可以使用：`pnpm add @easybits.cloud/mailmask`、`yarn add @easybits.cloud/mailmask`、`bun add @easybits.cloud/mailmask`

## README

# @ easybits. cloud/ mailmask (英语)

正式类型SDK [邮件掩码](https://mailmask.studio/) 电子邮件地址,邮箱和转发 API.

## 安装

```bash
npm i @easybits.cloud/mailmask
```

## 快开始

```ts
import { MailMask } from "@easybits.cloud/mailmask";

const mm = new MailMask({ apiKey: "mk_..." });

// Get your domains
const domains = await mm.domains.list();
const domainId = domains[0].id;

// Create an address — emails to hello@yourdomain.com forward to your inbox
const address = await mm.addresses.create(domainId, {
  alias: "hello",
  destinations: ["you@example.com"],
});

// Send an email (routed through MailMask for high deliverability)
await mm.send.send(domainId, {
  from: "hello",              // must be an active address; defaults to `noreply`
  fromName: "Acme",           // optional display name: Acme <hello@yourdomain.com>
  to: "client@example.com",
  subject: "Welcome!",
  html: "<p>Thanks for signing up.</p>", // or `body` (plain text) / `markdown`
});

// List addresses for a domain
const addresses = await mm.addresses.list(domainId);

// Add a domain — returns the DNS records to configure (MX, TXT, DKIM, SPF)
const { domain, dnsRecords } = await mm.domains.create("yourdomain.com");

// An address with an IMAP mailbox (activated domain): password is returned once
const inbox = await mm.addresses.create(domainId, { alias: "sales", mailbox: true });
// `mm.aliases` still works: it is the same resource under its old name.
console.log(inbox.buzon?.password, inbox.buzon?.imap);
```

## MCP 磁共振

同样的行动也提供给大赦国际的代理人 `https://www.mailmask.studio/mcp` (Streamable HTTP, , ) (St, `Authorization: Bearer mk_...`) (中文(简体) ). 见 [https://www.mailmask.studio/docs#mcp 互联网档案馆的存檔,存档日期2013-12-02](https://www.mailmask.studio/docs#mcp)。 。 。 .

## 再来

```ts
// Attachments, copies, threading, idempotency
const pdf = await mm.attachments.upload(domainId, { filename: "invoice.pdf", contentType: "application/pdf", data: bytes });
await mm.send.send(domainId, {
  from: "billing", to: "client@example.com", cc: ["cfo@example.com"],
  subject: "Re: Invoice 42", markdown: "Attached.", attachments: [pdf],
  inReplyTo: "<abc@mail.example.com>", references: "<abc@mail.example.com>",
}, { idempotencyKey: `invoice-42` });

// Account: identity + usage, and a full JSON export
const me = await mm.account.me();
const backup = await mm.account.export();

// Upload an image to embed in an outgoing email (returns a public url)
const { url } = await mm.domains.uploadImage(domainId, imageBlob);

// Suppression list (hard bounces / complaints)
await mm.suppressions.list(domainId);
await mm.suppressions.remove(domainId, "fixed@example.com");

// Webhooks (activated domain) — secret is shown once
const wh = await mm.webhooks.create(domainId, { url: "https://app.example.com/hooks/mailmask", events: ["email.received", "email.delivered", "email.bounced"] });

// On your server: verify the signature with the raw body
import { verifyWebhookSignature } from "@easybits.cloud/mailmask";
const ok = await verifyWebhookSignature(secret, { signature: req.headers["x-mailmask-signature"], timestamp: req.headers["x-mailmask-timestamp"] }, rawBody);
```

## 合作伙伴

对于发送电子邮件的应用程序 **代表自己的客户** (每个客户都是 _租户_ 联合国 (b) 拥有所有权、配额和声誉。 使用合伙人证书(`mmk_…` + 键 `mms_…` 秘密),而不是一个 `mk_` 键。 每一份申请均自动与HMAC-SHA256签署; `send`编号 :`batch` 得到一个 `idempotencyKey` 当你不通过一个(每一次重试上相同的钥匙)时.

运输(0.6):

-   **检索** 5xx,网络出错和超时——与焦急的指数倒置 (单位:千美元)`retryBaseMs` 500~250~500 ms,500~1000 ms,.),荣誉 `Retry-After`。只有安全请求 重审:获得/PUT/DELETE, `tenants.pause`编号 :`resume`, (中文(简体) ), `domains.add` 财务报告和审定财务报表 `send`编号 :`batch` (他们总是) (a) 携带一能键. `webhook.rotateSecret` 财务报告和审定财务报表 `webhook.test` 从来没有被重审。 若为 `Retry-After` 要求超过 `retryMaxDelayMs` (默认 30 s) 错误随 `retryAfterMs` 所以你决定. `maxRetries` 默认 3,最大 10; `onRetry` 钩取出你的日志.
-   **超时** 每次尝试 `AbortController` (单位:千美元)`timeoutMs`,默认为30 s) ∗∗ `TimeoutError`。 。 。 .
-   **秘密远离日志**:秘密不是客户的财产(`console.log(mm)` 没办法 打印),没有错误消息随附.

```ts
const mm = new MailMaskPartner({
  keyId: process.env.MAILMASK_KEY_ID!, secret: process.env.MAILMASK_SECRET!,
  timeoutMs: 10_000, maxRetries: 5,
  onRetry: ({ attempt, delayMs, error }) => console.warn(`mailmask retry ${attempt} in ${delayMs} ms`, error.kind, error.requestId),
});
```

例:Formmy从客户域发送通讯.

```ts
import {
  MailMaskPartner, PausedError, QuotaError, RecipientSuppressedError, ReputationHoldError, ValidationError, verifyWebhook,
} from "@easybits.cloud/mailmask";

const mm = new MailMaskPartner({ keyId: process.env.MAILMASK_KEY_ID!, secret: process.env.MAILMASK_SECRET! });

// 1. One tenant per Formmy customer (idempotent: call it on every sign-up/update)
await mm.tenants.put("formmy-user-42", { name: "Panadería La Espiga" });

// 2. Their domain: show these DNS records to the customer, then poll the status
const { records } = await mm.domains.add("formmy-user-42", "laespiga.mx");
// records: [{ type: "TXT", name: "_amazonses", value: "…", level: "requerido" }, …]
const { state } = await mm.domains.status("formmy-user-42", "laespiga.mx"); // "pending" | "verified" | "failed"

// 3. Send (pass your own idempotencyKey to make retries across processes safe).
//    `stream`: "transactional" (default for send: receipts, reminders; no unsubscribe link) or
//    "broadcast" (newsletters: one-click List-Unsubscribe; an unsubscribe only silences broadcast).
try {
  await mm.send("formmy-user-42", {
    from: "La Espiga <hola@laespiga.mx>",
    to: "reader@example.com",
    subject: "Pan de muerto ya disponible",
    html: "<p>Desde hoy…</p>",
    tags: { campaign: "oct-26" },
    stream: "broadcast",
    idempotencyKey: "campaign-oct-26:reader@example.com",
  });
} catch (e) {
  if (e instanceof RecipientSuppressedError) { /* e.reason, e.stream, e.recipient: mark the reader, don't retry */ }
  else if (e instanceof PausedError) { /* tenant paused (bounces/complaints): stop until resume */ }
  else if (e instanceof QuotaError) { /* e.limit, e.resetsAt */ }
  else if (e instanceof ValidationError) { /* e.field */ }
  else throw e;
}

// Up to 10,000 recipients in the background (stream defaults to "broadcast"; suppressed ones are
// skipped). Recipients are plain addresses or { to, vars }: {{name}} in subject/html/text is replaced
// per recipient (escaped inside html). Broadcasts get {{unsubscribe_url}} (or a footer if unused).
const job = await mm.batch("formmy-user-42", {
  from: "hola@laespiga.mx", subject: "Hola {{nombre}}", html: "<p>{{nombre}}, tu cupón: {{cupon}}</p>",
  recipients: ["ana@example.com", { to: "bea@example.com", vars: { nombre: "Bea", cupon: "OCT10" } }],
});
const st = await mm.batchStatus("formmy-user-42", job.batchId);
// st.sent / failed / skippedSuppressed, then (for up to 7 days) delivered / bounced / complained / unsubscribed

// Opens and clicks (opt-in per send/batch): `track: true` adds SES's open pixel and rewrites the html
// links. Without it nothing is added. Unique counts + rates + top links come back in batchStatus, and
// `email.opened` / `email.clicked` fire once per message (the first time). Opens are inflated by Apple
// Mail Privacy Protection: decide by clicks.
const tracked = await mm.batch("formmy-user-42", { from: "hola@laespiga.mx", subject: "Oct", html: "<a href=\"https://laespiga.mx/menu?utm_source=mail\">Menú</a>", recipients: ["ana@example.com"], track: true });
const ts = await mm.batchStatus("formmy-user-42", tracked.batchId);
// ts.opened / clicked (unique), ts.openRate / clickRate (0–1 over delivered, or sent while delivered is 0),
// ts.topLinks: [{ url: "https://laespiga.mx/menu", clicks, uniqueClicks }] (utm_*, fbclid, gclid stripped)

// Test mode: validates everything (signature, domain, suppression, quota) without sending or billing
await mm.send("formmy-user-42", { from: "hola@laespiga.mx", to: "x@example.com", subject: "s", text: "t" }, { test: true });

// Preview exactly what one recipient gets — same function the server uses, no network
import { previewMessage } from "@easybits.cloud/mailmask";
const p = previewMessage({ subject: "Hola {{nombre}}", html: "<p>{{nombre}}</p>", stream: "broadcast", vars: { nombre: "Ana" } });
// p.subject, p.html, p.unsubscribeFooter, p.missingVars, p.unusedVars

// Monthly usage (active tenants = at least one send this month)
await mm.usage({ month: "2026-10" });

// 4. Webhook: the whsec_ secret comes back only the FIRST time (later `set` calls return null:
//    changing the URL doesn't rotate it)
const { secret } = await mm.webhook.set({ url: "https://formmy.app/api/mailmask/events" });
await mm.webhook.test(); // sends a `ping`

// Rotate without losing events: during the grace period (default 24 h) every delivery carries both
// signatures; pass both secrets to verifyWebhook until you've deployed the new one.
const { secret: next, previousValidUntil } = await mm.webhook.rotateSecret({ graceMinutes: 60 });

// Pausing: tenants.resume only lifts a pause YOU set (pausedBy "partner"). A reputation pause
// (pausedBy "mailmask" | "ses") throws ReputationHoldError: MailMask lifts it after review.
try { await mm.tenants.resume("formmy-user-42"); } catch (e) { if (!(e instanceof ReputationHoldError)) throw e; }
```

Webhook 处理器——单线(工人,德诺, Bun, Next.js 路由处理器,Hono...): 在生体上签名,请回答 401 / 200,如果您 `onEvent` 抛出( MailMask 重试) :

```ts
import { createWebhookHandler } from "@easybits.cloud/mailmask";
export const POST = createWebhookHandler({
  secret: [process.env.MAILMASK_WEBHOOK_SECRET!, process.env.MAILMASK_WEBHOOK_SECRET_OLD ?? ""],
  onEvent: async (event) => { /* dedupe by event.id */ },
});
```

或者用手(Remix/React Router, 任何带有 `Request`:

```ts
export async function action({ request }: { request: Request }) {
  const rawBody = await request.text(); // the RAW body, before JSON.parse
  const event = await verifyWebhook({ rawBody, headers: request.headers, secret: [process.env.MAILMASK_WEBHOOK_SECRET!, process.env.MAILMASK_WEBHOOK_SECRET_OLD ?? ""] });
  if (!event) return new Response("bad signature", { status: 401 });
  // event.id is the delivery id: use it to drop duplicates
  switch (event.event) {
    case "email.bounced":
    case "contact.unsubscribed":
      // mark the reader as unsubscribed for event.externalId
      break;
    case "tenant.paused":
      // notify the customer
      break;
    case "email.clicked":
      // event.data.url, event.data.recipient, event.data.batchId, event.data.tags (first click only)
      break;
  }
  return new Response("ok");
}
```

错误:全部延伸 `MailMaskError` (单位:千美元)`status`, (中文(简体) ), `code`, (中文(简体) ), `requestId`, (中文(简体) ), `retryable`和 `PartnerApiError` (单位:千美元)`body`, (中文(简体) ), `retryAfterMs` (单位:千美元) `kind` 为平地而歧视的人 `switch`:

| 类 | `status` | `kind` | 被SDK重审 |
| --- | --- | --- | --- |
| `AuthError` | 401 (中文(简体) ) | `auth` | 没有 - `clockSkewMs` + 如果时钟关闭, 消息中提示 |
| `ForbiddenError` | 403 (韩语) | `forbidden` | 没有 |
| `ReputationHoldError` (续) `ForbiddenError`(中文(简体) ) | 403 (韩语) | `reputation_hold` | 没有 |
| `NotFoundError` | 404 (韩语) | `not_found` | 没有 |
| `ConflictError` | 409个 | `conflict` | 没有 |
| `ValidationError` (单位:千美元)`field`(中文(简体) ) | 422个 | `validation` | 没有 |
| `RecipientSuppressedError` (单位:千美元)`reason`, (中文(简体) ), `stream`, (中文(简体) ), `recipient`(中文(简体) ) | 422个 | `suppressed` | 没有 |
| `PausedError` | 423个 | `paused` | 没有 |
| `QuotaError` (单位:千美元)`limit`, (中文(简体) ), `resetsAt`(中文(简体) ) | 429个 `quota_exceeded` | `quota` | 没有 |
| `RateLimitError` (续) `QuotaError`(中文(简体) ) | 429个 `rate_limited` | `rate_limit` | 对 |
| `PartnerApiError` | 5 (简体中文) | `server` | 是(安全请求) |
| `NetworkError` 编号 : `TimeoutError` | 0 个 | `network` 编号 : `timeout` | 是(安全请求) |

`requestId` 来自 `X-Request-Id` 编号 : `Fly-Request-Id`:请求支持时引用.

管理路线(您的 `mk_` 车主钥匙,不是合伙人证书: `MailMaskPartnerAdmin` —— 说 `listPartners`, (中文(简体) ), `listCredentials`, (中文(简体) ), `createCredential`, (中文(简体) ), `rotateCredential`, (中文(简体) ), `revokeCredential`, (中文(简体) ), `getFallbackZone` 和只为 MailMask 的工作人员, `createPartner`, (中文(简体) ), `setReputationPolicy`, (中文(简体) ), `resumeTenant`, (中文(简体) ), `setFallbackZone` (注册合伙人倒扣区等. `marketing.denik.me`时返回 4个NS改为代表;然后 `domains.add(id, "<label>.marketing.denik.me")` 撰写其DNS并核查 此外,《公约》第2条第1款(a)项也规定: 发行和轮换 从未被重审(失去的反应会隐藏一个没人看见的秘密).

## 类型脚本

公共宣传品 **脚本类型 4.9 en adelante**,塔姆比昂孔 `moduleResolution: node` (l clásico,罪过) `exports`) (中文(简体) ). El paquete se compila con TypeScript 7, pero (英语) 建造 " 国家公园 " `export type *`, de TS 5.0) a su (中文(简体) ) 等额(`scripts/postbuild-dts.mjs`:罪以索,非自相残杀 协进会 `TS1383` y desde aquí no se nota. \[ y des ade as as equí\] 互联网档案馆的存檔,存档日期2014-12-02. `npm run check:consumer` 阿尔马·埃尔·帕克特孔 `npm pack` y o compila en un proyecto con TypeScript 4.9.5 para virarlo.

## 文档

完整 API 参考和实例 : [https://mailmask.studio/docs (中文(简体) )](https://mailmask.studio/docs)

## 链接

-   [邮件掩码](https://mailmask.studio/) 电子邮件地址、信箱和转发到您的域名
-   [简单比特](https://easybits.cloud/) ——云台站
-   [固定器](https://fixter.org/) - 发展小组

## 许可证

麻省理工学院

## 常见问题

### @easybits\.cloud/mailmask 是什么？

MailMask 电子邮件别名/转发 API 的官方 SDK

### 如何安装 @easybits\.cloud/mailmask？

在项目目录运行 npm install @easybits\.cloud/mailmask，或使用 pnpm add / yarn add / bun add。

### @easybits\.cloud/mailmask 支持 TypeScript 吗？

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

### @easybits\.cloud/mailmask 支持 ESM 还是 CommonJS？

同时提供 ES Module 与 CommonJS 入口，import 和 require\(\) 都可以使用，打包工具会自动选择合适的版本。

### @easybits\.cloud/mailmask 可以商用吗？

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

### @easybits\.cloud/mailmask 的下载量有多少？

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

### @easybits\.cloud/mailmask 还在维护吗？

最新版本 0\.9\.2 发布于 2026/11/12（2 天前）；至今共发布 31 个版本，其中过去 12 个月发布了 31 个；首次发布于 2026/03/04。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

