Files
flyer-crawler.projectium.com/src/services/emailService.server.ts
Torben Sorensen 1d0bd630b2
Some checks failed
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Failing after 48s
test, more id fixes, and naming all files
2025-11-25 05:59:56 -08:00

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.');
}
};