mailpilot sending

This commit is contained in:
southseact-3d
2026-02-12 19:35:55 +00:00
parent 6c09b70317
commit df3a8cdf43
15 changed files with 4419 additions and 136 deletions

View File

@@ -12,7 +12,7 @@ const archiver = require('archiver');
const AdmZip = require('adm-zip');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const nodemailer = require('nodemailer');
const nodemailer = null;
const PDFDocument = require('pdfkit');
const security = require('./security');
const { createExternalWpTester, getExternalTestingConfig } = require('./external-wp-testing');
@@ -397,19 +397,8 @@ const oauthStateStore = new Map();
const OAUTH_USER_AGENT = process.env.OAUTH_USER_AGENT || 'shopify-ai-app-builder';
const EMAIL_VERIFICATION_TTL_MS = Number(process.env.EMAIL_VERIFICATION_TTL_MS || 24 * 60 * 60 * 1000); // 24h
const PASSWORD_RESET_TTL_MS = Number(process.env.PASSWORD_RESET_TTL_MS || 60 * 60 * 1000); // 1h
const SMTP_HOST = process.env.SMTP_HOST || '';
const SMTP_PORT = Number(process.env.SMTP_PORT || 587);
const SMTP_SECURE = process.env.SMTP_SECURE === '1' || String(process.env.SMTP_SECURE || '').toLowerCase() === 'true';
const SMTP_USER = process.env.SMTP_USER || process.env.SMTP_USERNAME || '';
const SMTP_PASS = (() => {
if (process.env.SMTP_PASS_FILE) {
try {
return fsSync.readFileSync(process.env.SMTP_PASS_FILE, 'utf8').trim();
} catch (_) { /* fall back to env */ }
}
return process.env.SMTP_PASS || process.env.SMTP_PASSWORD || '';
})();
const SMTP_FROM = process.env.SMTP_FROM || process.env.EMAIL_FROM || '';
const MAILPILOT_URL = process.env.MAILPILOT_URL || 'https://emailmarketing.modelrailway3d.co.uk';
const MAILPILOT_TOKEN = process.env.MAILPILOT_TOKEN || '';
const POSTHOG_API_KEY = process.env.POSTHOG_API_KEY || '';
const POSTHOG_API_HOST = process.env.POSTHOG_API_HOST || 'https://app.posthog.com';
const DODO_WEBHOOK_KEY = process.env.DODO_PAYMENTS_WEBHOOK_KEY || process.env.DODO_WEBHOOK_KEY || '';
@@ -1580,28 +1569,99 @@ let usersDb = []; // In-memory user database cache
let invoicesDb = []; // In-memory invoice database cache
let mailTransport = null;
// Security Rate Limiting Data Structures
const loginAttempts = new Map(); // { email:ip: { count, windowStart, lockedUntil } }
const adminLoginAttempts = new Map(); // { ip: { count, windowStart, lockedUntil } }
const apiRateLimit = new Map(); // { userId: { requests, windowStart } }
const csrfTokens = new Map(); // { token: { userId, expiresAt } }
const affiliateSessions = new Map();
let affiliatesDb = [];
let trackingData = {
visits: [],
summary: {
totalVisits: 0,
uniqueVisitors: new Set(),
referrers: {},
pages: {},
dailyVisits: {},
conversions: { signup: 0, paid: 0 },
financials: { totalRevenue: 0, dailyRevenue: {} },
referrersToUpgrade: {},
conversionSources: {
signup: { home: 0, pricing: 0, other: 0 },
paid: { home: 0, pricing: 0, other: 0 }
function summarizeMailConfig() {
return {
configured: !!MAILPILOT_TOKEN,
url: MAILPILOT_URL,
tokenSet: !!MAILPILOT_TOKEN,
};
}
function ensureMailTransport() {
if (mailTransport) return mailTransport;
if (!MAILPILOT_TOKEN) {
console.log('');
console.log('┌─────────────────────────────────────────────────────────────┐');
console.log('│ 📧 MAILPILOT NOT CONFIGURED │');
console.log('├─────────────────────────────────────────────────────────────┤');
console.log('│ Password reset and verification emails will NOT be sent. │');
console.log('│ To enable real emails, configure MailPilot in .env file: │');
console.log('│ │');
console.log('│ MAILPILOT_URL=https://emailmarketing.modelrailway3d.co.uk│');
console.log('│ MAILPILOT_TOKEN=tx_abc123def456... │');
console.log('│ │');
console.log('│ 💡 Tip: Emails will be logged below when triggered │');
console.log('└─────────────────────────────────────────────────────────────┘');
console.log('');
mailTransport = {
sendMail: async (payload) => {
console.log('');
console.log('┌─────────────────────────────────────────────────────────────┐');
console.log('│ 📧 [MOCK EMAIL - Not Sent] │');
console.log('├─────────────────────────────────────────────────────────────┤');
console.log(`│ To: ${payload.to}`);
console.log(`│ Subject: ${payload.subject}`);
console.log('│ Body preview:');
const preview = (payload.text || payload.html || '').slice(0, 200).replace(/\n/g, ' ');
console.log(`${preview}...`);
console.log('│ │');
console.log('│ Configure MAILPILOT_TOKEN in .env to send real emails │');
console.log('└─────────────────────────────────────────────────────────────┘');
console.log('');
return { messageId: 'mock-' + Date.now() };
},
verify: (cb) => cb(null, true)
};
return mailTransport;
}
log('MailPilot configured', summarizeMailConfig());
mailTransport = { url: MAILPILOT_URL, token: MAILPILOT_TOKEN };
return mailTransport;
}
async function sendEmail({ to, subject, text, html }) {
try {
const transport = ensureMailTransport();
if (!MAILPILOT_TOKEN) {
await transport.sendMail({ to, subject, text, html });
return { messageId: 'mock-' + Date.now() };
}
const payload = {
to,
subject,
html: html || text || '',
};
log('sending email via MailPilot', { to, subject });
const response = await fetch(`${MAILPILOT_URL}/api/transactional-emails/trigger/${MAILPILOT_TOKEN}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`MailPilot API error: ${response.status} - ${errorText}`);
}
const result = await response.json();
log('email sent successfully via MailPilot', { to, status: result.status });
return { messageId: result.status || 'sent' };
} catch (err) {
log('FAILED TO SEND EMAIL', {
to,
subject,
error: String(err),
hint: 'Check MAILPILOT_URL and MAILPILOT_TOKEN configuration in .env file'
});
throw err;
}
}
},
// Enhanced Analytics Tracking
userAnalytics: {
@@ -2030,12 +2090,9 @@ function sanitizeGitMessage(message) {
function summarizeMailConfig() {
return {
hostConfigured: !!SMTP_HOST,
portConfigured: Number.isFinite(SMTP_PORT) && SMTP_PORT > 0,
secure: SMTP_SECURE,
hasUser: !!SMTP_USER,
hasPass: !!SMTP_PASS,
fromConfigured: !!SMTP_FROM,
configured: !!MAILPILOT_TOKEN,
url: MAILPILOT_URL,
tokenSet: !!MAILPILOT_TOKEN,
};
}
@@ -3488,26 +3545,21 @@ async function trackAffiliateCommission(user, plan) {
function ensureMailTransport() {
if (mailTransport) return mailTransport;
const invalidPort = !Number.isFinite(SMTP_PORT) || SMTP_PORT <= 0;
if (!SMTP_HOST || invalidPort || !SMTP_FROM || !SMTP_USER || !SMTP_PASS) {
log('⚠️ SMTP configuration is incomplete. Emails will be logged to CONSOLE only (not sent).', summarizeMailConfig());
if (!MAILPILOT_TOKEN) {
console.log('');
console.log('┌─────────────────────────────────────────────────────────────┐');
console.log('│ 📧 EMAIL/SMTP NOT CONFIGURED │');
console.log('│ 📧 MAILPILOT NOT CONFIGURED │');
console.log('├─────────────────────────────────────────────────────────────┤');
console.log('│ Password reset and verification emails will NOT be sent. │');
console.log('│ To enable real emails, configure SMTP in .env file: │');
console.log('│ To enable real emails, configure MailPilot in .env file: │');
console.log('│ │');
console.log('│ SMTP_HOST=smtp.gmail.com (or your SMTP server) │');
console.log('│ SMTP_PORT=587 │');
console.log('│ SMTP_USER=your-email@gmail.com │');
console.log('│ SMTP_PASS=your-app-password │');
console.log('│ SMTP_FROM=noreply@yourdomain.com │');
console.log('│ MAILPILOT_URL=https://emailmarketing.modelrailway3d.co.uk│');
console.log('│ MAILPILOT_TOKEN=tx_abc123def456... │');
console.log('│ │');
console.log('│ 💡 Tip: Emails will be logged below when triggered │');
console.log('└─────────────────────────────────────────────────────────────┘');
console.log('');
// Create a mock transport that logs to console
mailTransport = {
sendMail: async (payload) => {
console.log('');
@@ -3520,7 +3572,7 @@ function ensureMailTransport() {
const preview = (payload.text || payload.html || '').slice(0, 200).replace(/\n/g, ' ');
console.log(`${preview}...`);
console.log('│ │');
console.log('│ Configure SMTP in .env to send real emails │');
console.log('│ Configure MAILPILOT_TOKEN in .env to send real emails │');
console.log('└─────────────────────────────────────────────────────────────┘');
console.log('');
return { messageId: 'mock-' + Date.now() };
@@ -3530,52 +3582,48 @@ function ensureMailTransport() {
return mailTransport;
}
log('initializing mail transport', summarizeMailConfig());
mailTransport = nodemailer.createTransport({
host: SMTP_HOST,
port: SMTP_PORT,
secure: SMTP_SECURE,
auth: { user: SMTP_USER, pass: SMTP_PASS },
// Add timeouts to avoid long hangs
connectionTimeout: 5000,
greetingTimeout: 5000,
socketTimeout: 5000,
});
// Verify the connection asynchronously
mailTransport.verify((error) => {
if (error) {
log('❌ SMTP verification failed', { error: String(error) });
} else {
log('✅ SMTP transport verified and ready');
}
});
log('MailPilot configured', summarizeMailConfig());
mailTransport = { url: MAILPILOT_URL, token: MAILPILOT_TOKEN };
return mailTransport;
}
async function sendEmail({ to, subject, text, html }) {
try {
const transport = ensureMailTransport();
const plain = text || undefined;
if (!MAILPILOT_TOKEN) {
await transport.sendMail({ to, subject, text, html });
return { messageId: 'mock-' + Date.now() };
}
const payload = {
from: SMTP_FROM || 'noreply@plugincompass.com',
to,
subject,
html: html || text || '',
};
if (plain) payload.text = plain;
if (html) payload.html = html;
log('sending email', { to, subject });
const info = await transport.sendMail(payload);
log('email sent successfully', { to, messageId: info.messageId });
return info;
log('sending email via MailPilot', { to, subject });
const response = await fetch(`${MAILPILOT_URL}/api/transactional-emails/trigger/${MAILPILOT_TOKEN}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`MailPilot API error: ${response.status} - ${errorText}`);
}
const result = await response.json();
log('email sent successfully via MailPilot', { to, status: result.status });
return { messageId: result.status || 'sent' };
} catch (err) {
log('FAILED TO SEND EMAIL', {
log('FAILED TO SEND EMAIL', {
to,
subject,
error: String(err),
hint: 'Check SMTP configuration in .env file'
hint: 'Check MAILPILOT_URL and MAILPILOT_TOKEN configuration in .env file'
});
throw err;
}
@@ -19004,24 +19052,20 @@ async function bootstrap() {
planningChain: planSettings.planningChain
});
// Log email/SMTP configuration
const smtpConfig = summarizeMailConfig();
console.log('[CONFIG] Email / SMTP:');
console.log(' - SMTP Configured:', smtpConfig.hostConfigured ? 'YES ✓' : 'NO ✗');
console.log(' - SMTP Host:', smtpConfig.hostConfigured ? SMTP_HOST : 'not configured');
console.log(' - SMTP Port:', smtpConfig.portConfigured ? SMTP_PORT : 'not configured');
console.log(' - SMTP Secure:', smtpConfig.secure ? 'YES (TLS/SSL)' : 'NO (STARTTLS)');
console.log(' - From Address:', smtpConfig.fromConfigured ? SMTP_FROM : 'not configured');
const mailConfig = summarizeMailConfig();
console.log('[CONFIG] MailPilot Email:');
console.log(' - MailPilot Configured:', mailConfig.configured ? 'YES ✓' : 'NO ✗');
console.log(' - MailPilot URL:', MAILPILOT_URL);
console.log('');
if (!smtpConfig.hostConfigured) {
console.log(' ⚠️ WARNING: Email is NOT configured. Password reset and verification');
if (!mailConfig.configured) {
console.log(' ⚠️ WARNING: MailPilot is NOT configured. Password reset and verification');
console.log(' emails will be logged to console only. To enable real emails:');
console.log(' 1. Edit the .env file');
console.log(' 2. Set SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, and SMTP_FROM');
console.log(' 2. Set MAILPILOT_TOKEN to your transactional email token');
console.log(' 3. Restart the server');
console.log(' 💡 Tip: Use /debug/email/preview?type=reset to preview the email template');
} else {
console.log(' ✓ Email/SMTP is configured and ready to send emails');
console.log(' ✓ MailPilot is configured and ready to send emails');
}
console.log('');
console.log('==============================');