97 lines
4.1 KiB
TypeScript
97 lines
4.1 KiB
TypeScript
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" <noreply@taskflow.local>',
|
|
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: `<h1>Welcome to TaskFlow!</h1><p>Hi ${user.username},</p><p>We're excited to have you on board.</p><p>Best,<br>The TaskFlow Team</p>`
|
|
});
|
|
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: `<h1>Reset Password</h1><p>Hi ${user.username},</p><p>You requested a password reset. Click the link below to reset your password:</p><p><a href="${resetLink}">Reset Password</a></p><p>If you didn't request this, please ignore this email.</p><p>This link expires in 1 hour.</p>`
|
|
});
|
|
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" <noreply@taskflow.local>';
|
|
}
|
|
}
|