mailpilot sending
This commit is contained in:
236
chat/server.js
236
chat/server.js
@@ -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('==============================');
|
||||
|
||||
Reference in New Issue
Block a user