Skip to content

Test#96

Merged
sweetmantech merged 11 commits intomainfrom
test
Jan 7, 2026
Merged

Test#96
sweetmantech merged 11 commits intomainfrom
test

Conversation

@sweetmantech
Copy link
Contributor

@sweetmantech sweetmantech commented Jan 7, 2026

Summary by CodeRabbit

  • New Features

    • Send email tool: default sender address, automatic footer (reply note + optional chat link), support for multiple recipients, CC, HTML and plain-text bodies, and input validation.
  • Tests

    • Added coverage for email footer rendering, send-email flows, CC handling, and error scenarios.
  • Documentation

    • Contribution guide updated with new branching and sync workflow instructions.

✏️ Tip: You can customize this high-level summary in your review settings.

sweetmantech and others added 9 commits January 7, 2026 12:00
- 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
@vercel
Copy link
Contributor

vercel bot commented Jan 7, 2026

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

Project Deployment Review Updated (UTC)
recoup-api Ready Ready Preview Jan 7, 2026 6:41pm

@coderabbitai
Copy link

coderabbitai bot commented Jan 7, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

Adds 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

Cohort / File(s) Summary
Documentation
CLAUDE.md
Adds rule to open PRs against test and a "Starting a New Task" section describing syncing test with main before creating feature branches.
Constants
lib/const.ts
New exported RECOUP_FROM_EMAIL constant composing a default from-address using OUTBOUND_EMAIL_DOMAIN.
Email Footer Utilities
lib/emails/getEmailFooter.ts, lib/emails/__tests__/getEmailFooter.test.ts
New getEmailFooter(roomId?: string) returns trimmed HTML footer with a reply note, horizontal rule, and optional chat link when roomId is provided; tests validate conditional content and styling.
Email Schema
lib/emails/sendEmailSchema.ts
New Zod schema sendEmailSchema and derived SendEmailInput type for email payload validation (to, cc, subject, text, html, headers, room_id).
Inbound Email Response
lib/emails/inbound/generateEmailResponse.ts
Replaced inline footer HTML assembly with call to getEmailFooter(roomId).
MCP Send Email Tool
lib/mcp/tools/registerSendEmailTool.ts, lib/mcp/tools/__tests__/registerSendEmailTool.test.ts
New registerSendEmailTool(server) registers send_email tool using sendEmailSchema; handler composes body+footer, enforces RECOUP_FROM_EMAIL, normalizes CC, calls sendEmailWithResend, and maps NextResponse errors to tool errors; tests cover registration, payload composition, CC handling, and error propagation.
Tool Registration
lib/mcp/tools/index.ts
Imports and invokes registerSendEmailTool(server) inside registerAllTools to register the new tool at startup.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰
I stitched a footer soft and neat,
Chat links trailing like carrot-sweet,
Zod keeps fields in tidy rows,
MCP hops where the message goes,
From Recoup burrow — bounce and beat! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Test' is vague and generic, providing no meaningful information about the changeset's purpose or main changes. Use a descriptive title that summarizes the primary change, such as 'Add send email tool with footer generation' or 'Implement email sending functionality with MCP tool registration'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1f05769 and ed45944.

📒 Files selected for processing (1)
  • lib/emails/sendEmailSchema.ts

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 and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 includes undefined when 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 undefined input.

lib/mcp/tools/__tests__/registerSendEmailTool.test.ts (2)

47-54: Use RECOUP_FROM_EMAIL constant instead of hardcoded value.

The from address 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 the RECOUP_FROM_EMAIL constant 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 to arrays 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

📥 Commits

Reviewing files that changed from the base of the PR and between 058c714 and 54bb239.

📒 Files selected for processing (9)
  • CLAUDE.md
  • lib/const.ts
  • lib/emails/__tests__/getEmailFooter.test.ts
  • lib/emails/getEmailFooter.ts
  • lib/emails/inbound/generateEmailResponse.ts
  • lib/emails/sendEmailSchema.ts
  • lib/mcp/tools/__tests__/registerSendEmailTool.test.ts
  • lib/mcp/tools/index.ts
  • lib/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.ts including 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 existing OUTBOUND_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 getEmailFooter function. The tests effectively validate both conditional branches (with/without roomId), 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_email tool 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 getEmailFooter function, reducing code duplication while preserving the existing behavior. The function call properly passes the roomId parameter.

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>
@sweetmantech sweetmantech merged commit b6fffb1 into main Jan 7, 2026
2 of 4 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Jan 8, 2026
Merged
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