Some checks failed
Build and Push Docker Images / docker (push) Has been cancelled
- 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>
188 lines
5.3 KiB
JavaScript
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;
|