SMTP MCP Server
by samihalawa
- src
import nodemailer from 'nodemailer';
import { getDefaultSmtpConfig, getSmtpConfigs, SmtpServerConfig, getDefaultEmailTemplate, getEmailTemplates, EmailTemplate, logEmailActivity, EmailLogEntry } from './config.js';
import { logToFile } from "./index.js";
// Interface for email recipient
export interface EmailRecipient {
email: string;
name?: string;
}
// Interface for email data
export interface EmailData {
to: EmailRecipient | EmailRecipient[];
subject: string;
body: string;
from?: {
email: string;
name?: string;
};
cc?: EmailRecipient[];
bcc?: EmailRecipient[];
templateId?: string;
templateData?: Record<string, any>;
}
// Interface for bulk email data
export interface BulkEmailData {
recipients: EmailRecipient[];
subject: string;
body: string;
from?: {
email: string;
name?: string;
};
cc?: EmailRecipient[];
bcc?: EmailRecipient[];
templateId?: string;
templateData?: Record<string, any>;
batchSize?: number;
delayBetweenBatches?: number; // in milliseconds
}
// Rate limiting state
const rateLimitState = {
lastSendTime: 0,
messageCount: 0,
resetTime: 0
};
/**
* Create a nodemailer transport using SMTP config
*/
export async function createTransport(smtpConfigId?: string) {
let smtpConfig: SmtpServerConfig;
if (smtpConfigId) {
const configs = await getSmtpConfigs();
const config = configs.find(c => c.id === smtpConfigId);
if (!config) {
throw new Error(`SMTP configuration with ID ${smtpConfigId} not found`);
}
smtpConfig = config;
} else {
smtpConfig = await getDefaultSmtpConfig();
}
return nodemailer.createTransport({
host: smtpConfig.host,
port: smtpConfig.port,
secure: smtpConfig.secure,
auth: {
user: smtpConfig.auth.user,
pass: smtpConfig.auth.pass
}
});
}
/**
* Replace template variables with actual values
*/
function processTemplate(template: string, data: Record<string, any> = {}): string {
return template.replace(/\{\{([^}]+)\}\}/g, (match, key) => {
const value = data[key.trim()];
return value !== undefined ? value : match;
});
}
/**
* Generate email content from template
*/
async function generateEmailContent(
templateId: string | undefined,
templateData: Record<string, any> | undefined,
subject: string,
body: string
): Promise<{ subject: string; body: string }> {
// If no template ID is provided, use the provided subject and body
if (!templateId) {
return { subject, body };
}
// Get templates
const templates = await getEmailTemplates();
let template: EmailTemplate | undefined;
if (templateId === 'default') {
template = await getDefaultEmailTemplate();
} else {
template = templates.find(t => t.id === templateId);
}
// If template not found, use the provided subject and body
if (!template) {
console.warn(`Template with ID ${templateId} not found. Using provided subject and body.`);
return { subject, body };
}
// Process template
const processedSubject = processTemplate(template.subject, templateData);
const processedBody = processTemplate(template.body, templateData);
return {
subject: processedSubject,
body: processedBody
};
}
/**
* Format recipients for nodemailer
*/
function formatRecipient(recipient: EmailRecipient): string {
if (recipient.name) {
return `"${recipient.name}" <${recipient.email}>`;
}
return recipient.email;
}
/**
* Format recipients array for nodemailer
*/
function formatRecipients(recipients: EmailRecipient | EmailRecipient[]): string | string[] {
if (Array.isArray(recipients)) {
return recipients.map(formatRecipient);
}
return formatRecipient(recipients);
}
/**
* Send an email
*/
export async function sendEmail(data: EmailData, smtpConfigId?: string): Promise<{ success: boolean; message?: string }> {
try {
const transport = await createTransport(smtpConfigId);
const smtpConfig = smtpConfigId
? (await getSmtpConfigs()).find(c => c.id === smtpConfigId)
: await getDefaultSmtpConfig();
if (!smtpConfig) {
return { success: false, message: 'SMTP configuration not found' };
}
// Generate email content from template if templateId is provided
const { subject, body } = await generateEmailContent(
data.templateId,
data.templateData,
data.subject,
data.body
);
// Create mail options
const mailOptions = {
from: data.from
? (data.from.name ? `"${data.from.name}" <${data.from.email}>` : data.from.email)
: (smtpConfig.auth.user),
to: formatRecipients(data.to),
subject,
html: body,
cc: data.cc ? formatRecipients(data.cc) : undefined,
bcc: data.bcc ? formatRecipients(data.bcc) : undefined
};
// Send email
const info = await transport.sendMail(mailOptions);
// Log email activity
const recipients = Array.isArray(data.to) ? data.to : [data.to];
for (const recipient of recipients) {
const logEntry: EmailLogEntry = {
timestamp: new Date().toISOString(),
smtpConfig: smtpConfig.id,
templateId: data.templateId,
recipient: recipient.email,
subject,
success: true,
message: `Message sent: ${info.messageId}`
};
await logEmailActivity(logEntry);
}
return { success: true, message: `Message sent: ${info.messageId}` };
} catch (error) {
logToFile(`Error sending email: ${error}`);
// Log failed email activity
if (data.to) {
const recipients = Array.isArray(data.to) ? data.to : [data.to];
for (const recipient of recipients) {
const logEntry: EmailLogEntry = {
timestamp: new Date().toISOString(),
smtpConfig: smtpConfigId || 'unknown',
templateId: data.templateId,
recipient: recipient.email,
subject: data.subject,
success: false,
message: error instanceof Error ? error.message : 'Unknown error sending email'
};
await logEmailActivity(logEntry);
}
}
return {
success: false,
message: error instanceof Error ? error.message : 'Unknown error sending email'
};
}
}
/**
* Send emails in bulk to multiple recipients
*/
export async function sendBulkEmails(data: BulkEmailData, smtpConfigId?: string): Promise<{
success: boolean;
totalSent: number;
totalFailed: number;
failures?: { email: string; error: string }[];
message?: string;
}> {
try {
const { recipients, batchSize = 10, delayBetweenBatches = 1000 } = data;
if (!recipients || recipients.length === 0) {
return {
success: false,
totalSent: 0,
totalFailed: 0,
message: 'No recipients provided'
};
}
const failures: { email: string; error: string }[] = [];
let totalSent = 0;
// Process recipients in batches
for (let i = 0; i < recipients.length; i += batchSize) {
const batch = recipients.slice(i, i + batchSize);
// Send emails to the batch (one by one to allow for individual template processing)
const promises = batch.map(async (recipient) => {
try {
// Create email data for single recipient using the bulk data
const emailData: EmailData = {
to: recipient,
subject: data.subject,
body: data.body,
from: data.from,
cc: data.cc,
bcc: data.bcc,
templateId: data.templateId,
templateData: {
...data.templateData,
email: recipient.email,
name: recipient.name || ''
}
};
const result = await sendEmail(emailData, smtpConfigId);
if (result.success) {
totalSent++;
return { success: true };
} else {
failures.push({ email: recipient.email, error: result.message || 'Unknown error' });
return { success: false, error: result.message };
}
} catch (error) {
failures.push({
email: recipient.email,
error: error instanceof Error ? error.message : 'Unknown error'
});
return { success: false, error };
}
});
await Promise.all(promises);
// If not the last batch, wait before processing the next batch
if (i + batchSize < recipients.length) {
await new Promise(resolve => setTimeout(resolve, delayBetweenBatches));
}
}
return {
success: totalSent > 0,
totalSent,
totalFailed: failures.length,
failures: failures.length > 0 ? failures : undefined,
message: `Successfully sent ${totalSent} out of ${recipients.length} emails`
};
} catch (error) {
logToFile(`Error sending bulk emails: ${error}`);
return {
success: false,
totalSent: 0,
totalFailed: data.recipients.length,
message: error instanceof Error ? error.message : 'Unknown error sending bulk emails'
};
}
}