Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 21 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,50 @@ All notable changes to the MCP Send Email project will be documented in this fil
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- Add more audience tools (create/get/delete)
- Add contact tools
- Add broadcast tools

### Changed

- Upgrade Resend SDK to v6.1.0
- Improve `list-audiences` response formatting

## [1.1.0] - 2025-07-08

### Added

- List audiences tool for Resend
- Removed React Email dependencies since it's not used in the project
- Updated Resend to latest version
- Add biome for formatting

## [Unreleased]

- Improved instructions in README
- Removed test email address from example email.md

### Added
- CC and BCC support for email recipients
- Full request/response logging for improved debugging
- New "Features" section in README documentation
- Usage examples for CC/BCC in email.md

### Fixed

- Sender email handling with Resend's API
- Type definitions for email request object

### Changed

- Improved instructions in README
- Removed test email address from example email.md
- Enhanced console logging for easier troubleshooting
- Updated documentation with Resend's email verification requirements

## [1.0.0] - 2025-02-24

### Added

- Initial release
- Basic email sending functionality
- HTML email support
- Email scheduling capability
- Reply-to addressing
- Reply-to addressing
179 changes: 15 additions & 164 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import minimist from 'minimist';
import { Resend } from 'resend';
import { z } from 'zod';
import packageJson from './package.json' with { type: 'json' };
import {
addAudienceTools,
addBroadcastTools,
addContactTools,
addEmailTools,
} from './tools/index.js';

// Parse command line arguments
const argv = minimist(process.argv.slice(2));
Expand Down Expand Up @@ -37,171 +43,16 @@ const resend = new Resend(apiKey);
// Create server instance
const server = new McpServer({
name: 'email-sending-service',
version: '1.0.0',
version: packageJson.version,
});

server.tool(
'send-email',
'Send an email using Resend',
{
to: z.string().email().describe('Recipient email address'),
subject: z.string().describe('Email subject line'),
text: z.string().describe('Plain text email content'),
html: z
.string()
.optional()
.describe(
'HTML email content. When provided, the plain text argument MUST be provided as well.',
),
cc: z
.string()
.email()
.array()
.optional()
.describe(
'Optional array of CC email addresses. You MUST ask the user for this parameter. Under no circumstance provide it yourself',
),
bcc: z
.string()
.email()
.array()
.optional()
.describe(
'Optional array of BCC email addresses. You MUST ask the user for this parameter. Under no circumstance provide it yourself',
),
scheduledAt: z
.string()
.optional()
.describe(
"Optional parameter to schedule the email. This uses natural language. Examples would be 'tomorrow at 10am' or 'in 2 hours' or 'next day at 9am PST' or 'Friday at 3pm ET'.",
),
// If sender email address is not provided, the tool requires it as an argument
...(!senderEmailAddress
? {
from: z
.string()
.email()
.nonempty()
.describe(
'Sender email address. You MUST ask the user for this parameter. Under no circumstance provide it yourself',
),
}
: {}),
...(replierEmailAddresses.length === 0
? {
replyTo: z
.string()
.email()
.array()
.optional()
.describe(
'Optional email addresses for the email readers to reply to. You MUST ask the user for this parameter. Under no circumstance provide it yourself',
),
}
: {}),
},
async ({ from, to, subject, text, html, replyTo, scheduledAt, cc, bcc }) => {
const fromEmailAddress = from ?? senderEmailAddress;
const replyToEmailAddresses = replyTo ?? replierEmailAddresses;

// Type check on from, since "from" is optionally included in the arguments schema
// This should never happen.
if (typeof fromEmailAddress !== 'string') {
throw new Error('from argument must be provided.');
}

// Similar type check for "reply-to" email addresses.
if (
typeof replyToEmailAddresses !== 'string' &&
!Array.isArray(replyToEmailAddresses)
) {
throw new Error('replyTo argument must be provided.');
}

console.error(`Debug - Sending email with from: ${fromEmailAddress}`);

// Explicitly structure the request with all parameters to ensure they're passed correctly
const emailRequest: {
to: string;
subject: string;
text: string;
from: string;
replyTo: string | string[];
html?: string;
scheduledAt?: string;
cc?: string[];
bcc?: string[];
} = {
to,
subject,
text,
from: fromEmailAddress,
replyTo: replyToEmailAddresses,
};

// Add optional parameters conditionally
if (html) {
emailRequest.html = html;
}

if (scheduledAt) {
emailRequest.scheduledAt = scheduledAt;
}

if (cc) {
emailRequest.cc = cc;
}

if (bcc) {
emailRequest.bcc = bcc;
}

console.error(`Email request: ${JSON.stringify(emailRequest)}`);

const response = await resend.emails.send(emailRequest);

if (response.error) {
throw new Error(
`Email failed to send: ${JSON.stringify(response.error)}`,
);
}

return {
content: [
{
type: 'text',
text: `Email sent successfully! ${JSON.stringify(response.data)}`,
},
],
};
},
);

server.tool(
'list-audiences',
'List all audiences from Resend. This tool is useful for getting the audience ID to help the user find the audience they want to use for other tools. If you need an audience ID, you MUST use this tool to get all available audiences and then ask the user to select the audience they want to use.',
{},
async () => {
console.error('Debug - Listing audiences');

const response = await resend.audiences.list();

if (response.error) {
throw new Error(
`Failed to list audiences: ${JSON.stringify(response.error)}`,
);
}

return {
content: [
{
type: 'text',
text: `Audiences found: ${JSON.stringify(response.data)}`,
},
],
};
},
);
addAudienceTools(server, resend);
addBroadcastTools(server, resend, {
senderEmailAddress,
replierEmailAddresses,
});
addContactTools(server, resend);
addEmailTools(server, resend, { senderEmailAddress, replierEmailAddresses });

async function main() {
const transport = new StdioServerTransport();
Expand Down
Loading