#!/usr/bin/env node
// src/index.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
ErrorCode,
McpError
} from "@modelcontextprotocol/sdk/types.js";
var API_KEY = process.env.GETMAILER_API_KEY;
var API_URL = process.env.GETMAILER_API_URL || "https://getmailer.app";
function requireApiKey() {
if (!API_KEY) {
throw new Error(
"GETMAILER_API_KEY is not configured. Use the signup tool to create an account and get an API key, then add it to your MCP config."
);
}
}
async function apiRequest(endpoint, options = {}) {
requireApiKey();
const url = `${API_URL}${endpoint}`;
const response = await fetch(url, {
...options,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${API_KEY}`,
...options.headers
}
});
if (!response.ok) {
let errorMessage = response.statusText;
try {
const errorData = await response.json();
errorMessage = errorData.error || errorData.message || errorMessage;
} catch {
}
throw new Error(`API Error: ${errorMessage}`);
}
return response.json();
}
async function publicApiRequest(endpoint, options = {}) {
const url = `${API_URL}${endpoint}`;
const response = await fetch(url, {
...options,
headers: {
"Content-Type": "application/json",
...options.headers
}
});
if (!response.ok) {
let errorMessage = response.statusText;
try {
const errorData = await response.json();
errorMessage = errorData.error || errorData.message || errorMessage;
} catch {
}
throw new Error(`API Error: ${errorMessage}`);
}
return response.json();
}
var server = new Server(
{
name: "getmailer-mcp",
version: "1.1.0"
},
{
capabilities: {
tools: {}
}
}
);
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "signup",
description: "Create a new GetMailer account. Returns an API key that you can use to send emails. No authentication required.",
inputSchema: {
type: "object",
properties: {
email: {
type: "string",
description: "Your email address"
},
password: {
type: "string",
description: "Password (min 8 chars, must include uppercase, lowercase, and number)"
},
name: {
type: "string",
description: "Your name (optional)"
}
},
required: ["email", "password"]
}
},
{
name: "account_status",
description: "Check your account status including email verification, subscription plan, and sending limits. Use this to see if you can send emails.",
inputSchema: {
type: "object",
properties: {}
}
},
{
name: "send_email",
description: "Send a transactional email via GetMailer",
inputSchema: {
type: "object",
properties: {
from: {
type: "string",
description: "Sender email address (must be from a verified domain)"
},
to: {
type: "array",
items: { type: "string" },
description: "Recipient email address(es)"
},
subject: {
type: "string",
description: "Email subject line"
},
html: {
type: "string",
description: "HTML content of the email"
},
text: {
type: "string",
description: "Plain text content of the email"
},
cc: {
type: "array",
items: { type: "string" },
description: "CC recipients (optional)"
},
bcc: {
type: "array",
items: { type: "string" },
description: "BCC recipients (optional)"
},
replyTo: {
type: "string",
description: "Reply-to address (optional)"
},
templateId: {
type: "string",
description: "Template ID to use instead of html/text (optional)"
},
variables: {
type: "object",
description: "Template variables as key-value pairs (optional)"
}
},
required: ["from", "to", "subject"]
}
},
{
name: "list_emails",
description: "List sent emails with status information",
inputSchema: {
type: "object",
properties: {
limit: {
type: "number",
description: "Number of emails to return (default: 20)"
},
cursor: {
type: "string",
description: "Pagination cursor for next page"
}
}
}
},
{
name: "get_email",
description: "Get details of a specific email including delivery events",
inputSchema: {
type: "object",
properties: {
id: {
type: "string",
description: "Email ID"
}
},
required: ["id"]
}
},
{
name: "list_templates",
description: "List available email templates",
inputSchema: {
type: "object",
properties: {}
}
},
{
name: "create_template",
description: "Create a new email template",
inputSchema: {
type: "object",
properties: {
name: {
type: "string",
description: "Template name"
},
subject: {
type: "string",
description: "Email subject (can include {{variables}})"
},
html: {
type: "string",
description: "HTML content (can include {{variables}})"
},
text: {
type: "string",
description: "Plain text content (optional)"
}
},
required: ["name", "subject", "html"]
}
},
{
name: "list_domains",
description: "List verified sending domains",
inputSchema: {
type: "object",
properties: {}
}
},
{
name: "add_domain",
description: "Add a new sending domain (returns DNS records to configure)",
inputSchema: {
type: "object",
properties: {
domain: {
type: "string",
description: "Domain name to add (e.g., example.com)"
}
},
required: ["domain"]
}
},
{
name: "verify_domain",
description: "Check if a domain has been verified",
inputSchema: {
type: "object",
properties: {
id: {
type: "string",
description: "Domain ID"
}
},
required: ["id"]
}
},
{
name: "get_analytics",
description: "Get email analytics and statistics",
inputSchema: {
type: "object",
properties: {
type: {
type: "string",
enum: ["summary", "daily"],
description: "Type of analytics (summary or daily)"
},
days: {
type: "number",
description: "Number of days for daily stats (default: 30)"
}
}
}
},
{
name: "list_suppression",
description: "List suppressed email addresses (bounced, complained, or manually added)",
inputSchema: {
type: "object",
properties: {
limit: {
type: "number",
description: "Number of entries to return (default: 50)"
}
}
}
},
{
name: "add_to_suppression",
description: "Add email addresses to the suppression list",
inputSchema: {
type: "object",
properties: {
emails: {
type: "array",
items: { type: "string" },
description: "Email addresses to suppress"
},
reason: {
type: "string",
enum: ["MANUAL", "BOUNCE", "COMPLAINT"],
description: "Reason for suppression (default: MANUAL)"
}
},
required: ["emails"]
}
},
{
name: "create_batch",
description: "Create a batch email job to send to multiple recipients",
inputSchema: {
type: "object",
properties: {
name: {
type: "string",
description: "Batch job name"
},
from: {
type: "string",
description: "Sender email address"
},
subject: {
type: "string",
description: "Email subject (can include {{variables}})"
},
html: {
type: "string",
description: "HTML content (can include {{variables}})"
},
text: {
type: "string",
description: "Plain text content (optional)"
},
templateId: {
type: "string",
description: "Template ID to use instead of html/text (optional)"
},
recipients: {
type: "array",
items: {
type: "object",
properties: {
to: { type: "string" },
variables: { type: "object" }
},
required: ["to"]
},
description: "Array of recipients with optional per-recipient variables"
},
replyTo: {
type: "string",
description: "Reply-to address (optional)"
}
},
required: ["name", "from", "recipients"]
}
},
{
name: "list_batches",
description: "List batch email jobs",
inputSchema: {
type: "object",
properties: {}
}
},
{
name: "get_batch",
description: "Get batch job status and progress",
inputSchema: {
type: "object",
properties: {
id: {
type: "string",
description: "Batch ID"
}
},
required: ["id"]
}
},
{
name: "list_inbox",
description: "List received/inbound emails in your inbox",
inputSchema: {
type: "object",
properties: {
limit: {
type: "number",
description: "Number of emails to return (default: 20)"
},
cursor: {
type: "string",
description: "Pagination cursor for next page"
},
status: {
type: "string",
enum: ["unread", "read", "all"],
description: "Filter by read status (default: all)"
}
}
}
},
{
name: "get_inbox_email",
description: "Get details of a specific inbound email including full content",
inputSchema: {
type: "object",
properties: {
id: {
type: "string",
description: "Inbox email ID"
}
},
required: ["id"]
}
},
{
name: "mark_inbox_read",
description: "Mark one or more inbox emails as read or unread",
inputSchema: {
type: "object",
properties: {
ids: {
type: "array",
items: { type: "string" },
description: "Email IDs to mark"
},
read: {
type: "boolean",
description: "Set to true to mark as read, false to mark as unread (default: true)"
}
},
required: ["ids"]
}
},
{
name: "delete_inbox_email",
description: "Delete one or more emails from the inbox",
inputSchema: {
type: "object",
properties: {
ids: {
type: "array",
items: { type: "string" },
description: "Email IDs to delete"
}
},
required: ["ids"]
}
}
]
}));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case "signup": {
const result = await publicApiRequest("/api/public/signup", {
method: "POST",
body: JSON.stringify({
email: args?.email,
password: args?.password,
name: args?.name
})
});
const configInstructions = `
To start using GetMailer, add your API key to your MCP configuration:
Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"getmailer": {
"command": "npx",
"args": ["getmailer-mcp"],
"env": {
"GETMAILER_API_KEY": "${result.apiKey}"
}
}
}
}
Then restart your MCP client to apply the configuration.
`;
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2) + "\n\n--- Configuration Instructions ---\n" + configInstructions
}
]
};
}
case "account_status": {
const result = await apiRequest("/api/account/status");
let statusMessage = `Account Status for ${result.account.email}
`;
statusMessage += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
`;
if (result.account.emailVerified) {
statusMessage += `\u2705 Email: Verified
`;
} else {
statusMessage += `\u274C Email: NOT VERIFIED
`;
statusMessage += ` \u2192 Check your inbox for the verification link!
`;
}
if (result.subscription) {
statusMessage += `\u{1F4E6} Plan: ${result.subscription.plan}
`;
statusMessage += `\u{1F4E7} Emails remaining: ${result.subscription.emailsRemaining}
`;
}
statusMessage += `\u{1F310} Verified domains: ${result.domains.verifiedCount}
`;
statusMessage += `
`;
if (result.canSendEmails) {
statusMessage += `\u2705 You CAN send emails
`;
} else {
statusMessage += `\u274C You CANNOT send emails yet
`;
if (result.warnings.length > 0) {
statusMessage += `
Required actions:
`;
result.warnings.forEach((w) => {
statusMessage += ` \u2022 ${w.message}
`;
});
}
}
return {
content: [
{
type: "text",
text: statusMessage
}
]
};
}
case "send_email": {
const result = await apiRequest(
"/api/emails",
{
method: "POST",
body: JSON.stringify(args)
}
);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2)
}
]
};
}
case "list_emails": {
const params = new URLSearchParams();
if (args?.limit) params.set("limit", String(args.limit));
if (args?.cursor) params.set("cursor", String(args.cursor));
const result = await apiRequest(`/api/emails?${params}`);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
};
}
case "get_email": {
const result = await apiRequest(`/api/emails/${args?.id}`);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
};
}
case "list_templates": {
const result = await apiRequest("/api/templates");
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
};
}
case "create_template": {
const result = await apiRequest("/api/templates", {
method: "POST",
body: JSON.stringify(args)
});
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
};
}
case "list_domains": {
const result = await apiRequest("/api/domains");
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
};
}
case "add_domain": {
const result = await apiRequest("/api/domains", {
method: "POST",
body: JSON.stringify({ domain: args?.domain })
});
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
};
}
case "verify_domain": {
const result = await apiRequest("/api/domains/verify", {
method: "POST",
body: JSON.stringify({ domainId: args?.id })
});
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
};
}
case "get_analytics": {
const type = args?.type || "summary";
const days = args?.days || 30;
const result = await apiRequest(`/api/analytics?type=${type}&days=${days}`);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
};
}
case "list_suppression": {
const params = new URLSearchParams();
if (args?.limit) params.set("limit", String(args.limit));
const result = await apiRequest(`/api/suppression?${params}`);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
};
}
case "add_to_suppression": {
const result = await apiRequest("/api/suppression", {
method: "POST",
body: JSON.stringify({
emails: args?.emails,
reason: args?.reason || "MANUAL"
})
});
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
};
}
case "create_batch": {
const result = await apiRequest("/api/batch", {
method: "POST",
body: JSON.stringify(args)
});
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
};
}
case "list_batches": {
const result = await apiRequest("/api/batch");
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
};
}
case "get_batch": {
const result = await apiRequest(`/api/batch/${args?.id}`);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
};
}
case "list_inbox": {
const params = new URLSearchParams();
if (args?.limit) params.set("limit", String(args.limit));
if (args?.cursor) params.set("cursor", String(args.cursor));
if (args?.status) params.set("status", String(args.status));
const result = await apiRequest(`/api/inbox?${params}`);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
};
}
case "get_inbox_email": {
const result = await apiRequest(`/api/inbox/${args?.id}`);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
};
}
case "mark_inbox_read": {
const result = await apiRequest("/api/inbox/mark-read", {
method: "POST",
body: JSON.stringify({
ids: args?.ids,
read: args?.read !== false
})
});
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
};
}
case "delete_inbox_email": {
const result = await apiRequest("/api/inbox", {
method: "DELETE",
body: JSON.stringify({
ids: args?.ids
})
});
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
};
}
default:
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
}
} catch (error) {
const message = error instanceof Error ? error.message : "Unknown error";
return {
content: [{ type: "text", text: `Error: ${message}` }],
isError: true
};
}
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("GetMailer MCP server running");
}
main().catch((error) => {
console.error("Server error:", error);
process.exit(1);
});