TypeScript · 13312 bytes Raw Blame History
1 import nodemailer from 'nodemailer';
2 import type { Transporter } from 'nodemailer';
3
4 export interface EmailConfig {
5 host: string;
6 port: number;
7 secure: boolean;
8 auth?: {
9 user: string;
10 pass: string;
11 };
12 from: string;
13 }
14
15 export class EmailService {
16 private transporter: Transporter;
17 private config: EmailConfig;
18
19 constructor(config: EmailConfig) {
20 this.config = config;
21
22 // For development, use Ethereal Email (fake SMTP service)
23 if (process.env.NODE_ENV === 'development' && !config.auth) {
24 // This will be setup asynchronously
25 this.setupDevelopmentTransporter();
26 } else {
27 this.transporter = nodemailer.createTransporter({
28 host: config.host,
29 port: config.port,
30 secure: config.secure,
31 auth: config.auth,
32 });
33 }
34 }
35
36 private async setupDevelopmentTransporter() {
37 try {
38 // Create Ethereal Email account for development
39 const testAccount = await nodemailer.createTestAccount();
40
41 this.transporter = nodemailer.createTransporter({
42 host: 'smtp.ethereal.email',
43 port: 587,
44 secure: false,
45 auth: {
46 user: testAccount.user,
47 pass: testAccount.pass,
48 },
49 });
50
51 console.log('Development email transporter created with Ethereal Email');
52 console.log('Preview URLs will be logged when emails are sent');
53 } catch (error) {
54 console.error('Failed to create development email transporter:', error);
55 // Fallback to console logging
56 this.transporter = {
57 sendMail: async (options: any) => {
58 console.log('Email would be sent:', options);
59 return { messageId: 'dev-' + Date.now() };
60 },
61 } as any;
62 }
63 }
64
65 async sendEmailVerification(to: string, token: string, username?: string): Promise<void> {
66 const verificationUrl = `${process.env.FRONTEND_URL || 'http://localhost:3000'}/verify-email?token=${token}`;
67
68 const html = `
69 <!DOCTYPE html>
70 <html>
71 <head>
72 <meta charset="utf-8">
73 <title>Verify your ZephyrFS account</title>
74 <style>
75 body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
76 .container { max-width: 600px; margin: 0 auto; padding: 20px; }
77 .header { background: #007bff; color: white; padding: 20px; text-align: center; }
78 .content { padding: 30px; background: #f9f9f9; }
79 .button {
80 display: inline-block;
81 background: #007bff;
82 color: white;
83 padding: 12px 30px;
84 text-decoration: none;
85 border-radius: 5px;
86 margin: 20px 0;
87 }
88 .footer { padding: 20px; text-align: center; color: #666; font-size: 14px; }
89 </style>
90 </head>
91 <body>
92 <div class="container">
93 <div class="header">
94 <h1>Welcome to ZephyrFS!</h1>
95 </div>
96 <div class="content">
97 <h2>Verify Your Email Address</h2>
98 <p>Hello${username ? ` ${username}` : ''}!</p>
99 <p>Thank you for signing up for ZephyrFS, the secure and decentralized file backup system.</p>
100 <p>To complete your registration and start using ZephyrFS, please verify your email address by clicking the button below:</p>
101
102 <div style="text-align: center;">
103 <a href="${verificationUrl}" class="button">Verify Email Address</a>
104 </div>
105
106 <p>Or copy and paste this link into your browser:</p>
107 <p style="word-break: break-all; background: #eee; padding: 10px; border-radius: 3px;">
108 ${verificationUrl}
109 </p>
110
111 <p><strong>This verification link will expire in 24 hours.</strong></p>
112
113 <p>If you didn't create an account with ZephyrFS, you can safely ignore this email.</p>
114 </div>
115 <div class="footer">
116 <p>© 2024 ZephyrFS. All rights reserved.</p>
117 <p>Secure, decentralized file backup for everyone.</p>
118 </div>
119 </div>
120 </body>
121 </html>
122 `;
123
124 const text = `
125 Welcome to ZephyrFS!
126
127 Hello${username ? ` ${username}` : ''}!
128
129 Thank you for signing up for ZephyrFS, the secure and decentralized file backup system.
130
131 To complete your registration and start using ZephyrFS, please verify your email address by visiting this link:
132
133 ${verificationUrl}
134
135 This verification link will expire in 24 hours.
136
137 If you didn't create an account with ZephyrFS, you can safely ignore this email.
138
139 © 2024 ZephyrFS. All rights reserved.
140 `;
141
142 const result = await this.transporter.sendMail({
143 from: this.config.from || 'ZephyrFS <noreply@zephyrfs.org>',
144 to,
145 subject: 'Verify your ZephyrFS account',
146 text,
147 html,
148 });
149
150 // In development, log preview URL
151 if (process.env.NODE_ENV === 'development') {
152 const previewUrl = nodemailer.getTestMessageUrl(result);
153 if (previewUrl) {
154 console.log('Email verification preview:', previewUrl);
155 }
156 }
157 }
158
159 async sendPasswordReset(to: string, token: string, username?: string): Promise<void> {
160 const resetUrl = `${process.env.FRONTEND_URL || 'http://localhost:3000'}/reset-password?token=${token}`;
161
162 const html = `
163 <!DOCTYPE html>
164 <html>
165 <head>
166 <meta charset="utf-8">
167 <title>Reset your ZephyrFS password</title>
168 <style>
169 body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
170 .container { max-width: 600px; margin: 0 auto; padding: 20px; }
171 .header { background: #dc3545; color: white; padding: 20px; text-align: center; }
172 .content { padding: 30px; background: #f9f9f9; }
173 .button {
174 display: inline-block;
175 background: #dc3545;
176 color: white;
177 padding: 12px 30px;
178 text-decoration: none;
179 border-radius: 5px;
180 margin: 20px 0;
181 }
182 .footer { padding: 20px; text-align: center; color: #666; font-size: 14px; }
183 .warning { background: #fff3cd; border: 1px solid #ffeaa7; padding: 15px; border-radius: 5px; margin: 20px 0; }
184 </style>
185 </head>
186 <body>
187 <div class="container">
188 <div class="header">
189 <h1>Password Reset Request</h1>
190 </div>
191 <div class="content">
192 <h2>Reset Your Password</h2>
193 <p>Hello${username ? ` ${username}` : ''}!</p>
194 <p>We received a request to reset the password for your ZephyrFS account.</p>
195
196 <div class="warning">
197 <strong>⚠️ Security Notice:</strong> If you didn't request this password reset, please ignore this email. Your account is safe.
198 </div>
199
200 <p>To reset your password, click the button below:</p>
201
202 <div style="text-align: center;">
203 <a href="${resetUrl}" class="button">Reset Password</a>
204 </div>
205
206 <p>Or copy and paste this link into your browser:</p>
207 <p style="word-break: break-all; background: #eee; padding: 10px; border-radius: 3px;">
208 ${resetUrl}
209 </p>
210
211 <p><strong>This reset link will expire in 1 hour.</strong></p>
212
213 <p>After clicking the link, you'll be able to create a new password for your account.</p>
214 </div>
215 <div class="footer">
216 <p>© 2024 ZephyrFS. All rights reserved.</p>
217 <p>If you have security concerns, contact us immediately.</p>
218 </div>
219 </div>
220 </body>
221 </html>
222 `;
223
224 const text = `
225 Password Reset Request
226
227 Hello${username ? ` ${username}` : ''}!
228
229 We received a request to reset the password for your ZephyrFS account.
230
231 If you didn't request this password reset, please ignore this email. Your account is safe.
232
233 To reset your password, visit this link:
234
235 ${resetUrl}
236
237 This reset link will expire in 1 hour.
238
239 After clicking the link, you'll be able to create a new password for your account.
240
241 © 2024 ZephyrFS. All rights reserved.
242 `;
243
244 const result = await this.transporter.sendMail({
245 from: this.config.from || 'ZephyrFS Security <security@zephyrfs.org>',
246 to,
247 subject: 'Reset your ZephyrFS password',
248 text,
249 html,
250 });
251
252 // In development, log preview URL
253 if (process.env.NODE_ENV === 'development') {
254 const previewUrl = nodemailer.getTestMessageUrl(result);
255 if (previewUrl) {
256 console.log('Password reset preview:', previewUrl);
257 }
258 }
259 }
260
261 async sendWelcomeEmail(to: string, userType: 'backup' | 'volunteer', username?: string): Promise<void> {
262 const isVolunteer = userType === 'volunteer';
263 const dashboardUrl = `${process.env.FRONTEND_URL || 'http://localhost:3000'}/dashboard`;
264
265 const html = `
266 <!DOCTYPE html>
267 <html>
268 <head>
269 <meta charset="utf-8">
270 <title>Welcome to ZephyrFS!</title>
271 <style>
272 body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
273 .container { max-width: 600px; margin: 0 auto; padding: 20px; }
274 .header { background: #28a745; color: white; padding: 20px; text-align: center; }
275 .content { padding: 30px; background: #f9f9f9; }
276 .button {
277 display: inline-block;
278 background: #28a745;
279 color: white;
280 padding: 12px 30px;
281 text-decoration: none;
282 border-radius: 5px;
283 margin: 20px 0;
284 }
285 .footer { padding: 20px; text-align: center; color: #666; font-size: 14px; }
286 .feature { background: white; padding: 15px; margin: 10px 0; border-left: 4px solid #28a745; }
287 </style>
288 </head>
289 <body>
290 <div class="container">
291 <div class="header">
292 <h1>🎉 Welcome to ZephyrFS!</h1>
293 </div>
294 <div class="content">
295 <h2>Your account is ready!</h2>
296 <p>Hello${username ? ` ${username}` : ''}!</p>
297 <p>Welcome to ZephyrFS! Your email has been verified and your account is now active.</p>
298
299 ${isVolunteer ? `
300 <h3>🤝 Thank you for volunteering!</h3>
301 <p>As a storage volunteer, you're helping build a decentralized, secure backup network that benefits everyone.</p>
302
303 <div class="feature">
304 <strong>📁 Safe Storage Allocation</strong><br>
305 Choose how much disk space to contribute safely without risking your system.
306 </div>
307
308 <div class="feature">
309 <strong>🔒 Zero-Knowledge Security</strong><br>
310 All stored data is encrypted - you can't see what's stored on your system.
311 </div>
312
313 <div class="feature">
314 <strong>💰 Earn Rewards</strong><br>
315 Get compensated for providing reliable storage to the network.
316 </div>
317
318 <p>Ready to get started? Use our desktop app to set up your storage node:</p>
319 ` : `
320 <h3>☁️ Secure Backup Made Simple</h3>
321 <p>Your files will be encrypted, distributed, and safely backed up across our decentralized network.</p>
322
323 <div class="feature">
324 <strong>🔐 Military-Grade Encryption</strong><br>
325 Your files are encrypted before leaving your device.
326 </div>
327
328 <div class="feature">
329 <strong>🌐 Distributed Storage</strong><br>
330 Files are split and stored across multiple secure nodes.
331 </div>
332
333 <div class="feature">
334 <strong>⚡ Simple as Google Drive</strong><br>
335 Drag and drop to backup. That's it!
336 </div>
337
338 <p>Ready to start backing up your files?</p>
339 `}
340
341 <div style="text-align: center;">
342 <a href="${dashboardUrl}" class="button">Get Started</a>
343 </div>
344
345 <p>If you have any questions or need help getting started, our community is here to support you!</p>
346 </div>
347 <div class="footer">
348 <p>© 2024 ZephyrFS. All rights reserved.</p>
349 <p>Building the future of secure, decentralized storage.</p>
350 </div>
351 </div>
352 </body>
353 </html>
354 `;
355
356 const result = await this.transporter.sendMail({
357 from: this.config.from || 'ZephyrFS <welcome@zephyrfs.org>',
358 to,
359 subject: '🎉 Welcome to ZephyrFS - Your account is ready!',
360 html,
361 });
362
363 // In development, log preview URL
364 if (process.env.NODE_ENV === 'development') {
365 const previewUrl = nodemailer.getTestMessageUrl(result);
366 if (previewUrl) {
367 console.log('Welcome email preview:', previewUrl);
368 }
369 }
370 }
371 }