11const nodemailer = require ( 'nodemailer' ) ;
2- const { sendEmail, transporter } = require ( '../utils/sendEmail ' ) ;
2+ const { sendEmail } = require ( '../utils/SendEmail ' ) ;
33const logger = require ( '../utils/logger' ) ;
4+ const mongoose = require ( 'mongoose' ) ;
5+ const EmailEvent = require ( '../models/EmailEvent' ) ;
46
5- //POST /api/emails/send
6- //Body: {to,subject,text,html,from}
7+ // POST /api/emails/send
8+ // Body: { to, subject, text, html, from }
79exports . send = async ( req , res ) => {
810 try {
911 const { to, subject, text, html, from } = req . body ;
10- if ( ! to ) return res . status ( 400 ) . json ( { success : false , error : 'Recipient "to" is required' } ) ;
12+ if ( ! to ) {
13+ return res . status ( 400 ) . json ( { success : false , error : 'Recipient "to" is required' } ) ;
14+ }
15+
16+ // create unique emailId for tracking
17+ const emailId = new mongoose . Types . ObjectId ( ) ;
18+
19+ // ensure base URL is defined
20+ const baseUrl = process . env . BASE_URL || 'http://localhost:5000' ;
21+
22+ // inject tracking pixel and link into HTML body
23+ const trackedHtml = `
24+ ${ html || '' }
25+ <img src="${ baseUrl } /api/track/open/${ emailId } .png" width="1" height="1" style="display:none;" />
26+ <p><a href="${ baseUrl } /api/track/click/${ emailId } ?redirect=https://mailmern.vercel.app">Click here</a></p>
27+ ` ;
28+
29+ // send email through configured transporter
30+ const info = await sendEmail ( { to, subject, text, html : trackedHtml , from } ) ;
1131
12- const info = await sendEmail ( { to, subject, text, html, from } ) ;
32+ // store initial record in database
33+ await EmailEvent . create ( {
34+ _id : emailId ,
35+ recipient : to ,
36+ subject,
37+ status : 'sent' ,
38+ createdAt : new Date ( )
39+ } ) ;
1340
1441 return res . status ( 200 ) . json ( {
1542 success : true ,
16- message : 'Email sent' ,
43+ message : 'Email sent (with tracking) ' ,
1744 info : {
1845 messageId : info . messageId ,
1946 accepted : info . accepted ,
2047 rejected : info . rejected ,
2148 response : info . response
22- }
49+ } ,
50+ emailId
2351 } ) ;
2452 } catch ( error ) {
25- //full error.message for debugging
2653 logger . error ( 'Error in send email controller:' , error && ( error . stack || error ) ) ;
2754 return res . status ( 500 ) . json ( {
2855 success : false ,
@@ -31,33 +58,49 @@ exports.send = async (req, res) => {
3158 }
3259} ;
3360
34- //POST /api/emails/test
35- //Body optional: {to} provide in body or in .env
61+ // POST /api/emails/test
3662exports . test = async ( req , res ) => {
3763 try {
38- const to = req . body ?. to || process . env . EMAIL_TEST_TO || process . env . SMTP_USER || process . env . EMAIL_USER ;
64+ const to =
65+ req . body ?. to ||
66+ process . env . EMAIL_TEST_TO ||
67+ process . env . SMTP_USER ||
68+ process . env . EMAIL_USER ;
3969 if ( ! to ) {
4070 return res . status ( 400 ) . json ( {
4171 success : false ,
42- error : 'No test recipient configured. Provide "to" in body or set EMAIL_TEST_TO/SMTP_USER/EMAIL_USER in env.'
72+ error :
73+ 'No test recipient configured. Provide "to" in body or set EMAIL_TEST_TO/SMTP_USER/EMAIL_USER in env.'
4374 } ) ;
4475 }
4576
46- const subject = 'MailMERN — Test Email' ;
47- const text = `MailMERN test email sent at ${ new Date ( ) . toISOString ( ) } ` ;
48- const html = `<p>MailMERN test email sent at <strong>${ new Date ( ) . toISOString ( ) } </strong></p>` ;
77+ const subject = 'MailMERN — Test Email (Tracked)' ;
78+ const html = `<p>Hello! This is a MailMERN tracking test email.</p>` ;
79+
80+ const emailId = new mongoose . Types . ObjectId ( ) ;
81+ const baseUrl = process . env . BASE_URL || 'http://localhost:5000' ;
82+
83+ const trackedHtml = `
84+ ${ html }
85+ <img src="${ baseUrl } /api/track/open/${ emailId } .png" width="1" height="1" style="display:none;" />
86+ <p><a href="${ baseUrl } /api/track/click/${ emailId } ?redirect=https://mailmern.vercel.app">Click here</a></p>
87+ ` ;
4988
50- const info = await sendEmail ( { to, subject, text, html } ) ;
89+ const info = await sendEmail ( { to, subject, html : trackedHtml } ) ;
90+
91+ await EmailEvent . create ( {
92+ _id : emailId ,
93+ recipient : to ,
94+ subject,
95+ status : 'sent' ,
96+ createdAt : new Date ( )
97+ } ) ;
5198
5299 return res . status ( 200 ) . json ( {
53100 success : true ,
54- message : `Test email sent to ${ to } ` ,
55- info : {
56- messageId : info . messageId ,
57- accepted : info . accepted ,
58- rejected : info . rejected ,
59- response : info . response
60- }
101+ message : `Test tracked email sent to ${ to } ` ,
102+ info,
103+ emailId
61104 } ) ;
62105 } catch ( error ) {
63106 logger . error ( 'Error in email test controller:' , error && ( error . stack || error ) ) ;
@@ -68,9 +111,7 @@ exports.test = async (req, res) => {
68111 }
69112} ;
70113
71-
72- //POST /api/emails/test-ethereal
73- //Creates a temporary Ethereal account and sends a test email there.useful to check sending code path without real SMTP credentials
114+ // POST /api/emails/test-ethereal
74115exports . testEthereal = async ( req , res ) => {
75116 try {
76117 const testAccount = await nodemailer . createTestAccount ( ) ;
@@ -87,14 +128,12 @@ exports.testEthereal = async (req, res) => {
87128
88129 const to = req . body ?. to || process . env . EMAIL_TEST_TO || 'recipient@example.com' ;
89130 const subject = 'MailMERN — Ethereal Test Email' ;
90- const text = `Ethereal test email sent at ${ new Date ( ) . toISOString ( ) } ` ;
91131 const html = `<p>Ethereal test email sent at <strong>${ new Date ( ) . toISOString ( ) } </strong></p>` ;
92132
93133 const info = await ethTransport . sendMail ( {
94134 from : process . env . EMAIL_FROM || `MailMERN <${ testAccount . user } >` ,
95135 to,
96136 subject,
97- text,
98137 html
99138 } ) ;
100139
@@ -122,4 +161,4 @@ exports.testEthereal = async (req, res) => {
122161 error : error && ( error . message || error )
123162 } ) ;
124163 }
125- } ;
164+ } ;
0 commit comments