@flink-app/ inbound-email-plugin (英语)
为 Flink 应用程序输入 SMTP 电子邮件 。 启动 SMTP 服务器和自动发现 EmailHandler 文件在 src/inbound-email-handlers/,将每个发送的邮件传送到第一个匹配的处理器.
安装
pnpm add @flink-app/inbound-email-plugin
设置
1. 注册编译器插件
// flink.config.js
const { compilerPlugin } = require("@flink-app/inbound-email-plugin/compiler");
module.exports = {
compilerPlugins: [compilerPlugin()],
};
2. 添加运行时间插件
// 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. 创建电子邮件处理器
// 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;
路线
EmailRouteProps 字段都是可选的 —— 省略任何匹配所有 :
| 外地 | 类型 | 匹配时 |
|---|---|---|
from | string | RegExp | (email) => boolean | 发送地址( 信头) From:(中文(简体) ) |
to | string | RegExp | 任何收件人地址 |
subject | string | RegExp | 主题行 |
to 与 SMTP 信封接收者匹配(RCPT TO,其中包含BCC,并反映实际交付目标. 标题 To: 对路由不可靠,因为BCC,邮件列表,以及转发收件人不在其内. 用于方案 deliver() 不提供电话 envelopeTo火柴倒回头顶 to。 。 。 .
每一个处理者 Route 以发现顺序引用匹配 .
SPF/DKIM核查
过 verify: true 既允许默认检查,也允许精确控制的对象:
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()
},
})
何时 rejectOnFail 假的(默认的),结果将传递给每个处理器,作为 auth 和您的代码决定怎么做:
const handler: EmailHandler<Ctx> = async ({ email, auth }) => {
if (!auth?.passed) {
console.warn("Unauthenticated email from", email.from);
return;
}
// ...
};
auth.passed 实值 true 仅在每次启用的检查返回时 "pass". 各项结果可在 auth.spf.result 财务报告和审定财务报表 auth.dkim.result (有签名细节) auth.dkim.signatures) (中文(简体) ).
核查工作由 邮件 并懒惰地装入,所以启动时间不会在 verify 未配置.
信件标识
InboundEmail 曝光两个消息ID:
messageId—— 说 总是在场. 镜像发送者的 SMTPMessage-ID在提供时,否则即为中继生成的UUID. 用这个做你的独能钥匙.smtpMessageId—— 说 选项. 最初的SMTPMessage-ID头(取出角括号). 用于In-Reply-To编号 :References关联,发件人-侧去dupe,或交叉引用发件人的日志. 发件人控制——不要作为信任边界.
页眉
email.headers 是一个 Record<string, string[]> 以小写头名键入。 值为 RFC822 逐字记录后文本 <key>: 前缀( 已修剪的白空间) 。 A 类 string[] 被使用,因为真实信件中包含重复头- Received: 例如,每一次中继跳出一次,除一人外,丢掉所有信息就会丢失.
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=...`
插件选项
| 选项 | 类型 | 默认 | 说明 |
|---|---|---|---|
port | number | 2525 | SMTP 监听端口 |
allowedDomains | string[] | —— 说 | 拒绝邮件到其他域 |
maxMessageSize | number | 10MB | 以字节表示的最大消息大小 |
tls | { key, cert } | —— 说 | TLS 的 PEM 文件路径 |
verify | boolean | VerifyOptions | —— 说 | SPF/DKIM核查(见上文) |
resolveUser | (email, ctx) => Promise<any> | —— 说 | 查找发送用户 |
resolvePermissions | (user, ctx) => Promise<string[]> | —— 说 | 为用户装入权限 |
onUnhandled | (email, ctx) => Promise<void> | —— 说 | 没有处理器匹配时调用 |
请求背景
何时 resolveUser 财务报告和审定财务报表 resolvePermissions 配置,用户和权限可以通过标准的Flink帮助在内部处理器中获取:
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();
};
这意味着从处理器内部调用的Flink工具和代理也自动接收正确的用户上下文.
CLI——试验交付
送来生的 .eml 文件到正在运行的本地 SMTP 服务器而不需要外部工具 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 -
该工具会说真正的 SMTP —— 页眉被解析 From编号 :To 信封地址自动, 并且 SPF/ DKIM 检查 运行在原始字节上 完全如实 从真正的中继.
方案交付
插件曝光一个 deliver 用于从应用代码中发送电子邮件的上下文方法 – 用于从webhooks,队列消费者或其他插件中转发。 它运行的管道与真正的SMTP送出(用户分辨率,路由匹配,requestContext)相同,但跳过SPF/DKIM验证,因为没有原始的SMTP信封.
1. 增加上下文类型
// 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. 有ctx接入的任何地方的呼叫发送
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");
}
返回值 { handled: boolean } 表示是否至少一个 EmailHandler 匹配电子邮件。 如果没有处理器匹配和 onUnhandled 在插件上被配置,它将被自动调用——与SMTP发送的电子邮件相同.
自定义扫描目录
// flink.config.js
compilerPlugins: [compilerPlugin({ scanDir: "src/mail-handlers" })]