Initial commit: Hermes MCP - Yahoo Mail server for Claude AI

- Multi-account email support (Yahoo + self-hosted IMAP/SMTP)
- MCP tools: get_profile, search_messages, read_message, list_folders, create_draft, send_email
- Streamable HTTP transport with session recovery
- Docker + Kubernetes deployment configuration
- Express server with health endpoint

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
garfieldheron
2026-03-05 13:14:30 -05:00
commit 356b6b9f55
14 changed files with 1009 additions and 0 deletions

97
src/smtp.ts Normal file
View File

@@ -0,0 +1,97 @@
import nodemailer from 'nodemailer';
import type { Account } from './imap.js';
function getSmtpTransport(account: Account = 'yahoo') {
if (account === 'fetcherpay') {
return nodemailer.createTransport({
host: process.env['FETCHERPAY_SMTP_HOST'] ?? 'mail.fetcherpay.com',
port: parseInt(process.env['FETCHERPAY_SMTP_PORT'] ?? '30587'),
secure: false, // STARTTLS
auth: {
user: process.env['FETCHERPAY_EMAIL']!,
pass: process.env['FETCHERPAY_PASSWORD']!,
},
tls: { rejectUnauthorized: false }, // self-signed cert
});
}
return nodemailer.createTransport({
host: 'smtp.mail.yahoo.com',
port: 587,
secure: false,
auth: {
user: process.env['YAHOO_EMAIL']!,
pass: process.env['YAHOO_APP_PASSWORD']!,
},
});
}
function getSenderEmail(account: Account = 'yahoo'): string {
return account === 'fetcherpay'
? process.env['FETCHERPAY_EMAIL']!
: process.env['YAHOO_EMAIL']!;
}
export async function sendEmail(
to: string,
subject: string,
body: string,
account: Account = 'yahoo',
): Promise<string> {
const transporter = getSmtpTransport(account);
const info = await transporter.sendMail({
from: getSenderEmail(account),
to,
subject,
text: body,
});
return info.messageId;
}
export async function createDraft(
to: string,
subject: string,
body: string,
account: Account = 'yahoo',
): Promise<string> {
const { ImapFlow } = await import('imapflow');
const imapConfig = account === 'fetcherpay'
? {
host: process.env['FETCHERPAY_IMAP_HOST'] ?? 'mail.fetcherpay.com',
port: parseInt(process.env['FETCHERPAY_IMAP_PORT'] ?? '30993'),
secure: true,
auth: {
user: process.env['FETCHERPAY_EMAIL']!,
pass: process.env['FETCHERPAY_PASSWORD']!,
},
tls: { rejectUnauthorized: false },
}
: {
host: 'imap.mail.yahoo.com',
port: 993,
secure: true,
auth: {
user: process.env['YAHOO_EMAIL']!,
pass: process.env['YAHOO_APP_PASSWORD']!,
},
};
const client = new ImapFlow(imapConfig);
await client.connect();
const from = getSenderEmail(account);
const rawMessage = [
`From: ${from}`,
`To: ${to}`,
`Subject: ${subject}`,
`MIME-Version: 1.0`,
`Content-Type: text/plain; charset=UTF-8`,
``,
body,
].join('\r\n');
await client.append('Drafts', Buffer.from(rawMessage), ['\\Draft', '\\Seen']);
await client.logout();
return `Draft created: "${subject}" to ${to}`;
}