import nodemailer from 'nodemailer'; import { IStorage } from './storage'; import { User } from '../shared/schema'; interface EmailSettings { host: string; port: number; user?: string; pass?: string; from: string; secure: boolean; } export class EmailService { private storage: IStorage; constructor(storage: IStorage) { this.storage = storage; } private async getTransporter() { // Try to get settings from DB const host = await this.storage.getSystemSettings('smtp_host'); const port = await this.storage.getSystemSettings('smtp_port'); const user = await this.storage.getSystemSettings('smtp_user'); const pass = await this.storage.getSystemSettings('smtp_pass'); const from = await this.storage.getSystemSettings('smtp_from'); const secure = await this.storage.getSystemSettings('smtp_secure'); // Fallback to Env or MailHog defaults const settings: EmailSettings = { host: host || process.env.SMTP_HOST || 'localhost', port: port ? parseInt(port) : (process.env.SMTP_PORT ? parseInt(process.env.SMTP_PORT) : 1025), user: user || process.env.SMTP_USER, pass: pass || process.env.SMTP_PASS, from: from || process.env.SMTP_FROM || '"TaskFlow" ', secure: secure === 'true' }; return nodemailer.createTransport({ host: settings.host, port: settings.port, secure: settings.secure, auth: settings.user ? { user: settings.user, pass: settings.pass } : undefined, ignoreTLS: !settings.secure // useful for MailHog }); } async sendWelcomeEmail(user: User) { try { const transporter = await this.getTransporter(); const info = await transporter.sendMail({ from: await this.getFromAddress(), to: user.email, subject: 'Welcome to TaskFlow!', text: `Hi ${user.username},\n\nWelcome to TaskFlow! We're excited to have you on board.\n\nBest,\nThe TaskFlow Team`, html: `

Welcome to TaskFlow!

Hi ${user.username},

We're excited to have you on board.

Best,
The TaskFlow Team

` }); console.log(`[Email] Welcome email sent to ${user.email}: ${info.messageId}`); return true; } catch (error) { console.error(`[Email] Failed to send welcome email to ${user.email}:`, error); return false; } } async sendPasswordResetEmail(user: User, token: string) { try { const transporter = await this.getTransporter(); // TODO: Get base URL from settings or env const baseUrl = process.env.APP_URL || 'http://localhost:5001'; const resetLink = `${baseUrl}/reset-password?token=${token}`; const info = await transporter.sendMail({ from: await this.getFromAddress(), to: user.email, subject: 'Reset your TaskFlow Password', text: `Hi ${user.username},\n\nYou requested a password reset. Click the link below to reset your password:\n\n${resetLink}\n\nIf you didn't request this, please ignore this email.\n\nThis link expires in 1 hour.`, html: `

Reset Password

Hi ${user.username},

You requested a password reset. Click the link below to reset your password:

Reset Password

If you didn't request this, please ignore this email.

This link expires in 1 hour.

` }); console.log(`[Email] Password reset email sent to ${user.email}: ${info.messageId}`); return true; } catch (error) { console.error(`[Email] Failed to send reset email to ${user.email}:`, error); return false; } } private async getFromAddress() { const from = await this.storage.getSystemSettings('smtp_from'); return from || process.env.SMTP_FROM || '"TaskFlow" '; } }