Skip to content

fix(email): close cost/abuse gap in inbound email-to-save webhook - #133

Merged
Rayan9064 merged 1 commit into
mainfrom
fix/email-webhook-cost-limit
Aug 7, 2026
Merged

fix(email): close cost/abuse gap in inbound email-to-save webhook#133
Rayan9064 merged 1 commit into
mainfrom
fix/email-webhook-cost-limit

Conversation

@Rayan9064

@Rayan9064 Rayan9064 commented Aug 7, 2026

Copy link
Copy Markdown
Owner

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

    • Added daily limits for saving inbound emails, helping prevent excessive usage.
    • Added clearer handling for emails from unregistered senders.
  • Bug Fixes

    • Inbound emails are now validated before content is retrieved.
    • Email content is only fetched for eligible, authenticated senders who remain within applicable limits.
    • Rate limiting continues to function safely when the supporting service is unavailable.

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

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
dumpit Ready Ready Preview Aug 7, 2026 4:39pm

@Rayan9064
Rayan9064 merged commit 7768d02 into main Aug 7, 2026
3 of 5 checks passed
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: da3a64bf-9201-41c9-8bd8-58e8c334ec78

📥 Commits

Reviewing files that changed from the base of the PR and between 6020ead and cccff2b.

📒 Files selected for processing (2)
  • app/api/_utils/rateLimit.ts
  • app/api/webhooks/resend-inbound/route.ts

📝 Walkthrough

Walkthrough

The 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.

Changes

Inbound email save flow

Layer / File(s) Summary
Email-save rate-limit utility
app/api/_utils/rateLimit.ts
Adds a lazily initialized Redis limiter with a 20-request daily sliding window. The exported checker returns { isLimited: boolean } and fails open without Redis.
Webhook sender and fetch gating
app/api/webhooks/resend-inbound/route.ts
Matches the webhook sender before fetching email content. It applies the email-save limit after resource checks and skips unregistered or limited senders.

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
Loading

Possibly related PRs

  • Rayan9064/dumpit#114: Both changes extend Redis-backed rate limiting in app/api/_utils/rateLimit.ts.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/email-webhook-cost-limit

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

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

app/api/_utils/rateLimit.ts

ESLint 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.ts

ESLint 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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant