Why Build a Custom Email MCP Server?
Autonomous coding assistants like Claude Desktop, Cursor, and Windsurf achieve their greatest utility when granted access to real-world services. However, giving an LLM raw bash access to run curl commands exposes system credentials and lacks schema validation.
A custom Model Context Protocol (MCP) server exposes strictly typed, scoped tools directly into the assistant UI. In this guide, we will build a complete TypeScript MCP server implementing two tools: send_transactional_email and check_domain_dns.
1. Project Initialization & Dependencies
Initialize a clean project using modern TypeScript and install the official Anthropic MCP server package and Zod validation library.
mkdir sadasend-email-mcp && cd sadasend-email-mcp
npm init -y
npm install @modelcontextprotocol/server zod
npm install -D typescript @types/node tsx2. Complete Server Implementation (server.ts)
Here is the complete implementation utilizing StdioServerTransport for native integration with Claude Desktop and Cursor.
import { McpServer } from '@modelcontextprotocol/server';
import { StdioServerTransport } from '@modelcontextprotocol/server/stdio.js';
import { z } from 'zod';
const server = new McpServer({
name: 'sadasend-email-mcp',
version: '2.0.0',
});
// Tool 1: Scoped Outbound Email Dispatch
server.tool(
'send_transactional_email',
'Dispatches transactional email via SadaSend with edge recipient allowlist enforcement.',
{
to: z.string().email().describe('Destination email address'),
subject: z.string().min(1).max(120).describe('Email subject line'),
body: z.string().min(1).describe('Plain-text body content'),
},
async ({ to, subject, body }) => {
const apiKey = process.env.SADASEND_API_KEY;
if (!apiKey) {
return {
isError: true,
content: [{ type: 'text', text: 'Configuration Error: SADASEND_API_KEY environment variable is not set.' }],
};
}
const res = await fetch('https://api.sadasend.com/emails', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ to, subject, text: body }),
});
const data = await res.json();
if (!res.ok) {
return {
isError: true,
content: [{ type: 'text', text: `Send Refused (${res.status}): ${data.message || 'Unauthorized'}` }],
};
}
return {
content: [{ type: 'text', text: `Email successfully queued. Message ID: ${data.id}` }],
};
}
);
// Tool 2: Inspect Sending Domain SPF/DKIM Health
server.tool(
'check_domain_dns',
'Inspects SPF, DKIM, and DMARC verification status for a sending domain.',
{ domain: z.string().describe('The domain name to verify (e.g. acme.com)') },
async ({ domain }) => {
const res = await fetch(`https://api.sadasend.com/domains/${encodeURIComponent(domain)}/verify`);
const data = await res.json();
return {
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
};
}
);
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('[MCP] SadaSend Server listening on stdio');
}
main().catch(console.error);3. Connecting to Claude Desktop (claude_desktop_config.json)
Add your server configuration to ~/Library/Application Support/Claude/claude_desktop_config.json on macOS or %APPDATA%\Claude\claude_desktop_config.json on Windows:
{
"mcpServers": {
"sadasend": {
"command": "npx",
"args": ["-y", "tsx", "/ABSOLUTE/PATH/TO/server.ts"],
"env": {
"SADASEND_API_KEY": "sada_live_sk_your_key_here"
}
}
}
}Building AI agents that send email?
Scoped API keys, per-key recipient allowlists, approval mode and a hosted MCP server with ten tools — on the free plan, without a card.