Some checks failed
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Failing after 48s
131 lines
5.1 KiB
TypeScript
131 lines
5.1 KiB
TypeScript
// src/services/emailService.server.ts
|
|
import nodemailer from 'nodemailer';
|
|
import { logger } from './logger.server';
|
|
|
|
let transporter: nodemailer.Transporter;
|
|
|
|
const fromAddress = process.env.EMAIL_FROM || 'noreply@flyer-crawler.projectium.com';
|
|
|
|
// Use a different transport based on the environment for robust development and production use.
|
|
if (process.env.NODE_ENV === 'production') {
|
|
// In production, use the 'sendmail' transport, which interfaces with the sendmail command on the server.
|
|
transporter = nodemailer.createTransport({
|
|
sendmail: true,
|
|
newline: 'unix',
|
|
path: '/usr/sbin/sendmail'
|
|
});
|
|
logger.info('Nodemailer configured to use sendmail transport for production.');
|
|
} else {
|
|
// In development, use the 'stream' transport to log emails to the console.
|
|
// This avoids the need for a local mail server and makes debugging easy.
|
|
transporter = nodemailer.createTransport({
|
|
streamTransport: true,
|
|
newline: 'unix',
|
|
buffer: true
|
|
});
|
|
logger.info('Nodemailer configured to use stream transport for development. Emails will be logged to the console.');
|
|
}
|
|
|
|
/**
|
|
* Sends a password reset email to a user.
|
|
* @param to The recipient's email address.
|
|
* @param resetLink The unique password reset link for the user.
|
|
*/
|
|
export const sendPasswordResetEmail = async (to: string, resetLink: string): Promise<void> => {
|
|
const mailOptions = {
|
|
from: `"Flyer Crawler" <${fromAddress}>`,
|
|
to: to,
|
|
subject: 'Your Password Reset Request',
|
|
text: `
|
|
Hello,
|
|
|
|
You requested a password reset for your Flyer Crawler account. Please click the link below to set a new password:
|
|
${resetLink}
|
|
|
|
This link will expire in 1 hour. If you did not request this, please ignore this email.
|
|
|
|
Thanks,
|
|
The Flyer Crawler Team
|
|
`,
|
|
html: `
|
|
<div style="font-family: sans-serif; line-height: 1.6;">
|
|
<h2>Password Reset Request</h2>
|
|
<p>Hello,</p>
|
|
<p>You requested a password reset for your Flyer Crawler account. Please click the button below to set a new password.</p>
|
|
<p style="margin: 24px 0;">
|
|
<a href="${resetLink}" style="background-color: #007bff; color: white; padding: 12px 20px; text-decoration: none; border-radius: 5px; font-weight: bold;">Reset Your Password</a>
|
|
</p>
|
|
<p>This link will expire in 1 hour.</p>
|
|
<p>If you did not request this, please ignore this email.</p>
|
|
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;" />
|
|
<p style="font-size: 0.9em; color: #777;">If you're having trouble with the button, copy and paste this URL into your browser: ${resetLink}</p>
|
|
</div>
|
|
`,
|
|
};
|
|
|
|
try {
|
|
const info = await transporter.sendMail(mailOptions);
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
// In development, log the full email content for easy inspection.
|
|
logger.debug('--- Development Email Sent ---');
|
|
logger.debug(`To: ${to}`);
|
|
logger.debug(`Subject: ${mailOptions.subject}`);
|
|
logger.debug('Body (HTML):\n' + (info.message as Buffer).toString());
|
|
logger.debug('--- End Development Email ---');
|
|
}
|
|
logger.info(`Password reset email sent to ${to}. Message ID: ${info.messageId}`);
|
|
} catch (error) {
|
|
logger.error(`Failed to send password reset email to ${to}`, { error });
|
|
// Re-throw the error so the calling function knows the email failed to send.
|
|
throw new Error('Failed to send password reset email.');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Sends a welcome email to a new user.
|
|
* @param to The recipient's email address.
|
|
* @param name The user's name to personalize the email. Can be undefined.
|
|
*/
|
|
export const sendWelcomeEmail = async (to: string, name: string | undefined | null): Promise<void> => {
|
|
// Provide a fallback for the name to make the greeting more friendly.
|
|
const userName = name || 'there';
|
|
const frontendUrl = process.env.FRONTEND_URL || 'http://localhost:5173';
|
|
|
|
const mailOptions = {
|
|
from: `"Flyer Crawler" <${fromAddress}>`,
|
|
to: to,
|
|
subject: 'Welcome to Flyer Crawler!',
|
|
text: `
|
|
Hello ${userName},
|
|
|
|
Welcome to Flyer Crawler! We're excited to have you on board.
|
|
|
|
Start exploring the latest deals and manage your shopping lists to save time and money.
|
|
|
|
Happy shopping!
|
|
|
|
The Flyer Crawler Team
|
|
`,
|
|
html: `
|
|
<div style="font-family: sans-serif; line-height: 1.6;">
|
|
<h2>Welcome to Flyer Crawler!</h2>
|
|
<p>Hello ${userName},</p>
|
|
<p>We're excited to have you on board. Start exploring the latest deals, manage your shopping lists, and make your grocery shopping smarter and easier.</p>
|
|
<p style="margin: 24px 0;">
|
|
<a href="${frontendUrl}" style="background-color: #28a745; color: white; padding: 12px 20px; text-decoration: none; border-radius: 5px; font-weight: bold;">Get Started</a>
|
|
</p>
|
|
<p>Happy shopping!</p>
|
|
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;" />
|
|
<p style="font-size: 0.9em; color: #777;">The Flyer Crawler Team</p>
|
|
</div>
|
|
`,
|
|
};
|
|
|
|
try {
|
|
const info = await transporter.sendMail(mailOptions);
|
|
logger.info(`Welcome email sent to ${to}. Message ID: ${info.messageId}`);
|
|
} catch (error) {
|
|
logger.error(`Failed to send welcome email to ${to}`, { error });
|
|
throw new Error('Failed to send welcome email.');
|
|
}
|
|
}; |