All files / src/lib/email nodemailer.ts

100% Statements 35/35
100% Branches 11/11
100% Functions 3/3
100% Lines 35/35

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101              7x       7x                             7x       3x 3x   3x               3x 3x   1x 1x 1x 1x 1x 1x     2x 1x   1x 1x             7x 3x   3x               3x 3x   1x 1x 1x 1x 1x 1x     2x 1x   1x   1x             7x 1x    
import nodemailer from 'nodemailer';
import { AUTH_ROUTES } from '../../types/routes';
import { resetPasswordEmailTemplate } from './templates/resetPasswordEmailTemplate';
import { welcomeEmailTemplate } from './templates/welcomeEmailTemplate';
 
// Check if email is configured
const isEmailConfigured =
  process.env.EMAIL_HOST &&
  process.env.EMAIL_USER &&
  process.env.EMAIL_PASSWORD;
 
const transporter = isEmailConfigured
  ? nodemailer.createTransport({
      host: process.env.EMAIL_HOST,
      port: Number(process.env.EMAIL_PORT) || 587,
      secure: process.env.EMAIL_SECURE === 'true', // true for 465, false for other ports
      auth: {
        user: process.env.EMAIL_USER,
        pass: process.env.EMAIL_PASSWORD,
      },
    })
  : null;
 
/**
 * Send password reset email to user
 */
export const sendPasswordResetEmail = async (
  email: string,
  resetToken: string,
) => {
  const resetUrl = `${process.env.NEXTAUTH_URL}${AUTH_ROUTES.RESET_PASSWORD}/${resetToken}`;
  const { html, text } = resetPasswordEmailTemplate(resetUrl);
 
  const mailOptions = {
    from: `"${process.env.EMAIL_FROM_NAME}" <${process.env.EMAIL_FROM}>`,
    to: email,
    subject: 'Password Reset Request',
    html,
    text,
  };
 
  try {
    if (!transporter) {
      // Development mode: log the reset URL instead of sending email
      console.log('=================================');
      console.log('📧 EMAIL NOT CONFIGURED - DEVELOPMENT MODE');
      console.log('Password reset link:');
      console.log(resetUrl);
      console.log('=================================');
      return;
    }
 
    await transporter.sendMail(mailOptions);
    console.log(`Password reset email sent to ${email}`);
  } catch (error) {
    console.error('Error sending password reset email:', error);
    throw new Error('Failed to send password reset email');
  }
};
 
/**
 * Send welcome email to newly registered user
 */
export const sendWelcomeEmail = async (email: string, userName: string) => {
  const { html, text } = welcomeEmailTemplate(userName);
 
  const mailOptions = {
    from: `"${process.env.EMAIL_FROM_NAME}" <${process.env.EMAIL_FROM}>`,
    to: email,
    subject: 'Welcome to Cookbook!',
    html,
    text,
  };
 
  try {
    if (!transporter) {
      // Development mode: log instead of sending email
      console.log('=================================');
      console.log('📧 EMAIL NOT CONFIGURED - DEVELOPMENT MODE');
      console.log(`Welcome email would be sent to: ${email}`);
      console.log(`User name: ${userName}`);
      console.log('=================================');
      return;
    }
 
    await transporter.sendMail(mailOptions);
    console.log(`Welcome email sent to ${email}`);
  } catch (error) {
    console.error('Error sending welcome email:', error);
    // Don't throw - we don't want to fail registration if email fails
    console.warn('Registration succeeded but welcome email failed to send');
  }
};
 
/**
 * Generate a random reset token
 */
export const generateResetToken = (): string => {
  return crypto.randomUUID();
};