Matt 7bdd462be9
Some checks failed
Build and Push Docker Images / docker (push) Has been cancelled
Implement comprehensive email templates with SMTP integration
- Add beautiful HTML email templates for receipts, admin notifications, and contact forms
- Implement SMTP email service with Nodemailer and Handlebars templating
- Add carbon equivalency calculations with EPA/DEFRA/IMO 2024 conversion factors
- Add portfolio color palette system for project visualization
- Integrate Wren API portfolio fetching in webhook handler
- Add light mode enforcement for email client compatibility
- Include Puffin logo from MinIO S3 in all templates
- Add test email endpoint for template validation

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-31 20:09:31 +01:00

188 lines
5.3 KiB
JavaScript

import express from 'express';
import rateLimit from 'express-rate-limit';
import { sendContactEmail, sendReceiptEmail, sendAdminNotification } from '../utils/emailService.js';
const router = express.Router();
// Rate limiters for different endpoints
const contactLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 5, // 5 requests per minute
message: { error: 'Too many contact form submissions. Please try again later.' },
standardHeaders: true,
legacyHeaders: false,
});
const receiptLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 10, // 10 receipts per minute
message: { error: 'Too many receipt requests. Please try again later.' },
standardHeaders: true,
legacyHeaders: false,
});
// Validation helper
function validateEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
// POST /api/email/contact - Send contact form to admin
router.post('/contact', contactLimiter, async (req, res) => {
try {
const { name, email, phone, company, message } = req.body;
// Validation
if (!name || !email || !message) {
return res.status(400).json({
error: 'Missing required fields: name, email, and message are required'
});
}
if (!validateEmail(email)) {
return res.status(400).json({
error: 'Invalid email address'
});
}
if (message.length < 10) {
return res.status(400).json({
error: 'Message must be at least 10 characters long'
});
}
if (message.length > 5000) {
return res.status(400).json({
error: 'Message must be less than 5000 characters'
});
}
// Send email
const result = await sendContactEmail({
name: name.trim(),
email: email.trim().toLowerCase(),
phone: phone?.trim() || '',
company: company?.trim() || '',
message: message.trim()
});
console.log(`✅ Contact form email sent from ${email}`);
res.status(200).json({
success: true,
messageId: result.messageId,
message: 'Contact form submitted successfully'
});
} catch (error) {
console.error('❌ Contact form email failed:', error);
res.status(500).json({
error: 'Failed to send contact form. Please try again later.',
details: process.env.NODE_ENV === 'development' ? error.message : undefined
});
}
});
// POST /api/email/receipt - Send receipt to customer
router.post('/receipt', receiptLimiter, async (req, res) => {
try {
const { customerEmail, orderDetails } = req.body;
// Validation
if (!customerEmail || !orderDetails) {
return res.status(400).json({
error: 'Missing required fields: customerEmail and orderDetails are required'
});
}
if (!validateEmail(customerEmail)) {
return res.status(400).json({
error: 'Invalid customer email address'
});
}
const required = ['tons', 'portfolioId', 'baseAmount', 'processingFee', 'totalAmount', 'orderId', 'stripeSessionId'];
const missing = required.filter(field => orderDetails[field] === undefined);
if (missing.length > 0) {
return res.status(400).json({
error: `Missing required order details: ${missing.join(', ')}`
});
}
// Send receipt email
const receiptResult = await sendReceiptEmail(
customerEmail.trim().toLowerCase(),
orderDetails
);
// Also send admin notification (non-blocking)
sendAdminNotification(orderDetails, customerEmail).catch(err => {
console.error('⚠️ Admin notification failed (non-fatal):', err.message);
});
console.log(`✅ Receipt email sent to ${customerEmail}`);
res.status(200).json({
success: true,
messageId: receiptResult.messageId,
message: 'Receipt sent successfully'
});
} catch (error) {
console.error('❌ Receipt email failed:', error);
res.status(500).json({
error: 'Failed to send receipt. Please contact support.',
details: process.env.NODE_ENV === 'development' ? error.message : undefined
});
}
});
// POST /api/email/admin-notify - Send admin notification
router.post('/admin-notify', receiptLimiter, async (req, res) => {
try {
const { orderDetails, customerEmail } = req.body;
// Validation
if (!orderDetails || !customerEmail) {
return res.status(400).json({
error: 'Missing required fields: orderDetails and customerEmail are required'
});
}
if (!validateEmail(customerEmail)) {
return res.status(400).json({
error: 'Invalid customer email address'
});
}
// Send notification
const result = await sendAdminNotification(orderDetails, customerEmail);
if (result.skipped) {
return res.status(200).json({
success: true,
skipped: true,
message: 'Admin notifications are disabled'
});
}
console.log(`✅ Admin notification sent for order ${orderDetails.orderId}`);
res.status(200).json({
success: true,
messageId: result.messageId,
message: 'Admin notification sent successfully'
});
} catch (error) {
console.error('❌ Admin notification failed:', error);
res.status(500).json({
error: 'Failed to send admin notification',
details: process.env.NODE_ENV === 'development' ? error.message : undefined
});
}
});
export default router;