Resend MCP Server
Enables AI agents to manage email newsletters and contacts via Resend, including sending broadcasts to segments, managing subscribers in bulk, scheduling campaigns, and tracking delivery status using human-friendly identifiers.
README
Resend MCP Server
Streamable HTTP MCP server for email and newsletter management via Resend.
Author: overment
[!WARNING] This server gives an AI agent access to your email sending capabilities. Language models can make mistakes, misinterpret instructions, or send unintended emails. Always:
- MAKE SURE that the client you use with this MCP requires you to REVIEW and CONFIRM the action before use.
- Test with a small segment first
- Broadcasts are always scheduled (minimum 5 minutes ahead) so you can cancel if needed (default is 5 minutes)
- Set
RESEND_DEFAULT_FROMto a verified sender address
Motivation
Traditional email APIs require knowing exact IDs, segment structures, and API quirks. This server is designed so AI agents can:
- Send newsletters — broadcast to segments with proper formatting
- Manage subscribers — add, update, remove contacts in bulk
- Use human identifiers — segment names and emails, not UUIDs
- Handle formatting — automatic multipart emails (HTML + plain text)
- Track campaigns — view delivery status, cancel scheduled sends
The result: an agent that can reliably manage your newsletter without you touching the Resend dashboard.
Features
- ✅ Contacts — Add, update, remove, search contacts (bulk-capable, up to 100 per call)
- ✅ Segments — Create, list, delete segments and manage membership
- ✅ Send — Individual emails or broadcast to segments
- ✅ Campaigns — View history, check delivery status, cancel scheduled
- ✅ Templates — List available templates with variables
- ✅ Subscriptions — Topic opt-in/opt-out management
- ✅ Multipart Emails — Auto-generates HTML + plain text for best deliverability
- ✅ Dual Runtime — Node.js/Bun or Cloudflare Workers
Design Principles
- LLM-friendly: Task-oriented tools, not 1:1 API mirrors
- Bulk-first: Contact operations accept arrays by default
- Human identifiers: Use emails and segment names, not UUIDs
- Multipart by default: Plain text auto-generates HTML for proper rendering
- Clear feedback: Results include summaries and next-step suggestions
Server Instructions (What the Model Sees)
Use these tools to manage email contacts, segments, and send emails via Resend.
WORKFLOW:
1. find_contacts or segments(list) → discover existing data
2. upsert_contacts → add/update subscribers
3. send → individual email or broadcast to segment
4. campaigns(status) → track delivery
FORMATTING (for body):
- Use \n\n between paragraphs
- Use \n between list items
- Plain text is auto-converted to multipart (HTML + text) for proper rendering
IMPORTANT:
- Segment names are case-insensitive
- Templates must be published in Resend dashboard to use
- Broadcasts use segment name, not ID
Quick Start
Prerequisites
- Bun or Node.js 20+
- Resend Account with verified domain
1. Install
cd resend-mcp
bun install
cp env.example .env
2. Configure
Get your Resend API key from resend.com/api-keys.
Edit .env:
PORT=3000
AUTH_ENABLED=true
AUTH_STRATEGY=bearer
# Generate with: openssl rand -hex 32
BEARER_TOKEN=your-random-auth-token
# Resend credentials
RESEND_API_KEY=re_your_resend_api_key
RESEND_DEFAULT_FROM=newsletter@yourdomain.com
3. Run
bun dev
# MCP: http://127.0.0.1:3000/mcp
4. Connect Client
Alice App:
- URL:
http://127.0.0.1:3000/mcp - Type:
streamable-http - Header:
Authorization: Bearer <your-BEARER_TOKEN>
Claude Desktop / Cursor:
{
"mcpServers": {
"resend": {
"command": "npx",
"args": ["mcp-remote", "http://localhost:3000/mcp", "--transport", "http-only"],
"env": { "NO_PROXY": "127.0.0.1,localhost" }
}
}
}
Tools
upsert_contacts
Add or update contacts in bulk. Creates if email doesn't exist, updates if it does.
// Input
{
contacts: [
{ email: "john@example.com", first_name: "John", last_name: "Doe" },
{ email: "jane@example.com", properties: { plan: "pro", company: "Acme" } }
],
segments?: ["Newsletter", "Premium"], // Add to these segments
unsubscribed?: false // Set subscription status
}
// Output
{
results: [{ email, ok, id, action: "created" | "updated", error? }],
summary: { created: 1, updated: 1, failed: 0 }
}
remove_contacts
Permanently delete contacts from your list.
// Input
{ emails: ["user@example.com", "other@example.com"] }
// Output
{ results: [{ email, ok, error? }], summary: { deleted: 2, failed: 0 } }
find_contacts
Search and filter contacts with pagination.
// Input
{
segment?: "Newsletter", // Filter by segment name
email?: "user@example.com", // Find specific contact
unsubscribed?: false, // Filter by status
limit?: 50, // Max 100
cursor?: "..." // From previous response
}
// Output
{
items: [{ id, email, first_name, last_name, unsubscribed, created_at, properties }],
has_more: boolean,
cursor?: string
}
segments
Manage segments and their members.
// List all segments
{ action: "list" }
→ { items: [{ id, name, contact_count, created_at }] }
// Create segment
{ action: "create", name: "VIP Customers" }
→ { id, name, ok: true }
// Delete segment (contacts remain in system)
{ action: "delete", name: "Old Segment" }
// Add contacts to segment
{ action: "add_contacts", name: "Newsletter", contacts: ["a@x.com", "b@x.com"] }
// Remove contacts from segment
{ action: "remove_contacts", name: "Newsletter", contacts: ["a@x.com"] }
send
Send individual emails or broadcast to segments.
// Individual email
{
to: "user@example.com", // or array up to 50
subject: "Welcome!",
body: "Hello {{{FIRST_NAME}}}!\n\nWelcome to our newsletter.",
from_name?: "Alice Newsletter",
reply_to?: "support@example.com",
schedule_for?: "2024-12-25T10:00:00Z"
}
// Broadcast to segment
{
segment: "Newsletter", // Segment name (case-insensitive)
subject: "Weekly Update",
name?: "Week 42 Newsletter", // Dashboard display name
body: "HEADLINE\n\nFirst paragraph.\n\nSecond paragraph.",
schedule_for?: "in 2 hours" // Natural language supported
}
// Using template
{
to: "user@example.com",
template: "welcome-email", // Template alias or ID
variables: { CTA_TEXT: "Get Started", DISCOUNT: "20%" }
}
Formatting tips:
- Use
\n\nbetween paragraphs - Use
\nbetween list items - Plain text is auto-converted to HTML with proper line breaks
- Personalization:
{{{FIRST_NAME}}}or{{{FIRST_NAME|Friend}}}
campaigns
View and manage broadcast campaigns.
// List recent campaigns
{ action: "list", limit?: 20 }
→ { items: [{ id, name, subject, segment_name, status, sent_at, recipients_count }] }
// Get detailed status
{ action: "status", campaign_id: "bc123..." }
→ { id, status, sent_count, delivered_count, opened_count, failed_count }
// Cancel scheduled campaign
{ action: "cancel", campaign_id: "bc123..." }
→ { id, cancelled: true }
subscriptions
Manage topic preferences for contacts.
// Subscribe to topic
{ emails: "user@example.com", action: "subscribe", topic: "Product Updates" }
// Unsubscribe from topic
{ emails: ["a@x.com", "b@x.com"], action: "unsubscribe", topic: "Newsletter" }
// Global unsubscribe (from all emails)
{ emails: "user@example.com", action: "unsubscribe_all" }
templates
List available email templates.
// Input
{ limit?: 50, cursor?: "..." }
// Output
{
items: [{ id, alias, name, subject, from, variables: [{ key, type, fallback }] }],
has_more: boolean
}
Examples
1. Import subscribers and send welcome email
// Add contacts to Newsletter segment
{
"name": "upsert_contacts",
"arguments": {
"contacts": [
{ "email": "alice@example.com", "first_name": "Alice" },
{ "email": "bob@example.com", "first_name": "Bob" }
],
"segments": ["Newsletter"]
}
}
Response:
Added 2 contact(s). Created: 2, Updated: 0, Failed: 0.
Next: Use 'find_contacts' to verify or 'send' to email them.
2. Send a broadcast newsletter
{
"name": "send",
"arguments": {
"segment": "Newsletter",
"subject": "This Week in AI",
"name": "Week 42 Newsletter",
"body": "HIGHLIGHTS\n\nClaude 4 was released this week with impressive new capabilities.\n\nKEY FEATURES\n\n• Extended context window\n• Improved reasoning\n• Better code generation\n\nREAD MORE\n\nCheck out the full announcement on our blog."
}
}
Response:
Broadcast to segment "Newsletter" queued for sending (Campaign ID: abc123).
Use 'campaigns' tool to track delivery.
3. Check campaign delivery
{
"name": "campaigns",
"arguments": { "action": "status", "campaign_id": "abc123" }
}
Response:
Campaign abc123: sent
- Sent: 1,234
- Delivered: 1,198
- Opened: 456 (38%)
- Failed: 36
4. Unsubscribe a user
{
"name": "subscriptions",
"arguments": {
"emails": "unhappy@example.com",
"action": "unsubscribe_all"
}
}
Response:
Updated 1 contact(s). Success: 1, Failed: 0.
Authentication Flow
┌─────────────────────────────────────────────────────────────────┐
│ Client (Alice App, Claude Desktop) │
│ │ │
│ │ Authorization: Bearer <BEARER_TOKEN> │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ MCP Server (Node.js / Cloudflare Worker) ││
│ │ ││
│ │ 1. Validate BEARER_TOKEN (client auth) ││
│ │ 2. Use RESEND_API_KEY internally ││
│ │ ││
│ │ RESEND_API_KEY ──────────► Resend API ││
│ │ RESEND_DEFAULT_FROM ─────► (api.resend.com) ││
│ └─────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘
Key points:
BEARER_TOKEN: Random token you generate — authenticates MCP clientsRESEND_API_KEY: Your Resend API key — never exposed to clientsRESEND_DEFAULT_FROM: Verified sender address — required
Deployment Options
Local Development (Node.js/Bun)
bun dev
# Endpoint: http://127.0.0.1:3000/mcp
Cloudflare Worker (Local Dev)
# Create .dev.vars with secrets
echo "BEARER_TOKEN=your_token" >> .dev.vars
echo "RESEND_API_KEY=re_xxx" >> .dev.vars
echo "RESEND_DEFAULT_FROM=newsletter@yourdomain.com" >> .dev.vars
bun x wrangler dev --local | cat
# Endpoint: http://127.0.0.1:8787/mcp
Cloudflare Worker (Production)
# Set secrets
bun x wrangler secret put BEARER_TOKEN
bun x wrangler secret put RESEND_API_KEY
bun x wrangler secret put RESEND_DEFAULT_FROM
# Deploy
bun x wrangler deploy
# Endpoint: https://<worker>.<account>.workers.dev/mcp
HTTP Endpoints
| Endpoint | Method | Purpose |
|---|---|---|
/mcp |
POST | MCP JSON-RPC 2.0 |
/mcp |
GET | SSE stream (for notifications) |
/health |
GET | Health check |
Environment Variables
| Variable | Required | Description |
|---|---|---|
RESEND_API_KEY |
✓ | Resend API key from resend.com/api-keys |
RESEND_DEFAULT_FROM |
✓ | Verified sender address (e.g., newsletter@yourdomain.com) |
BEARER_TOKEN |
✓ | Auth token for MCP clients (generate with openssl rand -hex 32) |
PORT |
Server port (default: 3000) | |
HOST |
Server host (default: 127.0.0.1) | |
AUTH_ENABLED |
Enable auth (default: true) | |
AUTH_STRATEGY |
Auth strategy (default: bearer) | |
LOG_LEVEL |
debug, info, warn, error (default: info) |
Architecture
src/
├── index.ts # Node.js entry point
├── worker.ts # Cloudflare Workers entry point
├── config/
│ ├── env.ts # Environment parsing
│ └── metadata.ts # Server & tool descriptions
├── core/
│ └── mcp.ts # McpServer builder
├── shared/
│ └── tools/
│ └── resend/ # Tool definitions
│ ├── upsert-contacts.ts
│ ├── remove-contacts.ts
│ ├── find-contacts.ts
│ ├── segments.ts
│ ├── send.ts
│ ├── campaigns.ts
│ ├── subscriptions.ts
│ └── templates.ts
├── services/
│ └── resend/
│ └── client.ts # Resend API client
├── schemas/
│ └── outputs.ts # Zod output schemas
└── http/
├── app.ts # Hono HTTP app
└── routes/
└── mcp.ts # MCP endpoint handler
Development
bun dev # Start with hot reload (note: sessions clear on reload)
bun start # Production mode (stable sessions)
bun run typecheck # TypeScript check
bun run lint # Lint code
bun run build # Production build
Testing with MCP Inspector:
bunx @modelcontextprotocol/inspector
# Connect to: http://localhost:3000/mcp
# Add header: Authorization: Bearer <your-BEARER_TOKEN>
Troubleshooting
| Issue | Solution |
|---|---|
| 401 Unauthorized | Check BEARER_TOKEN matches in server and client |
| "RESEND_API_KEY not configured" | Set RESEND_API_KEY in .env or secrets |
| "RESEND_DEFAULT_FROM not configured" | Set RESEND_DEFAULT_FROM to a verified sender |
| "Segment not found" | Use segments(action='list') to see available segments |
| "Template not found" | Ensure template is published in Resend dashboard |
| No newlines in email | Already fixed — emails are multipart (HTML + text) |
| Stale session after restart | Disconnect and reconnect client (hot reload clears sessions) |
| Rate limit (429) | Resend default is 2 req/s. Wait for retry-after header |
| Tools not showing | Reconnect client — session may be stale |
License
MIT
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。