@flowmails/flowmails-sdk

中文文档
0.2.1发布于 2026/11/01MITTypeScriptESM + CJS零依赖

Official JavaScript / TypeScript SDK for the Flowmails email API (https://sdk.flowmails.net): send, reply in thread, wait for inbound mail, read messages, and create addresses.

原始使用文档
文档目录12 节
  1. Install
  2. Quick start
  3. A send the relay refused
  4. Is my domain able to send?
  5. About Flowmails
  6. API keys
  7. Errors
  8. Idempotent sends
  9. Send quota
  10. The read half
  11. Limitations (v0.2)
  12. License

@flowmails/flowmails-sdk

The official JavaScript / TypeScript SDK for the Flowmails SDK backend (https://sdk.flowmails.net).

Status: public preview (v0.2.x) — published from the Flowmails monorepo. Wire format and surface can still change in v0.x; pin the minor version (~0.2) in production.

Install

pnpm add @flowmails/flowmails-sdk

Quick start

import { Flowmails } from "@flowmails/flowmails-sdk";

const fm = new Flowmails({
  apiKey: process.env.FLOWMAILS_API_KEY!,
  // baseURL: "https://sdk.flowmails.net",  // default
});

const result = await fm.send({
  from: "[email protected]",
  to: "[email protected]",
  subject: "Order #1234 confirmed",
  text: "Thanks for your order — we'll ship it tomorrow.",
  // html: "<p>Thanks for your order…</p>",
});

console.log(result); // { id: "msg_8421", status: "queued" }

A send the relay refused

send() answers 201 for a message the relay refused — the row exists, the mail does not. The result carries the reason, so a failure is never silent:

const result = await fm.send({ … });
if (result.status === "failed") {
  console.error(result.error);        // "send_failed" — always present
  console.error(result.sendFeedback);  // the relay's own reason, when it gave one
}

result.error is never null on a failed send: "failed with no reason" is not a state the wire can be in.

Is my domain able to send?

const me = await fm.me();
if (me.sending && !me.sending.canSend) {
  console.warn(me.sending.state); // "not_onboarded" | "unconfigured" | "unknown" | …
}

me.sending is the platform's own verdict, read from the same document the dashboard reads — null when it cannot be known (no bound domain, or the platform could not be read). Read it together with state: canSend: false with state: "unknown" means "we have not looked", not "this domain cannot send".

About Flowmails

Flowmails is a Cloudflare-native email platform: custom-domain mail (inbox, outbox, and routing rules) that lives in your own D1, with a REST API and webhooks on top. The dashboard, docs, and blog live at https://flowmails.net — sign up there to mint an API key and bind a domain before using this SDK.

API keys

API keys are minted in the Flowmails dashboard at Settings → API Keys. Each key is pinned to one of your registered domains — the SDK backend rejects send(...) calls where the from address doesn't land on the key-bound domain.

Errors

The SDK throws typed errors:

ClassHTTP statusRetryable
AuthenticationError401no
ValidationError400 / 404 / 409 / 422no
RateLimitError429yes (client backs off)
UpstreamError5xx / 408yes
FlowmailsErrorotherno

Use instanceof to discriminate:

import { RateLimitError, FlowmailsError } from "@flowmails/flowmails-sdk";

try {
  await fm.send({ ... });
} catch (err) {
  if (err instanceof RateLimitError) {
    // backoff and retry
  } else if (err instanceof FlowmailsError) {
    // surface `err.code` to your logs
  } else {
    throw err;
  }
}

Idempotent sends

Give a send an idempotencyKey and a retry becomes free: the same key under the same account and the same request returns the original result and does not send a second message.

await fm.send({ from, to, subject, text, idempotencyKey: "order-42" });

The key is scoped to the account, not globally. Reusing one key for a different payload is a client bug and is refused with idempotency_key_conflict (a ValidationError) rather than answered with the first result.

Send quota

Sends are capped per account and per domain, per hour and per day. A refusal is a RateLimitError carrying the facts you need to back off:

catch (err) {
  if (err instanceof RateLimitError && err.code === "rate_limit_exceeded") {
    console.log(err.remaining, err.limit, err.resetAt); // ISO-8601
  }
}

The same numbers travel in the X-Flowmails-RateLimit-Remaining and X-Flowmails-RateLimit-Reset response headers. The SDK does not retry a quota refusal for you — the window is an hour, so a retry inside it cannot succeed.

The read half

Sending is one half of the SDK; the other is reading — and it talks to a different origin (the platform worker, not the SDK backend), so a self-hosted install sets platformURL too.

// Check the credential without sending anything.
const me = await fm.me(); // { userId, domain, scopes[], sending }

// Tail the inbound log with a cursor (an event id, never a timestamp).
const page = await fm.events.list({ after: lastEventId });

// Search the mailbox history — addressing, subject and a preview.
const hits = await fm.messages.search(domainId, { q: "invoice" });

// Read the conversation a message belongs to.
const thread = await fm.threads.get(threadId);

// Wait for a message without holding a request open.
const wait = await fm.waits.create({ address: "[email protected]" });
const status = await fm.waits.get(wait.waitId); // pending → resolved | expired

// Create a mailbox — one local part, idempotent.
await fm.addresses.create(domainId, "billing");

The body is fetched, never pushed: an event carries the routing facts and the subject line, and fm.messages.get(messageId) is the ask. The SDK never hands you a message you did not ask for.

Limitations (v0.2)

  • Custom domain (HTTP/HTTPS) attachment hosting is not configurable by SDK callers; the SDK backend always sends via R2.
  • The webhook delivery-status events (message.delivered / message.bounced) do not exist yet — and neither does bounce detection. The receive worker has no DSN ingestion, so a send's status is sent (the relay accepted it) or failed (the relay rejected it) and nothing more. The vocabulary holds delivered / bounced / complained / delayed, but nothing writes them.

License

MIT © 2026 Flowmails. See LICENSE.

版本与发布记录

@flowmails/flowmails-sdk 共发布 4 个版本,首次发布于 2026/07/10;过去 12 个月发布了 4 个版本。

版本发布日期距今
0.2.1latest2026/11/012 天前
0.2.02026/10/313 天前
0.1.12026/07/153 个月前
0.1.02026/07/103 个月前

关于 @flowmails/flowmails-sdk 的常见问题

@flowmails/flowmails-sdk 是什么?

flowmail邮件API的官方JavaScript / TypeScript SDK (https://sdk.flowmails.net):发送、线程回复、等待入站邮件、读取消息、创建地址。

如何安装 @flowmails/flowmails-sdk?

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

@flowmails/flowmails-sdk 支持 TypeScript 吗?

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

@flowmails/flowmails-sdk 支持 ESM 还是 CommonJS?

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

@flowmails/flowmails-sdk 需要什么版本的 Node.js?

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

@flowmails/flowmails-sdk 可以商用吗?

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

@flowmails/flowmails-sdk 还在维护吗?

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