diff --git a/bun.lock b/bun.lock
index 9538c6e..340c868 100644
--- a/bun.lock
+++ b/bun.lock
@@ -24,6 +24,7 @@
"vike-photon": "^0.1.24",
"vike-vue": "^0.9.11",
"vue": "^3.5.30",
+ "worker-mailer": "^1.2.1",
},
"devDependencies": {
"@tailwindcss/vite": "^4.2.1",
@@ -1093,6 +1094,8 @@
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
+ "worker-mailer": ["worker-mailer@1.2.1", "", {}, "sha512-gS2ei/mrpRqNs+AHmqxhT6vFPwCLw2qnz5ShmyGD0ULaU0Q9hxnFAcx9jhAip/MnD6+MjgnQu6hQQgA8mlOkVA=="],
+
"workerd": ["workerd@1.20260401.1", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260401.1", "@cloudflare/workerd-darwin-arm64": "1.20260401.1", "@cloudflare/workerd-linux-64": "1.20260401.1", "@cloudflare/workerd-linux-arm64": "1.20260401.1", "@cloudflare/workerd-windows-64": "1.20260401.1" }, "bin": { "workerd": "bin/workerd" } }, "sha512-mUYCd+ohaWJWF5nhDzxugWaAD/DM8Dw0ze3B7bu8JaA7S70+XQJXcvcvwE8C4qGcxSdCyqjsrFzqxKubECDwzg=="],
"wrangler": ["wrangler@4.80.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.2", "@cloudflare/unenv-preset": "2.16.0", "blake3-wasm": "2.1.5", "esbuild": "0.27.3", "miniflare": "4.20260401.0", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20260401.1" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20260401.1" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-2ZKF7uPeOZy65BGk3YfvqBCPo/xH1MrAlMmH9mVP+tCNBrTUMnwOHSj1HrZHgR8LttkAqhko0fGz+I4ax1rzyQ=="],
diff --git a/modules/email/provider.ts b/modules/email/provider.ts
index d63e236..ec7ff6f 100644
--- a/modules/email/provider.ts
+++ b/modules/email/provider.ts
@@ -1,5 +1,5 @@
import { badRequestError, externalServiceError } from "../../lib/app-error";
-import type { EmailApiConfigValue, EmailProviderAdapter, EmailSendInput } from "./types";
+import type { EmailApiConfigValue, EmailProviderAdapter, EmailSendInput, EmailSmtpConfigValue } from "./types";
function normalizeBaseUrl(value: string) {
return value.replace(/\/+$/, "");
@@ -102,6 +102,39 @@ async function sendMailjetEmail(config: EmailApiConfigValue, input: EmailSendInp
};
}
+export function createSmtpEmailAdapter(config: EmailSmtpConfigValue): EmailProviderAdapter {
+ return {
+ async send(input) {
+ if (!config.smtpHost || !config.smtpPort) {
+ throw badRequestError("SMTP 配置不完整", "SMTP_CONFIG_INCOMPLETE");
+ }
+
+ const { WorkerMailer } = await import("worker-mailer");
+ await WorkerMailer.send(
+ {
+ host: config.smtpHost,
+ port: config.smtpPort,
+ secure: config.smtpSecure ?? false,
+ credentials: config.smtpUsername
+ ? { username: config.smtpUsername, password: config.smtpPassword ?? "" }
+ : undefined,
+ authType: config.smtpAuthType ?? "plain",
+ },
+ {
+ from: { email: config.fromEmail, name: config.fromName },
+ to: input.toEmail,
+ reply: input.replyTo || config.replyTo || undefined,
+ subject: input.subject,
+ text: input.text,
+ html: input.html,
+ },
+ );
+
+ return {};
+ },
+ };
+}
+
export function createApiEmailAdapter(config: EmailApiConfigValue): EmailProviderAdapter {
return {
async send(input) {
diff --git a/modules/email/service.ts b/modules/email/service.ts
index ad862e3..341993f 100644
--- a/modules/email/service.ts
+++ b/modules/email/service.ts
@@ -5,7 +5,7 @@ import { logger } from "../../lib/logger";
import { validateEmailConfigInput, validateEmailTemplateInput, validateTestEmailInput } from "../../lib/validators/email";
import { getAdminContext, logAdminOperation } from "../auth/service";
import { getSiteSetting } from "../site/service";
-import { createApiEmailAdapter } from "./provider";
+import { createApiEmailAdapter, createSmtpEmailAdapter } from "./provider";
import {
activateEmailConfigById,
createEmailConfigRecord,
@@ -270,7 +270,19 @@ async function sendByChannel(config: EmailConfigValue, input: {
}
if (config.provider === "SMTP") {
- throw badRequestError("当前版本暂未在 Worker 中实现 SMTP 直连发送,请先使用 API 分类", "SMTP_NOT_IMPLEMENTED");
+ const adapter = createSmtpEmailAdapter(config);
+ const result = await adapter.send({
+ toEmail: input.toEmail,
+ subject: input.subject,
+ text: input.text,
+ html: input.html,
+ replyTo: config.replyTo,
+ });
+ return {
+ provider: config.provider,
+ apiProvider: undefined,
+ messageId: result.messageId,
+ };
}
throw badRequestError("当前版本暂未接入 CloudFlare Email Send binding 的运行时发送,请先使用 API 分类", "CLOUDFLARE_NOT_IMPLEMENTED");
diff --git a/modules/email/types.ts b/modules/email/types.ts
index 0597e95..0252c3d 100644
--- a/modules/email/types.ts
+++ b/modules/email/types.ts
@@ -41,6 +41,7 @@ export interface EmailSmtpConfigValue extends EmailPushFlags {
smtpSecure?: boolean;
smtpUsername?: string;
smtpPassword?: string;
+ smtpAuthType?: "plain" | "login" | "cram-md5";
}
export interface EmailCloudflareConfigValue extends EmailPushFlags {
diff --git a/package.json b/package.json
index 249ba5f..a45599e 100644
--- a/package.json
+++ b/package.json
@@ -35,7 +35,8 @@
"vike": "^0.4.255",
"vike-photon": "^0.1.24",
"vike-vue": "^0.9.11",
- "vue": "^3.5.30"
+ "vue": "^3.5.30",
+ "worker-mailer": "^1.2.1"
},
"devDependencies": {
"@tailwindcss/vite": "^4.2.1",
diff --git a/pages/admin/email/+Page.vue b/pages/admin/email/+Page.vue
index 8fb0018..44b8630 100644
--- a/pages/admin/email/+Page.vue
+++ b/pages/admin/email/+Page.vue
@@ -254,6 +254,13 @@