diff --git a/workers/index.ts b/workers/index.ts index fd3359ce..c370b4ad 100644 --- a/workers/index.ts +++ b/workers/index.ts @@ -20,6 +20,7 @@ import { handleReplyEmail, handleForwardEmail } from "./routes/reply-forward"; import { Folders } from "../shared/folders"; import type { Env } from "./types"; import { requireMailbox, type MailboxContext } from "./lib/mailbox"; +import { logSendRateLimitHit } from "./lib/rate-limit"; type AppContext = Context; @@ -181,7 +182,10 @@ app.post("/api/v1/mailboxes/:mailboxId/emails", async (c: AppContext) => { const { messageId, outgoingMessageId } = generateMessageId(fromDomain); const stub = c.var.mailboxStub; const rateLimitError = await (stub as any).checkSendRateLimit(); - if (rateLimitError) return c.json({ error: rateLimitError }, 429); + if (rateLimitError) { + logSendRateLimitHit(mailboxId, "api.sendEmail", rateLimitError); + return c.json({ error: rateLimitError }, 429); + } const attachmentData = await storeAttachments(c.env.BUCKET, messageId, attachments); await stub.createEmail(Folders.SENT, { diff --git a/workers/lib/rate-limit.ts b/workers/lib/rate-limit.ts new file mode 100644 index 00000000..be430d22 --- /dev/null +++ b/workers/lib/rate-limit.ts @@ -0,0 +1,11 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +export function logSendRateLimitHit( + mailboxId: string, + source: string, + error: string, +) { + console.warn("Send rate limit hit", { mailboxId, source, error }); +} diff --git a/workers/lib/tools.ts b/workers/lib/tools.ts index 0180e340..278257ac 100644 --- a/workers/lib/tools.ts +++ b/workers/lib/tools.ts @@ -27,6 +27,7 @@ import { buildThreadingHeaders, } from "./email-helpers"; import { verifyDraft } from "./ai"; +import { logSendRateLimitHit } from "./rate-limit"; import { sendEmail } from "../email-sender"; import { Folders } from "../../shared/folders"; import type { Env } from "../types"; @@ -408,6 +409,7 @@ export async function toolSendReply( // Check send rate limit const rateLimitError = await (stub as unknown as RateLimitStub).checkSendRateLimit(); if (rateLimitError) { + logSendRateLimitHit(mailboxId, "tool.sendReply", rateLimitError); return { error: rateLimitError }; } @@ -486,6 +488,7 @@ export async function toolSendEmail( // Check send rate limit const rateLimitError = await (stub as unknown as RateLimitStub).checkSendRateLimit(); if (rateLimitError) { + logSendRateLimitHit(mailboxId, "tool.sendEmail", rateLimitError); return { error: rateLimitError }; } diff --git a/workers/routes/reply-forward.ts b/workers/routes/reply-forward.ts index 950333c3..e11d07d0 100644 --- a/workers/routes/reply-forward.ts +++ b/workers/routes/reply-forward.ts @@ -17,6 +17,7 @@ import { import { SendEmailRequestSchema } from "../lib/schemas"; import { Folders } from "../../shared/folders"; import type { MailboxContext } from "../lib/mailbox"; +import { logSendRateLimitHit } from "../lib/rate-limit"; type AppContext = Context; type RateLimitStub = { checkSendRateLimit: () => Promise }; @@ -50,6 +51,7 @@ export async function handleReplyEmail(c: AppContext) { const rateLimitError = await (stub as unknown as RateLimitStub) .checkSendRateLimit(); if (rateLimitError) { + logSendRateLimitHit(mailboxId, "api.replyEmail", rateLimitError); return c.json({ error: rateLimitError }, 429); } @@ -140,6 +142,7 @@ export async function handleForwardEmail(c: AppContext) { const rateLimitError = await (stub as unknown as RateLimitStub) .checkSendRateLimit(); if (rateLimitError) { + logSendRateLimitHit(mailboxId, "api.forwardEmail", rateLimitError); return c.json({ error: rateLimitError }, 429); }