diff --git a/apps/api/.dev.vars.example b/apps/api/.dev.vars.example index ab81ea88..d5b3e20a 100644 --- a/apps/api/.dev.vars.example +++ b/apps/api/.dev.vars.example @@ -58,8 +58,3 @@ TWILIO_ACCOUNT_SID=CHANGE_ME TWILIO_AUTH_TOKEN=CHANGE_ME TWILIO_PHONE_NUMBER=CHANGE_ME -AWS_ACCESS_KEY_ID=CHANGE_ME -AWS_SECRET_ACCESS_KEY=CHANGE_ME -AWS_REGION=CHANGE_ME - -SES_DEFAULT_FROM=CHANGE_ME diff --git a/apps/api/package.json b/apps/api/package.json index 5eb9f826..be51ddca 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -64,10 +64,11 @@ "drizzle-orm": "0.44.5", "exifreader": "^4.36.0", "geotiff": "^2.1.3", - "hono": "^4.12.2", + "hono": "^4.12.7", "jose": "^6.1.3", "jsonpath-plus": "^10.3.0", - "mailparser": "^3.9.1", + "mailparser": "^3.9.3", + "mimetext": "^3.0.28", "openai": "^6.16.0", "stripe": "^20.1.2", "three": "^0.182.0", diff --git a/apps/api/src/context.ts b/apps/api/src/context.ts index b9d3da72..219ad225 100644 --- a/apps/api/src/context.ts +++ b/apps/api/src/context.ts @@ -24,6 +24,7 @@ export interface Bindings { AI: Ai; AI_OPTIONS: AiOptions; LOADER?: any; // worker_loaders binding for Code Mode sandbox + SEND_EMAIL?: SendEmail; BROWSER?: Fetcher; EXECUTIONS: AnalyticsEngineDataset; WEB_HOST: string; @@ -53,10 +54,6 @@ export interface Bindings { TWILIO_ACCOUNT_SID?: string; TWILIO_AUTH_TOKEN?: string; TWILIO_PHONE_NUMBER?: string; - AWS_ACCESS_KEY_ID?: string; - AWS_SECRET_ACCESS_KEY?: string; - AWS_REGION?: string; - SES_DEFAULT_FROM?: string; HUGGINGFACE_API_KEY?: string; REPLICATE_API_TOKEN?: string; R2_ACCESS_KEY_ID?: string; diff --git a/apps/api/src/runtime/cloudflare-node-registry.ts b/apps/api/src/runtime/cloudflare-node-registry.ts index 523cb2ae..0eb9d1fb 100644 --- a/apps/api/src/runtime/cloudflare-node-registry.ts +++ b/apps/api/src/runtime/cloudflare-node-registry.ts @@ -453,12 +453,7 @@ export class CloudflareNodeRegistry extends BaseNodeRegistry { this.env.TWILIO_AUTH_TOKEN && this.env.TWILIO_PHONE_NUMBER ); - const hasSESEmail = !!( - this.env.AWS_ACCESS_KEY_ID && - this.env.AWS_SECRET_ACCESS_KEY && - this.env.AWS_REGION && - this.env.SES_DEFAULT_FROM - ); + const hasSendEmail = !!this.env.SEND_EMAIL; const hasGoogleMail = !!( this.env.INTEGRATION_GOOGLE_MAIL_CLIENT_ID && this.env.INTEGRATION_GOOGLE_MAIL_CLIENT_SECRET @@ -717,7 +712,7 @@ export class CloudflareNodeRegistry extends BaseNodeRegistry { this.registerImplementation(TwilioSmsNode); } - if (hasSESEmail) { + if (hasSendEmail) { this.registerImplementation(SendEmailNode); } diff --git a/apps/api/src/services/email-service.ts b/apps/api/src/services/email-service.ts index c51a8baa..c5971fe5 100644 --- a/apps/api/src/services/email-service.ts +++ b/apps/api/src/services/email-service.ts @@ -1,4 +1,5 @@ -import { AwsClient } from "aws4fetch"; +import { EmailMessage } from "cloudflare:email"; +import { createMimeMessage } from "mimetext"; import type { Bindings } from "../context"; @@ -7,58 +8,40 @@ export interface EmailOptions { subject: string; html?: string; text?: string; + cc?: string | string[]; replyTo?: string | string[]; } export interface EmailResult { success: boolean; - messageId?: string; error?: string; } /** * Application-level email service for sending transactional emails - * Uses Amazon SES with the configured noreply address + * Uses Cloudflare Email Routing with the configured noreply address */ export class EmailService { - private client: AwsClient; + private sendEmail: SendEmail; private fromAddress: string; - private region: string; constructor(env: Bindings) { - if ( - !env.AWS_ACCESS_KEY_ID || - !env.AWS_SECRET_ACCESS_KEY || - !env.AWS_REGION - ) { - throw new Error( - "AWS credentials (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION) are not configured" - ); - } - - if (!env.SES_DEFAULT_FROM) { - throw new Error("SES_DEFAULT_FROM is not configured"); + if (!env.SEND_EMAIL) { + throw new Error("SEND_EMAIL binding is not configured"); } - this.client = new AwsClient({ - accessKeyId: env.AWS_ACCESS_KEY_ID, - secretAccessKey: env.AWS_SECRET_ACCESS_KEY, - }); - this.region = env.AWS_REGION; - this.fromAddress = env.SES_DEFAULT_FROM; + this.sendEmail = env.SEND_EMAIL; + this.fromAddress = `noreply@${env.EMAIL_DOMAIN}`; } /** - * Send an email using Amazon SES + * Send an email using Cloudflare Email Routing */ async send(options: EmailOptions): Promise { - const { to, subject, html, text, replyTo } = options; + const { to, subject, html, text, cc, replyTo } = options; if (!to || !subject) { - return { - success: false, - error: "'to' and 'subject' are required", - }; + return { success: false, error: "'to' and 'subject' are required" }; } if (!html && !text) { @@ -70,72 +53,46 @@ export class EmailService { try { const toArray = typeof to === "string" ? [to] : to; - const replyToArray = replyTo - ? typeof replyTo === "string" - ? [replyTo] - : replyTo - : []; - - const params = new URLSearchParams(); - params.set("Action", "SendEmail"); - params.set("Source", this.fromAddress); - - for (let i = 0; i < toArray.length; i++) { - params.set(`Destination.ToAddresses.member.${i + 1}`, toArray[i]); - } - for (let i = 0; i < replyToArray.length; i++) { - params.set(`ReplyToAddresses.member.${i + 1}`, replyToArray[i]); + + // Build shared MIME parts once — only the recipient varies per send + const baseMsg = createMimeMessage(); + baseMsg.setSender({ addr: this.fromAddress, name: "Dafthunk" }); + baseMsg.setSubject(subject); + + if (cc) { + const ccArray = typeof cc === "string" ? [cc] : cc; + for (const addr of ccArray) { + baseMsg.setCc(addr); + } } - params.set("Message.Subject.Data", subject); - params.set("Message.Subject.Charset", "UTF-8"); + if (replyTo) { + const replyToArray = typeof replyTo === "string" ? [replyTo] : replyTo; + baseMsg.setHeader("Reply-To", replyToArray.join(", ")); + } if (html) { - params.set("Message.Body.Html.Data", html); - params.set("Message.Body.Html.Charset", "UTF-8"); + baseMsg.addMessage({ contentType: "text/html", data: html }); } if (text) { - params.set("Message.Body.Text.Data", text); - params.set("Message.Body.Text.Charset", "UTF-8"); + baseMsg.addMessage({ contentType: "text/plain", data: text }); } - const response = await this.client.fetch( - `https://email.${this.region}.amazonaws.com/`, - { - method: "POST", - headers: { "Content-Type": "application/x-www-form-urlencoded" }, - body: params.toString(), - } - ); - - const responseText = await response.text(); - - if (!response.ok) { - const errorMatch = responseText.match(/(.*?)<\/Message>/); - const errorMessage = errorMatch?.[1] ?? responseText; - console.error("Email Service SES Error:", errorMessage); - return { - success: false, - error: `AWS SES Error: ${errorMessage}`, - }; - } - - const messageIdMatch = responseText.match( - /(.*?)<\/MessageId>/ + await Promise.all( + toArray.map((recipient) => { + baseMsg.setRecipient(recipient); + const message = new EmailMessage( + this.fromAddress, + recipient, + baseMsg.asRaw() + ); + return this.sendEmail.send(message); + }) ); - if (messageIdMatch?.[1]) { - return { - success: true, - messageId: messageIdMatch[1], - }; - } - return { - success: false, - error: "Failed to send email via Amazon SES. No MessageId returned.", - }; + return { success: true }; } catch (error) { - console.error("Email Service SES Error:", error); + console.error("Email Service Error:", error); return { success: false, error: error instanceof Error ? error.message : String(error), diff --git a/apps/api/wrangler.jsonc b/apps/api/wrangler.jsonc index f5f40a86..72b38d4f 100644 --- a/apps/api/wrangler.jsonc +++ b/apps/api/wrangler.jsonc @@ -32,6 +32,11 @@ "ai": { "binding": "AI" }, + "send_email": [ + { + "name": "SEND_EMAIL" + } + ], "containers": [ { "class_name": "FFmpegContainer", @@ -233,6 +238,11 @@ "ai": { "binding": "AI" }, + "send_email": [ + { + "name": "SEND_EMAIL" + } + ], "containers": [ { "class_name": "FFmpegContainer", diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 87d7a885..441feaf3 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -33,7 +33,8 @@ "geotiff": "^2.1.3", "iconv-lite": "^0.7.2", "jsonpath-plus": "^10.3.0", - "mailparser": "^3.9.1", + "mailparser": "^3.9.3", + "mimetext": "^3.0.28", "openai": "^6.16.0", "three": "^0.182.0", "three-bvh-csg": "^0.0.17", diff --git a/packages/runtime/src/node-types.ts b/packages/runtime/src/node-types.ts index 62183015..2ad51f76 100644 --- a/packages/runtime/src/node-types.ts +++ b/packages/runtime/src/node-types.ts @@ -209,10 +209,7 @@ export interface NodeEnv { TWILIO_ACCOUNT_SID?: string; TWILIO_AUTH_TOKEN?: string; TWILIO_PHONE_NUMBER?: string; - AWS_ACCESS_KEY_ID?: string; - AWS_SECRET_ACCESS_KEY?: string; - AWS_REGION?: string; - SES_DEFAULT_FROM?: string; + SEND_EMAIL?: SendEmail; HUGGINGFACE_API_KEY?: string; REPLICATE_API_TOKEN?: string; } diff --git a/packages/runtime/src/nodes/email/send-email-node.ts b/packages/runtime/src/nodes/email/send-email-node.ts index d9ce8beb..75d4e06e 100644 --- a/packages/runtime/src/nodes/email/send-email-node.ts +++ b/packages/runtime/src/nodes/email/send-email-node.ts @@ -1,17 +1,18 @@ import { ExecutableNode, type NodeContext } from "@dafthunk/runtime"; import type { NodeExecution, NodeType } from "@dafthunk/types"; -import { AwsClient } from "aws4fetch"; +import { EmailMessage } from "cloudflare:email"; +import { createMimeMessage } from "mimetext"; export class SendEmailNode extends ExecutableNode { public static readonly nodeType: NodeType = { id: "send-email", name: "Send Email", type: "send-email", - description: "Send an email using Amazon SES", + description: "Send an email using Cloudflare Email Routing", tags: ["Social", "Email", "Send"], icon: "mail", documentation: - "This node sends emails using Amazon SES (Simple Email Service), supporting both HTML and plain text content with advanced features.", + "This node sends emails using Cloudflare Email Routing, supporting both HTML and plain text content.", usage: 10, inputs: [ { @@ -53,9 +54,9 @@ export class SendEmailNode extends ExecutableNode { ], outputs: [ { - name: "messageId", - type: "string", - description: "SES message ID", + name: "success", + type: "boolean", + description: "Whether the email was sent successfully", hidden: true, }, { @@ -69,15 +70,13 @@ export class SendEmailNode extends ExecutableNode { async execute(context: NodeContext): Promise { const { to, subject, html, text, cc, replyTo } = context.inputs; - const accessKeyId = context.env.AWS_ACCESS_KEY_ID; - const secretAccessKey = context.env.AWS_SECRET_ACCESS_KEY; - const region = context.env.AWS_REGION; - const defaultFrom = context.env.SES_DEFAULT_FROM; + const sendEmail = context.env.SEND_EMAIL; + const emailDomain = context.env.EMAIL_DOMAIN; const triggerFrom = context.emailMessage?.to; - if (!accessKeyId || !secretAccessKey || !region) { + if (!sendEmail) { return this.createErrorResult( - "AWS credentials (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION) are not set in environment variables." + "SEND_EMAIL binding is not configured in the environment." ); } @@ -91,93 +90,59 @@ export class SendEmailNode extends ExecutableNode { ); } - const sender = triggerFrom || defaultFrom; + const sender = triggerFrom || (emailDomain ? `noreply@${emailDomain}` : ""); if (!sender) { return this.createErrorResult( - "No sender address available. Configure a default sender in SES_DEFAULT_FROM or trigger the workflow via email." + "No sender address available. Configure EMAIL_DOMAIN or trigger the workflow via email." ); } try { const toArray = typeof to === "string" ? [to] : (to as string[]); - const ccArray = cc - ? typeof cc === "string" - ? [cc] - : (cc as string[]) - : []; - const replyToArray = replyTo - ? typeof replyTo === "string" - ? [replyTo] - : (replyTo as string[]) - : []; - const params = new URLSearchParams(); - params.set("Action", "SendEmail"); - params.set("Source", sender); + // Build shared MIME parts once — only the recipient varies per send + const baseMsg = createMimeMessage(); + baseMsg.setSender({ addr: sender, name: "Dafthunk" }); + baseMsg.setSubject(subject as string); - for (let i = 0; i < toArray.length; i++) { - params.set( - `Destination.ToAddresses.member.${i + 1}`, - toArray[i] as string - ); - } - for (let i = 0; i < ccArray.length; i++) { - params.set( - `Destination.CcAddresses.member.${i + 1}`, - ccArray[i] as string - ); - } - for (let i = 0; i < replyToArray.length; i++) { - params.set( - `ReplyToAddresses.member.${i + 1}`, - replyToArray[i] as string - ); + if (cc) { + const ccArray = typeof cc === "string" ? [cc] : (cc as string[]); + for (const addr of ccArray) { + baseMsg.setCc(addr); + } } - params.set("Message.Subject.Data", subject as string); - params.set("Message.Subject.Charset", "UTF-8"); + if (replyTo) { + const replyToArray = + typeof replyTo === "string" ? [replyTo] : (replyTo as string[]); + baseMsg.setHeader("Reply-To", replyToArray.join(", ")); + } if (html) { - params.set("Message.Body.Html.Data", html as string); - params.set("Message.Body.Html.Charset", "UTF-8"); + baseMsg.addMessage({ contentType: "text/html", data: html as string }); } if (text) { - params.set("Message.Body.Text.Data", text as string); - params.set("Message.Body.Text.Charset", "UTF-8"); - } - - const client = new AwsClient({ accessKeyId, secretAccessKey }); - const response = await client.fetch( - `https://email.${region}.amazonaws.com/`, - { - method: "POST", - headers: { "Content-Type": "application/x-www-form-urlencoded" }, - body: params.toString(), - } - ); - - const responseText = await response.text(); - - if (!response.ok) { - const errorMatch = responseText.match(/(.*?)<\/Message>/); - const errorMessage = errorMatch?.[1] ?? responseText; - return this.createErrorResult( - `AWS SES Error (${response.status}): ${errorMessage}` - ); + baseMsg.addMessage({ + contentType: "text/plain", + data: text as string, + }); } - const messageIdMatch = responseText.match( - /(.*?)<\/MessageId>/ + await Promise.all( + toArray.map((recipient) => { + baseMsg.setRecipient(recipient); + const message = new EmailMessage( + sender, + recipient, + baseMsg.asRaw() + ); + return sendEmail.send(message); + }) ); - if (messageIdMatch?.[1]) { - return this.createSuccessResult({ messageId: messageIdMatch[1] }); - } - return this.createErrorResult( - "Failed to send email via Amazon SES. No MessageId returned." - ); + return this.createSuccessResult({ success: true }); } catch (error) { - console.error("SES Error:", error); + console.error("Send Email Error:", error); return this.createErrorResult( error instanceof Error ? error.message : String(error) ); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7a281c5c..7a5e053a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -66,13 +66,13 @@ importers: version: 1.37.0(@modelcontextprotocol/sdk@1.26.0(@cfworker/json-schema@4.1.1)(zod@4.3.5))(bufferutil@4.1.0)(utf-8-validate@6.0.6) '@hono-rate-limiter/cloudflare': specifier: ^0.2.2 - version: 0.2.2(@cloudflare/workers-types@4.20260317.1)(hono@4.12.2) + version: 0.2.2(@cloudflare/workers-types@4.20260317.1)(hono@4.12.7) '@hono/oauth-providers': specifier: ^0.8.5 - version: 0.8.5(hono@4.12.2) + version: 0.8.5(hono@4.12.7) '@hono/zod-validator': specifier: ^0.7.6 - version: 0.7.6(hono@4.12.2)(zod@4.3.5) + version: 0.7.6(hono@4.12.7)(zod@4.3.5) '@mapbox/martini': specifier: ^0.2.0 version: 0.2.0 @@ -107,8 +107,8 @@ importers: specifier: ^2.1.3 version: 2.1.3 hono: - specifier: ^4.12.2 - version: 4.12.2 + specifier: ^4.12.7 + version: 4.12.7 jose: specifier: ^6.1.3 version: 6.1.3 @@ -116,8 +116,11 @@ importers: specifier: ^10.3.0 version: 10.3.0 mailparser: - specifier: ^3.9.1 - version: 3.9.1 + specifier: ^3.9.3 + version: 3.9.3 + mimetext: + specifier: ^3.0.28 + version: 3.0.28 openai: specifier: ^6.16.0 version: 6.16.0(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(zod@4.3.5) @@ -519,8 +522,11 @@ importers: specifier: ^10.3.0 version: 10.3.0 mailparser: - specifier: ^3.9.1 - version: 3.9.1 + specifier: ^3.9.3 + version: 3.9.3 + mimetext: + specifier: ^3.0.28 + version: 3.0.28 openai: specifier: ^6.16.0 version: 6.16.0(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(zod@4.3.5) @@ -1843,10 +1849,6 @@ packages: resolution: {integrity: sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==} engines: {node: ^14.21.3 || >=16} - '@noble/curves@1.9.7': - resolution: {integrity: sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==} - engines: {node: ^14.21.3 || >=16} - '@noble/hashes@1.8.0': resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} engines: {node: ^14.21.3 || >=16} @@ -4215,8 +4217,8 @@ packages: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true - hono@4.12.2: - resolution: {integrity: sha512-gJnaDHXKDayjt8ue0n8Gs0A007yKXj4Xzb8+cNjZeYsSzzwKc0Lr+OZgYwVfB0pHfUs17EPoLvrOsEaJ9mj+Tg==} + hono@4.12.7: + resolution: {integrity: sha512-jq9l1DM0zVIvsm3lv9Nw9nlJnMNPOcAtsbsgiUhWcFzPE99Gvo6yRTlszSLLYacMeQ6quHD6hMfId8crVHvexw==} engines: {node: '>=16.9.0'} html-to-text@9.0.5: @@ -4251,10 +4253,6 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} - iconv-lite@0.7.0: - resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} - engines: {node: '>=0.10.0'} - iconv-lite@0.7.2: resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} engines: {node: '>=0.10.0'} @@ -4523,8 +4521,8 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - mailparser@3.9.1: - resolution: {integrity: sha512-6vHZcco3fWsDMkf4Vz9iAfxvwrKNGbHx0dV1RKVphQ/zaNY34Buc7D37LSa09jeSeybWzYcTPjhiZFxzVRJedA==} + mailparser@3.9.3: + resolution: {integrity: sha512-AnB0a3zROum6fLaa52L+/K2SoRJVyFDk78Ea6q1D0ofcZLxWEWDtsS1+OrVqKbV7r5dulKL/AwYQccFGAPpuYQ==} math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} @@ -4721,8 +4719,8 @@ packages: node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} - nodemailer@7.0.11: - resolution: {integrity: sha512-gnXhNRE0FNhD7wPSCGhdNh46Hs6nm+uTyg+Kq0cZukNQiYdnCsoQjodNP9BQVG9XrcK/v6/MgpAPBUFyzh9pvw==} + nodemailer@7.0.13: + resolution: {integrity: sha512-PNDFSJdP+KFgdsG3ZzMXCgquO7I6McjY2vlqILjtJd0hy8wEvtugS9xKRF2NWlPNGxvLCXlTNIae4serI7dinw==} engines: {node: '>=6.0.0'} normalize-path@3.0.0: @@ -6610,22 +6608,22 @@ snapshots: lit: 3.3.2 three: 0.182.0 - '@hono-rate-limiter/cloudflare@0.2.2(@cloudflare/workers-types@4.20260317.1)(hono@4.12.2)': + '@hono-rate-limiter/cloudflare@0.2.2(@cloudflare/workers-types@4.20260317.1)(hono@4.12.7)': dependencies: '@cloudflare/workers-types': 4.20260317.1 - hono: 4.12.2 + hono: 4.12.7 - '@hono/node-server@1.19.11(hono@4.12.2)': + '@hono/node-server@1.19.11(hono@4.12.7)': dependencies: - hono: 4.12.2 + hono: 4.12.7 - '@hono/oauth-providers@0.8.5(hono@4.12.2)': + '@hono/oauth-providers@0.8.5(hono@4.12.7)': dependencies: - hono: 4.12.2 + hono: 4.12.7 - '@hono/zod-validator@0.7.6(hono@4.12.2)(zod@4.3.5)': + '@hono/zod-validator@0.7.6(hono@4.12.7)(zod@4.3.5)': dependencies: - hono: 4.12.2 + hono: 4.12.7 zod: 4.3.5 '@img/colour@1.0.0': {} @@ -6831,7 +6829,7 @@ snapshots: '@modelcontextprotocol/sdk@1.26.0(@cfworker/json-schema@4.1.1)(zod@4.3.5)': dependencies: - '@hono/node-server': 1.19.11(hono@4.12.2) + '@hono/node-server': 1.19.11(hono@4.12.7) ajv: 8.18.0 ajv-formats: 3.0.1(ajv@8.18.0) content-type: 1.0.5 @@ -6841,7 +6839,7 @@ snapshots: eventsource-parser: 3.0.6 express: 5.2.1 express-rate-limit: 8.3.1(express@5.2.1) - hono: 4.12.2 + hono: 4.12.7 jose: 6.1.3 json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 @@ -6873,11 +6871,6 @@ snapshots: '@noble/hashes': 1.8.0 optional: true - '@noble/curves@1.9.7': - dependencies: - '@noble/hashes': 1.8.0 - optional: true - '@noble/hashes@1.8.0': optional: true @@ -7652,7 +7645,7 @@ snapshots: '@scure/bip32@1.7.0': dependencies: - '@noble/curves': 1.9.7 + '@noble/curves': 1.9.1 '@noble/hashes': 1.8.0 '@scure/base': 1.2.6 optional: true @@ -9233,7 +9226,7 @@ snapshots: content-type: 1.0.5 debug: 4.4.3 http-errors: 2.0.1 - iconv-lite: 0.7.0 + iconv-lite: 0.7.2 on-finished: 2.4.1 qs: 6.15.0 raw-body: 3.0.2 @@ -10013,7 +10006,7 @@ snapshots: he@1.2.0: {} - hono@4.12.2: {} + hono@4.12.7: {} html-to-text@9.0.5: dependencies: @@ -10064,10 +10057,6 @@ snapshots: dependencies: safer-buffer: 2.1.2 - iconv-lite@0.7.0: - dependencies: - safer-buffer: 2.1.2 - iconv-lite@0.7.2: dependencies: safer-buffer: 2.1.2 @@ -10317,16 +10306,16 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - mailparser@3.9.1: + mailparser@3.9.3: dependencies: '@zone-eu/mailsplit': 5.4.8 encoding-japanese: 2.2.0 he: 1.2.0 html-to-text: 9.0.5 - iconv-lite: 0.7.0 + iconv-lite: 0.7.2 libmime: 5.3.7 linkify-it: 5.0.0 - nodemailer: 7.0.11 + nodemailer: 7.0.13 punycode.js: 2.3.1 tlds: 1.261.0 @@ -10650,7 +10639,7 @@ snapshots: node-releases@2.0.27: {} - nodemailer@7.0.11: {} + nodemailer@7.0.13: {} normalize-path@3.0.0: {} @@ -10934,7 +10923,7 @@ snapshots: dependencies: bytes: 3.1.2 http-errors: 2.0.1 - iconv-lite: 0.7.0 + iconv-lite: 0.7.2 unpipe: 1.0.0 rbush@2.0.2: