Replaces direct Poste.io SMTP (mail.squaremcp.com:30587, port 25 blocked) with Azure Communication Services relay (smtp.azurecomm.net:587). All sqcp_* accounts share a single ACS credential; FROM addresses unchanged. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
191 lines
6.5 KiB
TypeScript
191 lines
6.5 KiB
TypeScript
import nodemailer from 'nodemailer';
|
|
import type { Account, EmailCtx } from './imap.js';
|
|
import type { EmailCredentials } from './multitenancy/credential-store.js';
|
|
|
|
const FETCHERPAY_SMTP_HOST = process.env['FETCHERPAY_SMTP_HOST'] ?? 'mail.fetcherpay.com';
|
|
const FETCHERPAY_SMTP_PORT = parseInt(process.env['FETCHERPAY_SMTP_PORT'] ?? '30587');
|
|
|
|
const SQCP_SMTP_HOST = process.env['SQCP_SMTP_HOST'] ?? 'smtp.azurecomm.net';
|
|
const SQCP_SMTP_PORT = parseInt(process.env['SQCP_SMTP_PORT'] ?? '587');
|
|
const SQCP_SMTP_USER = process.env['SQCP_SMTP_USER'] ?? '';
|
|
const SQCP_SMTP_PASS = process.env['SQCP_SMTP_PASS'] ?? '';
|
|
|
|
function fetcherpaySmtpTransport(user: string, pass: string) {
|
|
return nodemailer.createTransport({
|
|
host: FETCHERPAY_SMTP_HOST,
|
|
port: FETCHERPAY_SMTP_PORT,
|
|
secure: false,
|
|
auth: { user, pass },
|
|
tls: { rejectUnauthorized: false },
|
|
});
|
|
}
|
|
|
|
function sqcpSmtpTransport() {
|
|
return nodemailer.createTransport({
|
|
host: SQCP_SMTP_HOST,
|
|
port: SQCP_SMTP_PORT,
|
|
secure: false,
|
|
auth: { user: SQCP_SMTP_USER, pass: SQCP_SMTP_PASS },
|
|
tls: { rejectUnauthorized: false },
|
|
});
|
|
}
|
|
|
|
function getEnvSmtpTransport(account: Account = 'yahoo') {
|
|
switch (account) {
|
|
case 'fetcherpay':
|
|
return fetcherpaySmtpTransport(process.env['FETCHERPAY_EMAIL']!, process.env['FETCHERPAY_PASSWORD']!);
|
|
case 'garfield':
|
|
return fetcherpaySmtpTransport(process.env['GARFIELD_EMAIL']!, process.env['GARFIELD_PASSWORD']!);
|
|
case 'sales':
|
|
return fetcherpaySmtpTransport(process.env['SALES_EMAIL']!, process.env['SALES_PASSWORD']!);
|
|
case 'leads':
|
|
return fetcherpaySmtpTransport(process.env['LEADS_EMAIL']!, process.env['LEADS_PASSWORD']!);
|
|
case 'founder':
|
|
return fetcherpaySmtpTransport(process.env['FOUNDER_EMAIL']!, process.env['FOUNDER_PASSWORD']!);
|
|
case 'sqcp_garfield':
|
|
return sqcpSmtpTransport();
|
|
case 'sqcp_info':
|
|
return sqcpSmtpTransport();
|
|
case 'sqcp_sales':
|
|
return sqcpSmtpTransport();
|
|
case 'sqcp_support':
|
|
return sqcpSmtpTransport();
|
|
case 'sqcp_founder':
|
|
return sqcpSmtpTransport();
|
|
case 'sqcp_contact':
|
|
return sqcpSmtpTransport();
|
|
case 'sqcp_admin':
|
|
return sqcpSmtpTransport();
|
|
case 'gmail':
|
|
return nodemailer.createTransport({
|
|
host: 'smtp.gmail.com',
|
|
port: 587,
|
|
secure: false,
|
|
auth: { user: process.env['GMAIL_EMAIL']!, pass: process.env['GMAIL_APP_PASSWORD']! },
|
|
});
|
|
default:
|
|
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 resolveSmtpTransport(ctx: EmailCtx) {
|
|
if (typeof ctx === 'object') {
|
|
return nodemailer.createTransport({
|
|
host: ctx.smtpHost ?? ctx.host,
|
|
port: ctx.smtpPort ?? 587,
|
|
secure: false,
|
|
auth: { user: ctx.user, pass: ctx.password },
|
|
tls: { rejectUnauthorized: false },
|
|
});
|
|
}
|
|
return getEnvSmtpTransport(ctx);
|
|
}
|
|
|
|
function resolveSenderEmail(ctx: EmailCtx): string {
|
|
if (typeof ctx === 'object') return ctx.user;
|
|
const emailMap: Record<Account, string> = {
|
|
yahoo: process.env['YAHOO_EMAIL'] ?? '',
|
|
fetcherpay: process.env['FETCHERPAY_EMAIL'] ?? '',
|
|
garfield: process.env['GARFIELD_EMAIL'] ?? '',
|
|
sales: process.env['SALES_EMAIL'] ?? '',
|
|
leads: process.env['LEADS_EMAIL'] ?? '',
|
|
founder: process.env['FOUNDER_EMAIL'] ?? '',
|
|
gmail: process.env['GMAIL_EMAIL'] ?? '',
|
|
sqcp_garfield: process.env['SQCP_GARFIELD_EMAIL'] ?? '',
|
|
sqcp_info: process.env['SQCP_INFO_EMAIL'] ?? '',
|
|
sqcp_sales: process.env['SQCP_SALES_EMAIL'] ?? '',
|
|
sqcp_support: process.env['SQCP_SUPPORT_EMAIL'] ?? '',
|
|
sqcp_founder: process.env['SQCP_FOUNDER_EMAIL'] ?? '',
|
|
sqcp_contact: process.env['SQCP_CONTACT_EMAIL'] ?? '',
|
|
sqcp_admin: process.env['SQCP_ADMIN_EMAIL'] ?? '',
|
|
};
|
|
return emailMap[ctx] ?? '';
|
|
}
|
|
|
|
export async function sendEmail(
|
|
to: string,
|
|
subject: string,
|
|
body: string,
|
|
ctx: EmailCtx = 'yahoo',
|
|
): Promise<string> {
|
|
const transporter = resolveSmtpTransport(ctx);
|
|
const info = await transporter.sendMail({
|
|
from: resolveSenderEmail(ctx),
|
|
to,
|
|
subject,
|
|
text: body,
|
|
});
|
|
return info.messageId;
|
|
}
|
|
|
|
export async function createDraft(
|
|
to: string,
|
|
subject: string,
|
|
body: string,
|
|
ctx: EmailCtx = 'yahoo',
|
|
): Promise<string> {
|
|
const { ImapFlow } = await import('imapflow');
|
|
|
|
let imapConfig: any;
|
|
if (typeof ctx === 'object') {
|
|
imapConfig = {
|
|
host: ctx.host,
|
|
port: ctx.port,
|
|
secure: ctx.port === 993,
|
|
auth: { user: ctx.user, pass: ctx.password },
|
|
tls: { rejectUnauthorized: false },
|
|
};
|
|
} else {
|
|
const fetcherpayImapBase = {
|
|
host: process.env['FETCHERPAY_IMAP_HOST'] ?? 'mail.fetcherpay.com',
|
|
port: parseInt(process.env['FETCHERPAY_IMAP_PORT'] ?? '30993'),
|
|
secure: true,
|
|
tls: { rejectUnauthorized: false },
|
|
};
|
|
const fetcherpayAccounts: Partial<Record<Account, { user: string; pass: string }>> = {
|
|
fetcherpay: { user: process.env['FETCHERPAY_EMAIL']!, pass: process.env['FETCHERPAY_PASSWORD']! },
|
|
garfield: { user: process.env['GARFIELD_EMAIL']!, pass: process.env['GARFIELD_PASSWORD']! },
|
|
sales: { user: process.env['SALES_EMAIL']!, pass: process.env['SALES_PASSWORD']! },
|
|
leads: { user: process.env['LEADS_EMAIL']!, pass: process.env['LEADS_PASSWORD']! },
|
|
founder: { user: process.env['FOUNDER_EMAIL']!, pass: process.env['FOUNDER_PASSWORD']! },
|
|
};
|
|
if (fetcherpayAccounts[ctx]) {
|
|
imapConfig = { ...fetcherpayImapBase, auth: fetcherpayAccounts[ctx]! };
|
|
} else if (ctx === 'gmail') {
|
|
imapConfig = {
|
|
host: 'imap.gmail.com', port: 993, secure: true,
|
|
auth: { user: process.env['GMAIL_EMAIL']!, pass: process.env['GMAIL_APP_PASSWORD']! },
|
|
};
|
|
} else {
|
|
imapConfig = {
|
|
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 = resolveSenderEmail(ctx);
|
|
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}`;
|
|
}
|