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 @@