Email Sending MCP
by resend
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { Resend } from "resend";
import minimist from "minimist";
// Parse command line arguments
const argv = minimist(process.argv.slice(2));
// Get API key from command line argument or fall back to environment variable
const apiKey = argv.key || process.env.RESEND_API_KEY;
// Get sender email address from command line argument or fall back to environment variable
// Optional.
const senderEmailAddress = argv.sender || process.env.SENDER_EMAIL_ADDRESS;
// Get reply to email addresses from command line argument or fall back to environment variable
let replierEmailAddresses: string[] = [];
if (Array.isArray(argv["reply-to"])) {
replierEmailAddresses = argv["reply-to"];
} else if (typeof argv["reply-to"] === "string") {
replierEmailAddresses = [argv["reply-to"]];
} else if (process.env.REPLY_TO_EMAIL_ADDRESSES) {
replierEmailAddresses = process.env.REPLY_TO_EMAIL_ADDRESSES.split(",");
}
if (!apiKey) {
console.error(
"No API key provided. Please set RESEND_API_KEY environment variable or use --key argument"
);
process.exit(1);
}
const resend = new Resend(apiKey);
// Create server instance
const server = new McpServer({
name: "email-sending-service",
version: "1.0.0",
});
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)}`,
},
],
};
}
);
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Email sending service MCP Server running on stdio");
}
main().catch((error) => {
console.error("Fatal error in main():", error);
process.exit(1);
});