const {onRequest, onCall} = require("firebase-functions/v2/https"); const {logger} = require("firebase-functions"); const admin = require('firebase-admin'); const nodemailer = require('nodemailer'); const cors = require('cors')({origin: true}); // Initialize Firebase Admin admin.initializeApp(); // Configure your email service (Gmail example) // You'll need to enable "Less secure app access" or use App Passwords const transporter = nodemailer.createTransporter({ service: 'gmail', auth: { user: process.env.GMAIL_USER, // Set in Firebase Functions config pass: process.env.GMAIL_PASSWORD // Set in Firebase Functions config } }); // Alternative configuration for other email services /* const transporter = nodemailer.createTransporter({ host: 'smtp.your-email-provider.com', port: 587, secure: false, auth: { user: process.env.EMAIL_USER, pass: process.env.EMAIL_PASSWORD } }); */ // Send Email Cloud Function exports.sendEmail = onCall(async (request) => { const data = request.data; try { // Validate required fields if (!data.to || !data.subject || !data.message) { throw new Error('Missing required fields: to, subject, or message'); } // Validate email addresses const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const validateEmails = (emails) => { return emails.every(email => emailRegex.test(email.trim())); }; if (!validateEmails(data.to)) { throw new Error('Invalid email addresses in "to" field'); } if (data.cc && !validateEmails(data.cc)) { throw new Error('Invalid email addresses in "cc" field'); } if (data.bcc && !validateEmails(data.bcc)) { throw new Error('Invalid email addresses in "bcc" field'); } // Prepare email options const mailOptions = { from: process.env.GMAIL_USER || 'your-email@gmail.com', to: data.to.join(', '), subject: data.subject, html: data.message, priority: data.priority || 'normal' }; // Add CC and BCC if provided if (data.cc && data.cc.length > 0) { mailOptions.cc = data.cc.join(', '); } if (data.bcc && data.bcc.length > 0) { mailOptions.bcc = data.bcc.join(', '); } // Set priority header if (data.priority === 'high') { mailOptions.priority = 'high'; mailOptions.headers = { 'X-Priority': '1' }; } else if (data.priority === 'low') { mailOptions.priority = 'low'; mailOptions.headers = { 'X-Priority': '5' }; } // Send the email const info = await transporter.sendMail(mailOptions); logger.info('Email sent successfully:', info.messageId); // Log to Firestore for tracking await admin.firestore().collection('emailLogs').add({ to: data.to, cc: data.cc || [], bcc: data.bcc || [], subject: data.subject, messageId: info.messageId, timestamp: admin.firestore.FieldValue.serverTimestamp(), status: 'sent' }); return { success: true, messageId: info.messageId, message: 'Email sent successfully' }; } catch (error) { logger.error('Error sending email:', error); // Log failed attempt await admin.firestore().collection('emailLogs').add({ to: data.to, subject: data.subject, error: error.message, timestamp: admin.firestore.FieldValue.serverTimestamp(), status: 'failed' }); return { success: false, error: error.message }; } }); // Get Email Logs (for admin purposes) exports.getEmailLogs = onCall(async (request) => { try { const snapshot = await admin.firestore() .collection('emailLogs') .orderBy('timestamp', 'desc') .limit(50) .get(); const logs = []; snapshot.forEach(doc => { logs.push({ id: doc.id, ...doc.data() }); }); return { success: true, logs }; } catch (error) { logger.error('Error getting email logs:', error); return { success: false, error: error.message }; } }); // Health check endpoint exports.healthCheck = onRequest(async (req, res) => { cors(req, res, () => { res.json({ status: 'ok', timestamp: new Date().toISOString(), service: 'Mail Composer Functions' }); }); }); // Test email configuration exports.testEmailConfig = onCall(async (request) => { try { // Verify transporter configuration await transporter.verify(); return { success: true, message: 'Email configuration is valid' }; } catch (error) { logger.error('Email configuration test failed:', error); return { success: false, error: error.message }; } });