Conversation
- Add RECOUP_FROM_EMAIL constant to lib/const.ts - Create sendEmailSchema for input validation - Implement registerSendEmailTool for MCP server - Add unit tests for the new tool Migrates sendEmailTool from Recoup-Chat to API MCP server 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
DRY principle - reference existing domain constant 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…-migrate-sendemailtool-to-the-mcp-server-in-the-api feat: add send_email MCP tool
Update CLAUDE.md to sync test with main at the start of each new task 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Extract email footer to shared getEmailFooter function - Update generateEmailResponse to use shared footer - Add send_email MCP tool with automatic footer appending - Add RECOUP_FROM_EMAIL constant for default sender - Add unit tests for getEmailFooter and sendEmailSchema 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…mail tool - Add room_id parameter to sendEmailSchema - Add getEmailFooter import and usage in registerSendEmailTool - Update test to expect footer in HTML - Remove duplicate emails folder structure 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Footer is now always included regardless of text vs html input - Text input is converted to HTML with <p> tags - Removed text field from sendEmailWithResend call (DRY/KISS) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…email-tool-email-footer-shared-with-email-client feat: add shared email footer and send_email MCP tool
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. 📝 WalkthroughWalkthroughAdds a send_email MCP tool with Zod input validation, extracts email footer generation into getEmailFooter, introduces RECOUP_FROM_EMAIL constant, updates generateEmailResponse to use the footer util, adds tests for footer and the tool, and documents PR branch/process guidelines in CLAUDE.md. Changes
Sequence DiagramsequenceDiagram
actor Client
participant MCP as MCP Server
participant Tool as send_email handler
participant Footer as getEmailFooter
participant Email as sendEmailWithResend
participant Resend as Resend API
Client->>MCP: Invoke send_email tool
MCP->>Tool: Execute handler with validated input
Tool->>Footer: getEmailFooter(room_id)
Footer-->>Tool: footer HTML
Tool->>Tool: Compose body (text/html + footer)\nSet from = RECOUP_FROM_EMAIL\nAttach CC if present
Tool->>Email: sendEmailWithResend(payload)
Email->>Resend: POST email
alt Success
Resend-->>Email: success
Email-->>Tool: success response
Tool-->>MCP: getToolResultSuccess(...)
else Error / NextResponse
Resend-->>Email: error / NextResponse
Email-->>Tool: NextResponse
Tool-->>MCP: getToolResultError(message)
end
MCP-->>Client: return tool result
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
📜 Recent review detailsConfiguration used: defaults Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
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 |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Fix all issues with AI agents
In @CLAUDE.md:
- Around line 15-25: The "Starting a New Task" section currently instructs
pushing directly to the test branch using the git command snippet, which
contradicts the repository rule that forbids direct pushes to main or test;
replace that snippet and prose so syncing is done via a temporary sync branch
and a PR (e.g., create a sync branch from test, merge origin/main into it, open
a PR to test and merge via review) or, if this direct-push exception is
intentional, update the repository learnings to explicitly permit it and
document the rationale; locate the "Starting a New Task" heading and the git
command block to make the change.
In @lib/emails/getEmailFooter.ts:
- Around line 13-21: The chatLink interpolation uses roomId directly and
sendEmailSchema currently allows any string; validate room_id as a UUID in the
sendEmailSchema (use z.string().uuid().optional() on the room_id field) and/or
HTML-escape/sanitize roomId before inserting into the href in getEmailFooter
(the chatLink construction or the getEmailFooter function) to prevent XSS —
apply one or both fixes and adjust any callers to accept the stricter schema.
In @lib/emails/sendEmailSchema.ts:
- Around line 4-11: The schema currently allows any string for the recipient
fields; update the "to" and "cc" definitions in sendEmailSchema.ts to validate
email format by using Zod's email schema (wrap z.string().email() inside
z.array(...)), preserving the existing .describe(), .default([]) and .optional()
behavior for "cc"; ensure "to" remains a required array of z.string().email()
and "cc" remains
z.array(z.string().email()).describe(...).default([]).optional() so invalid
emails are rejected during validation.
In @lib/mcp/tools/registerSendEmailTool.ts:
- Around line 39-44: The code incorrectly tries to read data?.error?.message
(treating error as an object); instead, use the actual response shape from
sendEmailWithResend: prefer data?.details?.message, fall back to data?.error
(string), and then the existing fallback message. Update the block in
registerSendEmailTool.ts where you handle result instanceof NextResponse (the
call to result.json() and the getToolResultError return) to use
data?.details?.message || data?.error || `Failed to send email from
${RECOUP_FROM_EMAIL} to ${to}.`.
- Line 27: The code builds bodyHtml with raw text interpolation (const bodyHtml
= html || (text ? `<p>${text}</p>` : "")) which allows XSS; add an
escapeHtml(text: string) utility that replaces &, <, >, ", ' with their HTML
entities and then use it when composing bodyHtml (i.e., replace `<p>${text}</p>`
with `<p>${escapeHtml(text)}</p>`), ensuring escapeHtml is exported/accessible
in the same module and used wherever plain text is converted into HTML.
🧹 Nitpick comments (3)
lib/emails/sendEmailSchema.ts (1)
5-26: Remove redundant.optional()after.default().In Zod 4,
.default()already makes the field optional in the input and ensures the output always has a value. Chaining.optional()after.default()is redundant since the output type never includesundefinedwhen a default is provided.♻️ Simplify the schema by removing redundant .optional() calls
cc: z .array(z.string()) .describe( "Optional array of CC email addresses. active_account_email should always be included unless already in 'to'.", ) - .default([]) - .optional(), + .default([]), subject: z.string().describe("Email subject line"), text: z .string() .describe("Plain text body of the email. Use context to make this creative and engaging.") .optional(), html: z .string() .describe("HTML body of the email. Use context to make this creative and engaging.") - .default("") - .optional(), + .default(""), headers: z .record(z.string(), z.string()) .describe("Optional custom headers for the email") - .default({}) - .optional(), + .default({}),Based on learnings, Zod 4 changed default value behavior to short-circuit on
undefinedinput.lib/mcp/tools/__tests__/registerSendEmailTool.test.ts (2)
47-54: Use RECOUP_FROM_EMAIL constant instead of hardcoded value.The
fromaddress is hardcoded as"Agent by Recoup <agent@recoupable.com>". This creates a maintenance burden—if the constant value changes, the test will break. Import and use theRECOUP_FROM_EMAILconstant for consistency.♻️ Import and use the constant
import { describe, it, expect, vi, beforeEach } from "vitest"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { registerSendEmailTool } from "../registerSendEmailTool"; import { NextResponse } from "next/server"; +import { RECOUP_FROM_EMAIL } from "@/lib/const"; const mockSendEmailWithResend = vi.fn();expect(mockSendEmailWithResend).toHaveBeenCalledWith({ - from: "Agent by Recoup <agent@recoupable.com>", + from: RECOUP_FROM_EMAIL, to: ["test@example.com"], cc: undefined, subject: "Test Subject", html: expect.stringMatching(/Test body.*you can reply directly to this email/s), headers: {}, });
12-100: Consider adding test coverage for HTML escaping and edge cases.The test suite would benefit from additional coverage for:
- Text input containing HTML special characters (
<,>,&,",') to verify proper escaping- Room ID handling in the email footer
- Edge cases like empty
toarrays or malformed email addresses (especially after adding email validation)These tests would help prevent regressions after fixing the XSS vulnerability identified in the implementation.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
CLAUDE.mdlib/const.tslib/emails/__tests__/getEmailFooter.test.tslib/emails/getEmailFooter.tslib/emails/inbound/generateEmailResponse.tslib/emails/sendEmailSchema.tslib/mcp/tools/__tests__/registerSendEmailTool.test.tslib/mcp/tools/index.tslib/mcp/tools/registerSendEmailTool.ts
🧰 Additional context used
📓 Path-based instructions (1)
lib/const.ts
📄 CodeRabbit inference engine (CLAUDE.md)
All shared constants should be defined in
lib/const.tsincluding domain names, wallet addresses, model names, and API keys
Files:
lib/const.ts
🧠 Learnings (4)
📚 Learning: 2026-01-07T16:53:05.959Z
Learnt from: CR
Repo: Recoupable-com/Recoup-API PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-07T16:53:05.959Z
Learning: Applies to lib/const.ts : All shared constants should be defined in `lib/const.ts` including domain names, wallet addresses, model names, and API keys
Applied to files:
lib/const.ts
📚 Learning: 2026-01-07T16:53:05.959Z
Learnt from: CR
Repo: Recoupable-com/Recoup-API PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-07T16:53:05.959Z
Learning: Never push directly to `main` or `test` branches - always use feature branches and pull requests
Applied to files:
CLAUDE.md
📚 Learning: 2026-01-07T16:53:05.959Z
Learnt from: CR
Repo: Recoupable-com/Recoup-API PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-07T16:53:05.959Z
Learning: Before pushing changes, verify the current branch is not `main` or `test`
Applied to files:
CLAUDE.md
📚 Learning: 2026-01-07T16:53:05.959Z
Learnt from: CR
Repo: Recoupable-com/Recoup-API PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-07T16:53:05.959Z
Learning: Always commit and push changes after completing a task with descriptive commit messages
Applied to files:
CLAUDE.md
🧬 Code graph analysis (4)
lib/emails/__tests__/getEmailFooter.test.ts (1)
lib/emails/getEmailFooter.ts (1)
getEmailFooter(7-27)
lib/mcp/tools/registerSendEmailTool.ts (6)
lib/const.ts (1)
RECOUP_FROM_EMAIL(22-22)lib/emails/sendEmailSchema.ts (2)
sendEmailSchema(3-31)SendEmailInput(33-33)lib/emails/getEmailFooter.ts (1)
getEmailFooter(7-27)lib/emails/sendEmail.ts (1)
sendEmailWithResend(13-33)lib/mcp/getToolResultError.ts (1)
getToolResultError(9-16)lib/mcp/getToolResultSuccess.ts (1)
getToolResultSuccess(9-11)
lib/emails/inbound/generateEmailResponse.ts (1)
lib/emails/getEmailFooter.ts (1)
getEmailFooter(7-27)
lib/mcp/tools/index.ts (1)
lib/mcp/tools/registerSendEmailTool.ts (1)
registerSendEmailTool(16-53)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Vercel Agent Review
🔇 Additional comments (4)
lib/const.ts (1)
21-22: LGTM!The new constant correctly follows the coding guideline to define shared constants in
lib/const.ts, reuses the existingOUTBOUND_EMAIL_DOMAIN, and produces a valid RFC 5322 email format.lib/emails/__tests__/getEmailFooter.test.ts (1)
1-41: LGTM!Comprehensive test coverage for the
getEmailFooterfunction. The tests effectively validate both conditional branches (with/withoutroomId), HTML structure, security attributes (target="_blank",rel="noopener noreferrer"), and styling consistency.lib/mcp/tools/index.ts (1)
16-16: LGTM!The import and registration of the new
send_emailtool follows the established pattern and integrates cleanly with the existing tool registry.Also applies to: 36-36
lib/emails/inbound/generateEmailResponse.ts (1)
5-5: LGTM!This refactoring correctly extracts footer generation into the reusable
getEmailFooterfunction, reducing code duplication while preserving the existing behavior. The function call properly passes theroomIdparameter.Also applies to: 33-33
Replace hard-coded <p> tags with marked library for consistent markdown parsing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Summary by CodeRabbit
New Features
Tests
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.