Skip to content

Fix send-email schema; add template-based sending#82

Merged
mklocek merged 2 commits intomainfrom
send-email-fixes
May 11, 2026
Merged

Fix send-email schema; add template-based sending#82
mklocek merged 2 commits intomainfrom
send-email-fixes

Conversation

@mklocek
Copy link
Copy Markdown
Contributor

@mklocek mklocek commented May 7, 2026

Summary

Two related changes to send-email and send-sandbox-email MCP tools, on top of the customer-reported schema bug. Tracked in MT-22001.

  • 80c5c77 — fix: align input schema with REST API requirements

    • category and to removed from schema required arrays in both tools.
    • Runtime check: at least one of to / cc / bcc must contain a recipient (matches the API's actual rule).
    • parseSandboxTo no longer throws on empty input — combined check in handler is the single source of truth.
  • eea569e — feat: template-based sending

    • New template_uuid and template_variables parameters on send-email and send-sandbox-email.
    • Per the API, when template_uuid is set, subject / text / html / category must be omitted — enforced at runtime with a clear error.
    • Also rejects template_variables without template_uuid.
    • subject removed from schema required (does not apply to template sends).

Background

A customer reported that the schema was out of sync with the Mailtrap Send Email API:

  • category was marked required (it's an optional tracking tag).
  • to was marked required (per API, at least one of to/cc/bcc is required).
  • The MCP did not expose template_uuid at all — callers couldn't send via a Mailtrap template.

Test plan

Automated:

  • npm test — 186 / 186 jest tests pass (+ 10 new tests for cc/bcc-only sends and template paths).
  • npm run lint — eslint + tsc clean.

Manual against Mailtrap (suggested for QA):

  • Inline send: only to, only cc, only bcc, all combined.
  • Inline send: omit category (no longer schema-required).
  • Template send: template_uuid only; with template_variables; with cc/bcc.
  • Negative: template_uuid + subject/text/html/category → rejected with clear message.
  • Negative: template_variables without template_uuid → rejected.
  • Negative: no recipient at all → rejected with the combined-recipient error.

Summary by CodeRabbit

  • New Features

    • Template-based email sending with template variables; template mode disallows inline subject/text/html/category.
    • Recipient handling: to is optional; at least one recipient must exist across to, cc, or bcc.
  • Documentation

    • Clarified schema descriptions for template vs inline sends and recipient rules.
  • Tests

    • Added coverage for template sends, recipient validation (including cc/bcc-only cases), and related error paths.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 7, 2026

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2d620dcd-cac9-43f1-9fa6-deed3ef928d7

📥 Commits

Reviewing files that changed from the base of the PR and between eea569e and 539a8fa.

📒 Files selected for processing (9)
  • CHANGELOG.md
  • src/tools/sandbox/__tests__/sendSandboxEmail.test.ts
  • src/tools/sandbox/schemas/sendSandboxEmail.ts
  • src/tools/sandbox/sendSandboxEmail.ts
  • src/tools/sendEmail/__tests__/sendEmail.test.ts
  • src/tools/sendEmail/schema.ts
  • src/tools/sendEmail/sendEmail.ts
  • src/types/mailtrap.ts
  • src/utils/mailtrapAddresses.ts
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/tools/sandbox/sendSandboxEmail.ts
  • src/utils/mailtrapAddresses.ts
  • src/types/mailtrap.ts
  • src/tools/sendEmail/sendEmail.ts
  • src/tools/sendEmail/schema.ts
  • src/tools/sandbox/tests/sendSandboxEmail.test.ts
  • src/tools/sendEmail/tests/sendEmail.test.ts

📝 Walkthrough

Walkthrough

This PR adds template-based email sending support to both sendEmail and sendSandboxEmail. It introduces template_uuid and optional template_variables, makes to/subject/category optional in types and schemas, shifts required-field enforcement to runtime (at least one recipient across to/cc/bcc), and adds tests and changelog entries.

Changes

Template-Based Email Sending

Layer / File(s) Summary
Type Contracts
src/types/mailtrap.ts
Add TemplateVariableValue recursive type; make to, subject, and category optional in SendMailToolRequest and SendSandboxEmailRequest; add template_uuid and template_variables fields.
Schema & Field Descriptions
src/tools/sendEmail/schema.ts, src/tools/sandbox/schemas/sendSandboxEmail.ts
Expand property descriptions to document template vs inline modes and mutual-exclusion rules; change required arrays to [] to allow runtime validation.
Recipient Parsing Utility
src/utils/mailtrapAddresses.ts
Change parseSandboxTo to return an array (possibly empty) instead of throwing; update JSDoc to require callers validate combined to/cc/bcc.
sendEmail Implementation
src/tools/sendEmail/sendEmail.ts
Add template-aware validation/payload construction; treat to as optional and only error if all recipients empty; normalize cc/bcc conditionally; conditional emailData for template vs inline sends.
sendSandboxEmail Implementation
src/tools/sandbox/sendSandboxEmail.ts
Add template-aware validation/payload construction for sandbox; parse to only when provided; normalize cc/bcc conditionally; error if combined recipients empty; success message prefers to then cc/bcc.
sendEmail Test Coverage
src/tools/sendEmail/__tests__/sendEmail.test.ts
Add tests for no-recipient across to/cc/bcc, empty to filtering, cc/bcc-only sends, template sends with template_variables, and validation errors for invalid template/inline combinations.
sendSandboxEmail Test Coverage
src/tools/sandbox/__tests__/sendSandboxEmail.test.ts
Add tests for no-recipient error, cc/bcc-only sends with omitted to, template sends, and invalid template/inline combinations.
Changelog
CHANGELOG.md
Document new template-based sending fields and rules, category optionality, optional to with runtime recipient validation, and omission requirements for template mode.

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly Related PRs

Suggested Reviewers

  • VladimirTaytor
  • IgorDobryn
  • i7an
  • piobeny

Poem

🐰 A rabbit writes in byte and rhyme,
Templates hop in, just in time.
Recipients counted, subject set free,
CC and BCC sing in harmony.
Mailtrap smiles—delivery jubilee!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix send-email schema; add template-based sending' accurately and concisely summarizes the two main changes: schema fixes and template-based sending feature addition.
Description check ✅ Passed The description is comprehensive and well-structured, covering motivation, background, specific changes with commit references, and a detailed test plan with both automated and manual testing guidance.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch send-email-fixes

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
Copy Markdown

@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: 1

🧹 Nitpick comments (2)
src/tools/sendEmail/sendEmail.ts (1)

30-55: ⚡ Quick win

Extract template-vs-inline validation into a shared helper to eliminate duplication.

Lines 30–55 here are duplicated almost verbatim in src/tools/sandbox/sendSandboxEmail.ts (lines 38–63). Any future change to the rules (e.g., a new forbidden field or a different error message) needs to be made in two places, which is error-prone. Consider a small helper in src/utils/mailtrapAddresses.ts (or a new src/utils/mailtrapTemplate.ts) such as validateTemplateOrInline({ template_uuid, template_variables, subject, text, html, category }) returning a discriminated { mode: "template" } | { mode: "inline"; subject: string } so callers also get a narrowed subject and avoid the as string cast at line 79.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tools/sendEmail/sendEmail.ts` around lines 30 - 55, The validation logic
for template vs inline email is duplicated in sendEmail (the block checking
template_uuid, subject, html/text, template_variables, category) and
sendSandboxEmail; extract this into a shared helper (e.g.,
validateTemplateOrInline) that accepts { template_uuid, template_variables,
subject, text, html, category } and returns a discriminated result like { mode:
"template" } | { mode: "inline", subject: string } (so callers get a narrowed
subject without casts); replace the duplicated blocks in sendEmail and
sendSandboxEmail with calls to validateTemplateOrInline and use its return to
drive subsequent logic and error messages.
src/types/mailtrap.ts (1)

29-40: ⚖️ Poor tradeoff

Consider discriminated union to enforce template vs. inline shapes at the type level.

The merged interface with all fields optional means TypeScript won't catch invalid combinations (e.g., template_uuid + subject together, or template_variables without template_uuid) at compile time — enforcement is deferred entirely to runtime. Modeling the two shapes as a discriminated union would make illegal states unrepresentable and let the type-checker catch misuse.

♻️ Suggested discriminated union approach
-export interface SendMailToolRequest {
-  from?: MailtrapAddressParam;
-  to?: MailtrapAddressParam | MailtrapAddressParam[];
-  subject?: string;
-  text?: string;
-  html?: string;
-  cc?: MailtrapAddressParam[];
-  bcc?: MailtrapAddressParam[];
-  category?: string;
-  template_uuid?: string;
-  template_variables?: Record<string, TemplateVariableValue>;
-}
+interface SendMailBaseRequest {
+  from?: MailtrapAddressParam;
+  to?: MailtrapAddressParam | MailtrapAddressParam[];
+  cc?: MailtrapAddressParam[];
+  bcc?: MailtrapAddressParam[];
+}
+
+interface SendMailInlineRequest extends SendMailBaseRequest {
+  template_uuid?: never;
+  template_variables?: never;
+  subject?: string;
+  text?: string;
+  html?: string;
+  category?: string;
+}
+
+interface SendMailTemplateRequest extends SendMailBaseRequest {
+  template_uuid: string;
+  template_variables?: Record<string, TemplateVariableValue>;
+  subject?: never;
+  text?: never;
+  html?: never;
+  category?: never;
+}
+
+export type SendMailToolRequest = SendMailInlineRequest | SendMailTemplateRequest;

The same pattern can be applied to SendSandboxEmailRequest.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/types/mailtrap.ts` around lines 29 - 40, Change SendMailToolRequest into
a discriminated union that enforces "template" vs "inline" shapes: one variant
must have template_uuid (and may include template_variables) and must exclude
inline-only fields like subject/text/html, and the other variant must omit
template_uuid/template_variables and require at least one of subject/text/html;
do the same refactor for SendSandboxEmailRequest. Update the exported types so
the discriminator is template_uuid (present vs absent) and ensure all callers
compile against the new union to catch illegal combinations at compile time.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/tools/sendEmail/schema.ts`:
- Around line 58-63: The schema description for template_variables contradicts
runtime behavior: update the description of template_variables in
src/tools/sendEmail/schema.ts to state that template_variables must be used
together with template_uuid (i.e., "'template_variables' is only allowed when
'template_uuid' is provided and will be rejected otherwise") to match the
handler logic that throws when template_variables is present without
template_uuid; make the identical wording change in the sandbox schema at
src/tools/sandbox/schemas/sendSandboxEmail.ts so both schemas match the
validation performed by the sendEmail handler and its sandbox counterpart.

---

Nitpick comments:
In `@src/tools/sendEmail/sendEmail.ts`:
- Around line 30-55: The validation logic for template vs inline email is
duplicated in sendEmail (the block checking template_uuid, subject, html/text,
template_variables, category) and sendSandboxEmail; extract this into a shared
helper (e.g., validateTemplateOrInline) that accepts { template_uuid,
template_variables, subject, text, html, category } and returns a discriminated
result like { mode: "template" } | { mode: "inline", subject: string } (so
callers get a narrowed subject without casts); replace the duplicated blocks in
sendEmail and sendSandboxEmail with calls to validateTemplateOrInline and use
its return to drive subsequent logic and error messages.

In `@src/types/mailtrap.ts`:
- Around line 29-40: Change SendMailToolRequest into a discriminated union that
enforces "template" vs "inline" shapes: one variant must have template_uuid (and
may include template_variables) and must exclude inline-only fields like
subject/text/html, and the other variant must omit
template_uuid/template_variables and require at least one of subject/text/html;
do the same refactor for SendSandboxEmailRequest. Update the exported types so
the discriminator is template_uuid (present vs absent) and ensure all callers
compile against the new union to catch illegal combinations at compile time.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 12aae77b-5d37-443f-aac5-af407ea40026

📥 Commits

Reviewing files that changed from the base of the PR and between ec92130 and eea569e.

📒 Files selected for processing (9)
  • CHANGELOG.md
  • src/tools/sandbox/__tests__/sendSandboxEmail.test.ts
  • src/tools/sandbox/schemas/sendSandboxEmail.ts
  • src/tools/sandbox/sendSandboxEmail.ts
  • src/tools/sendEmail/__tests__/sendEmail.test.ts
  • src/tools/sendEmail/schema.ts
  • src/tools/sendEmail/sendEmail.ts
  • src/types/mailtrap.ts
  • src/utils/mailtrapAddresses.ts

Comment thread src/tools/sendEmail/schema.ts
@mklocek mklocek force-pushed the send-email-fixes branch from eea569e to eb36251 Compare May 7, 2026 16:56
mklocek added 2 commits May 8, 2026 11:21
Per the Mailtrap Send Email API, `category` is an optional tracking
tag, and recipients are satisfied by any of `to`, `cc`, or `bcc` —
not specifically `to`. The send-email and send-sandbox-email schemas
over-declared both: `category` was listed as required, and `to`
was the only allowed recipient channel.

- send-email schema: drop `category` and `to` from `required`;
  clarify descriptions.
- send-sandbox-email schema: drop `to` from `required`; clarify
  descriptions. (`category` was already optional here.)
- Handlers: validate at runtime that the combined to/cc/bcc list
  contains at least one recipient.
- `parseSandboxTo` no longer throws on empty input; the combined
  check in the handler is the single source of truth.
- Tests: update the empty-`to` cases to assert the new error
  message; add cc-only and bcc-only happy-path tests.
Adds `template_uuid` (with optional `template_variables`) so the MCP
tools can send via a Mailtrap email template instead of inline
content.

Per the Mailtrap API, when `template_uuid` is set the inline fields
(`subject`, `text`, `html`, `category`) must be omitted; this is
enforced at runtime in both handlers. `subject` is no longer in the
schema's `required` array because it does not apply to template
sends. Also guards against `template_variables` passed without
`template_uuid`.
@mklocek mklocek force-pushed the send-email-fixes branch from eb36251 to 539a8fa Compare May 8, 2026 09:21
@mklocek mklocek merged commit 1352bd7 into main May 11, 2026
2 checks passed
@mklocek mklocek deleted the send-email-fixes branch May 11, 2026 11:47
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.

4 participants