@flink-app/inbound-email-plugin

中文文档
2.0.0-alpha.134发布于 2026/11/07MITTypeScriptCommonJS近 30 天下载 2178

Flink plugin for SMTP inbound email with auto-discovered EmailHandler files

原始使用文档
文档目录16 节
  1. Install
  2. Setup
  3. 1. Register the compiler plugin
  4. 2. Add the runtime plugin
  5. 3. Create an email handler
  6. Routing
  7. SPF/DKIM verification
  8. Message IDs
  9. Headers
  10. Plugin options
  11. Request context
  12. CLI — test delivery
  13. Programmatic delivery
  14. 1. Add the context type
  15. 2. Call deliver from anywhere with ctx access
  16. Custom scan directory

@flink-app/inbound-email-plugin

Inbound SMTP email for Flink apps. Starts an SMTP server and auto-discovers EmailHandler files in src/inbound-email-handlers/, routing each incoming email to the first matching handler.

Install

pnpm add @flink-app/inbound-email-plugin

Setup

1. Register the compiler plugin

// flink.config.js
const { compilerPlugin } = require("@flink-app/inbound-email-plugin/compiler");

module.exports = {
  compilerPlugins: [compilerPlugin()],
};

2. Add the runtime plugin

// src/index.ts
import { inboundEmailPlugin } from "@flink-app/inbound-email-plugin";

new FlinkApp<Ctx>({
  // ...
  plugins: [
    inboundEmailPlugin({
      port: 2525,
      allowedDomains: ["myapp.com"],
      resolveUser: async (email, ctx) =>
        ctx.repos.userRepo.getOne({ email: email.from }),
      resolvePermissions: async (user, ctx) =>
        ctx.repos.permRepo.getForUser(user.id),
      onUnhandled: async (email) =>
        console.warn("No handler matched", email.from),
    }),
  ],
});

3. Create an email handler

// src/inbound-email-handlers/HandleSupportEmail.ts
import { EmailHandler, EmailRouteProps } from "@flink-app/inbound-email-plugin";
import { Ctx } from "../Ctx";

export const Route: EmailRouteProps = {
  from: /.*@customers\.com/,
};

const handler: EmailHandler<Ctx> = async ({ ctx, email, user }) => {
  await ctx.repos.ticketRepo.create({
    subject: email.subject,
    body: email.text,
    from: email.from,
    userId: user?.id,
  });
};

export default handler;

Routing

EmailRouteProps fields are all optional — omit any to match everything:

FieldTypeMatches when
fromstring | RegExp | (email) => booleansender address (header From:)
tostring | RegExpany recipient address
subjectstring | RegExpsubject line

to matches against the SMTP envelope recipients (RCPT TO), which include BCC and reflect the actual delivery target. The header To: is unreliable for routing because BCC, mailing-list, and forwarder recipients are not present in it. For programmatic deliver() calls that don't supply envelopeTo, the matcher falls back to the header to.

Every handler whose Route matches is invoked, in discovery order.

SPF/DKIM verification

Pass verify: true to enable both checks with defaults, or an object for fine-grained control:

inboundEmailPlugin({
  verify: {
    spf: true,           // default: true
    dkim: true,          // default: true
    rejectOnFail: true,  // bounce with SMTP 550 on failure — default: false
    mta: "mx.myapp.com", // your MTA hostname for SPF — default: os.hostname()
  },
})

When rejectOnFail is false (the default), the result is passed to every handler as auth and your code decides what to do:

const handler: EmailHandler<Ctx> = async ({ email, auth }) => {
  if (!auth?.passed) {
    console.warn("Unauthenticated email from", email.from);
    return;
  }
  // ...
};

auth.passed is true only when every enabled check returned "pass". The individual results are available on auth.spf.result and auth.dkim.result (with per-signature detail on auth.dkim.signatures).

Verification is powered by mailauth and loaded lazily so startup time is not affected when verify is not configured.

Message IDs

InboundEmail exposes two message IDs:

  • messageId — always present. Mirrors the sender's SMTP Message-ID when supplied, otherwise a relay-generated UUID. Use this as your idempotency key.
  • smtpMessageId — optional. The original SMTP Message-ID header (angle brackets stripped). Useful for In-Reply-To / References correlation, sender-side dedupe, or cross-referencing the sender's logs. Sender-controlled — do not use as a trust boundary.

Headers

email.headers is a Record<string, string[]> keyed by lower-cased header name. Values are the verbatim RFC822 text after the <key>: prefix (whitespace trimmed). A string[] is used because real messages contain repeated headers — Received: appears once per relay hop, for example — and dropping all but one would lose tracing information.

email.headers["from"];        // ['"Alice" <[email protected]>']
email.headers["received"];    // ['from mx1...', 'from mx2...', ...]
email.headers["dkim-signature"]; // raw `v=1; a=rsa-sha256; d=...; b=...`

Plugin options

OptionTypeDefaultDescription
portnumber2525SMTP listening port
allowedDomainsstring[]—Reject mail to other domains
maxMessageSizenumber10MBMax message size in bytes
tls{ key, cert }—Paths to PEM files for TLS
verifyboolean | VerifyOptions—SPF/DKIM verification (see above)
resolveUser(email, ctx) => Promise<any>—Look up the sending user
resolvePermissions(user, ctx) => Promise<string[]>—Load permissions for the user
onUnhandled(email, ctx) => Promise<void>—Called when no handler matched

Request context

When resolveUser and resolvePermissions are configured, the user and permissions are available inside handlers via the standard Flink helpers:

import { getRequestUser, getRequestPermissions } from "@flink-app/flink";

const handler: EmailHandler<Ctx> = async ({ ctx, email }) => {
  const user = getRequestUser(); // same as the `user` arg
  const perms = getRequestPermissions();
};

This means Flink tools and agents called from within a handler also receive the correct user context automatically.

CLI — test delivery

Deliver a raw .eml file to a running local SMTP server without needing an external tool like swaks:

# Built-in samples — quickest way to verify your handler is working
npx flink-email deliver --sample plain
npx flink-email deliver --sample attachment

# Your own .eml file
npx flink-email deliver ./my-email.eml

# Override port or envelope addresses
npx flink-email deliver --sample plain --port 2525 --from [email protected] --to [email protected]

# Pipe from stdin (e.g. paste from Gmail "Show original")
pbpaste | npx flink-email deliver -

The tool speaks real SMTP — headers are parsed for From/To envelope addresses automatically, and SPF/DKIM checks run on the raw bytes exactly as they would from a real relay.

Programmatic delivery

The plugin exposes a deliver method on its context for dispatching emails from app code — useful for forwarding from webhooks, queue consumers, or other plugins. It runs the same pipeline as a real SMTP delivery (user resolution, route matching, requestContext), but skips SPF/DKIM verification since there is no raw SMTP envelope.

1. Add the context type

// src/Ctx.ts
import { FlinkContext } from "@flink-app/flink";
import { InboundEmailPluginContext } from "@flink-app/inbound-email-plugin";

export interface Ctx extends FlinkContext<InboundEmailPluginContext> {
  repos: { /* ... */ };
}

2. Call deliver from anywhere with ctx access

import { InboundEmail } from "@flink-app/inbound-email-plugin";

const email: InboundEmail = {
  from: "[email protected]",
  to: ["[email protected]"],
  // envelopeFrom / envelopeTo are optional — supply them to mirror the
  // SMTP envelope (incl. BCC) so route `to` matchers behave the same as
  // for real SMTP deliveries.
  subject: "Forwarded from webhook",
  messageId: "abc-123",
  text: "Hello from a webhook",
  attachments: [],
  headers: {},
  receivedAt: new Date(),
};

const { handled } = await ctx.plugins.inboundEmailPlugin.deliver(email);

if (!handled) {
  console.warn("No handler matched the programmatic email");
}

The return value { handled: boolean } indicates whether at least one EmailHandler matched the email. If no handler matched and onUnhandled is configured on the plugin, it will be called automatically — same as for SMTP-delivered emails.

Custom scan directory

// flink.config.js
compilerPlugins: [compilerPlugin({ scanDir: "src/mail-handlers" })]

版本与发布记录

@flink-app/inbound-email-plugin 共发布 76 个版本,首次发布于 2026/03/01;过去 12 个月发布了 76 个版本。

版本发布日期距今
2.0.0-alpha.134latest2026/11/078 天前
2.0.0-alpha.1332026/11/069 天前
2.0.0-alpha.1322026/11/0510 天前
2.0.0-alpha.1312026/10/2818 天前
2.0.0-alpha.1302026/10/2521 天前
2.0.0-alpha.1292026/10/2521 天前

关于 @flink-app/inbound-email-plugin 的常见问题

@flink-app/inbound-email-plugin 是什么?

Flink plugin for SMTP inbound email with auto-discovered EmailHandler files

如何安装 @flink-app/inbound-email-plugin?

在项目目录运行 npm install @flink-app/inbound-email-plugin,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:@flink-app/flink(>=2.0.0-alpha.134)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。它还提供命令行工具 flink-email,可以用 npx flink-email 直接运行。

@flink-app/inbound-email-plugin 支持 TypeScript 吗?

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

@flink-app/inbound-email-plugin 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@flink-app/inbound-email-plugin 可以商用吗?

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

@flink-app/inbound-email-plugin 的下载量有多少?

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

@flink-app/inbound-email-plugin 还在维护吗?

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