fix(email): close cost/abuse gap in inbound email-to-save webhook - #133
Conversation
Two problems: (1) the route fetched the full email body via Resend's API before checking whether the sender matched a registered user, so random spam still cost a Resend API call every time; (2) checkResourceLimit only caps free-tier accounts at 50 resources — Pro/unlimited plans had no cap at all, so a spoofed From header impersonating a real paying user could trigger unbounded Gemini embedding calls with no limit. Reorder to match the sender against payload.data.from (already present in the webhook metadata) before ever calling resend.emails.receiving.get(), and add checkEmailSaveRateLimit — a new 20/day-per-user Upstash limit that applies regardless of plan tier, closing the Pro-account gap.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThe inbound webhook now identifies registered senders before fetching email content. It applies a Redis-backed limit of 20 email saves per user per day and skips unregistered or limited senders. ChangesInbound email save flow
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant ResendWebhook
participant InboundRoute
participant UserStore
participant Redis
participant ResendAPI
ResendWebhook->>InboundRoute: deliver sender metadata
InboundRoute->>UserStore: find user by sender address
UserStore-->>InboundRoute: registered user or no match
InboundRoute->>Redis: check daily email-save limit
Redis-->>InboundRoute: allow or limit request
InboundRoute->>ResendAPI: fetch email content for eligible user
ResendAPI-->>InboundRoute: return email content
Possibly related PRs
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
app/api/_utils/rateLimit.tsESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox. app/api/webhooks/resend-inbound/route.tsESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Two problems: (1) the route fetched the full email body via Resend's API before checking whether the sender matched a registered user, so random spam still cost a Resend API call every time; (2) checkResourceLimit only caps free-tier accounts at 50 resources — Pro/unlimited plans had no cap at all, so a spoofed From header impersonating a real paying user could trigger unbounded Gemini embedding calls with no limit.
Reorder to match the sender against payload.data.from (already present in the webhook metadata) before ever calling resend.emails.receiving.get(), and add checkEmailSaveRateLimit — a new 20/day-per-user Upstash limit that applies regardless of plan tier, closing the Pro-account gap.
Summary by CodeRabbit
New Features
Bug Fixes