mjml EMail

This commit is contained in:
2026-03-16 18:00:32 -05:00
parent b9f9df74c0
commit 5a7ba66c27
9 changed files with 3248 additions and 24 deletions

View File

@@ -12,6 +12,7 @@ const { formatDate, formatMoney } = require('../utils/helpers');
const { getBrowser, generatePdfFromHtml, getLogoHtml, renderInvoiceItems, formatAddressLines } = require('../services/pdf-service');
const { exportInvoiceToQbo, syncInvoiceToQbo } = require('../services/qbo-service');
const { getOAuthClient, getQboBaseUrl, makeQboApiCall } = require('../config/qbo');
const { sendInvoiceEmail } = require('../services/email-service');
function calculateNextRecurringDate(invoiceDate, interval) {
const d = new Date(invoiceDate);
@@ -816,5 +817,65 @@ router.get('/:id/html', async (req, res) => {
res.status(500).json({ error: 'Error generating HTML' });
}
});
router.post('/:id/send-email', async (req, res) => {
const { id } = req.params;
const { recipientEmail, customText, melioLink } = req.body;
if (!recipientEmail) {
return res.status(400).json({ error: 'Recipient email is required.' });
}
try {
// 1. Rechnungsdaten und Items laden (analog zu deiner PDF-Route)
const invoiceResult = await pool.query(`
SELECT i.*, c.name as customer_name, c.line1, c.line2, c.line3, c.line4, c.city, c.state, c.zip_code, c.account_number,
COALESCE((SELECT SUM(pi.amount) FROM payment_invoices pi WHERE pi.invoice_id = i.id), 0) as amount_paid
FROM invoices i
LEFT JOIN customers c ON i.customer_id = c.id
WHERE i.id = $1
`, [id]);
if (invoiceResult.rows.length === 0) return res.status(404).json({ error: 'Invoice not found' });
const invoice = invoiceResult.rows[0];
const itemsResult = await pool.query('SELECT * FROM invoice_items WHERE invoice_id = $1 ORDER BY item_order', [id]);
// 2. PDF generieren, aber nur im Speicher halten
const templatePath = path.join(__dirname, '..', '..', 'templates', 'invoice-template.html');
let html = await fs.readFile(templatePath, 'utf-8');
const logoHTML = await getLogoHtml();
const itemsHTML = renderInvoiceItems(itemsResult.rows, invoice);
const authHTML = invoice.auth_code ? `<p style="margin-bottom: 20px; font-size: 13px;"><strong>Authorization:</strong> ${invoice.auth_code}</p>` : '';
const streetBlock = formatAddressLines(invoice.line1, invoice.line2, invoice.line3, invoice.line4, invoice.customer_name);
html = html
.replace('{{LOGO_HTML}}', logoHTML)
.replace('{{CUSTOMER_NAME}}', invoice.bill_to_name || invoice.customer_name || '')
.replace('{{CUSTOMER_STREET}}', streetBlock)
.replace('{{CUSTOMER_CITY}}', invoice.city || '')
.replace('{{CUSTOMER_STATE}}', invoice.state || '')
.replace('{{CUSTOMER_ZIP}}', invoice.zip_code || '')
.replace('{{INVOICE_NUMBER}}', invoice.invoice_number || '')
.replace('{{ACCOUNT_NUMBER}}', invoice.account_number || '')
.replace('{{INVOICE_DATE}}', formatDate(invoice.invoice_date))
.replace('{{TERMS}}', invoice.terms)
.replace('{{AUTHORIZATION}}', authHTML)
.replace('{{ITEMS}}', itemsHTML);
const pdfBuffer = await generatePdfFromHtml(html);
// 3. E-Mail über SES versenden
const info = await sendInvoiceEmail(invoice, recipientEmail, customText, melioLink, pdfBuffer);
// 4. (Optional) Status in der DB aktualisieren
//await pool.query('UPDATE invoices SET email_status = $1 WHERE id = $2', ['sent', id]);
res.json({ success: true, messageId: info.messageId });
} catch (error) {
console.error('Error sending invoice email:', error);
res.status(500).json({ error: 'Failed to send email: ' + error.message });
}
});
module.exports = router;