@zola_do/email 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +424 -27
- package/dist/email-transport.interface.d.ts +10 -0
- package/dist/email-transport.interface.js +3 -0
- package/dist/email-transport.interface.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
# @zola_do/email
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@zola_do/email)
|
|
4
|
+
[](https://www.npmjs.com/package/@zola_do/email)
|
|
5
|
+
[](https://opensource.org/licenses/ISC)
|
|
6
|
+
|
|
7
|
+
Email service for NestJS applications with SMTP transport and Handlebars template support.
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
`@zola_do/email` provides:
|
|
12
|
+
|
|
13
|
+
- **SMTP Transport** — Configurable via environment variables
|
|
14
|
+
- **Handlebars Templates** — Server-side template rendering
|
|
15
|
+
- **HTML Emails** — Send rich HTML content
|
|
16
|
+
- **Connection Verification** — Test SMTP connection
|
|
17
|
+
- **NestJS Integration** — Module-based setup
|
|
4
18
|
|
|
5
19
|
## Installation
|
|
6
20
|
|
|
@@ -12,13 +26,31 @@ npm install @zola_do/email
|
|
|
12
26
|
npm install @zola_do/nestjs-shared
|
|
13
27
|
```
|
|
14
28
|
|
|
15
|
-
|
|
29
|
+
### Dependencies
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install @nestjs-modules/mailer nodemailer
|
|
33
|
+
npm install @types/nodemailer --save-dev
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Quick Start
|
|
16
37
|
|
|
17
|
-
###
|
|
38
|
+
### 1. Configure Environment
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# .env
|
|
42
|
+
EMAIL_SMTP=smtp.example.com
|
|
43
|
+
EMAIL_SMTP_PORT=587
|
|
44
|
+
EMAIL_SMTP_USERNAME=your-username
|
|
45
|
+
EMAIL_SMTP_PASSWORD=your-password
|
|
46
|
+
EMAIL_SMTP_DISPLAY_NAME=My App <noreply@example.com>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 2. Register Module
|
|
18
50
|
|
|
19
51
|
```typescript
|
|
20
|
-
import { Module } from
|
|
21
|
-
import { EmailModule } from
|
|
52
|
+
import { Module } from "@nestjs/common";
|
|
53
|
+
import { EmailModule } from "@zola_do/email";
|
|
22
54
|
|
|
23
55
|
@Module({
|
|
24
56
|
imports: [EmailModule],
|
|
@@ -26,50 +58,415 @@ import { EmailModule } from '@zola_do/email';
|
|
|
26
58
|
export class AppModule {}
|
|
27
59
|
```
|
|
28
60
|
|
|
29
|
-
###
|
|
30
|
-
|
|
31
|
-
Inject `EmailService` and send emails:
|
|
61
|
+
### 3. Send Emails
|
|
32
62
|
|
|
33
63
|
```typescript
|
|
34
|
-
import { Injectable } from
|
|
35
|
-
import { EmailService } from
|
|
64
|
+
import { Injectable } from "@nestjs/common";
|
|
65
|
+
import { EmailService } from "@zola_do/email";
|
|
36
66
|
|
|
37
67
|
@Injectable()
|
|
38
68
|
export class NotificationService {
|
|
39
69
|
constructor(private readonly emailService: EmailService) {}
|
|
40
70
|
|
|
41
71
|
async sendWelcomeEmail(userEmail: string, userName: string) {
|
|
42
|
-
|
|
43
|
-
|
|
72
|
+
await this.emailService.sendEmail({
|
|
73
|
+
to: userEmail,
|
|
74
|
+
subject: "Welcome to Our App",
|
|
75
|
+
html: `
|
|
76
|
+
<h1>Welcome, ${userName}!</h1>
|
|
77
|
+
<p>Thanks for signing up.</p>
|
|
78
|
+
`,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Email Architecture
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
88
|
+
│ Email Flow │
|
|
89
|
+
├─────────────────────────────────────────────────────────────────────┤
|
|
90
|
+
│ │
|
|
91
|
+
│ Controller/Service │
|
|
92
|
+
│ │ │
|
|
93
|
+
│ │ 1. Prepare email data │
|
|
94
|
+
│ ▼ │
|
|
95
|
+
│ ┌─────────────────────────────────────┐ │
|
|
96
|
+
│ │ EmailService │ │
|
|
97
|
+
│ │ │ │
|
|
98
|
+
│ │ sendEmail({ to, subject, html }) │ │
|
|
99
|
+
│ └──────────────────┬──────────────────┘ │
|
|
100
|
+
│ │ │
|
|
101
|
+
│ │ 2. Render template (optional) │
|
|
102
|
+
│ ▼ │
|
|
103
|
+
│ ┌─────────────────────────────────────┐ │
|
|
104
|
+
│ │ templates/ │ │
|
|
105
|
+
│ │ welcome.hbs │ │
|
|
106
|
+
│ └──────────────────┬──────────────────┘ │
|
|
107
|
+
│ │ │
|
|
108
|
+
│ │ 3. Send via SMTP │
|
|
109
|
+
│ ▼ │
|
|
110
|
+
│ ┌─────────────────────────────────────┐ │
|
|
111
|
+
│ │ nodemailer │ │
|
|
112
|
+
│ │ (SMTP Transport) │ │
|
|
113
|
+
│ └──────────────────┬──────────────────┘ │
|
|
114
|
+
│ │ │
|
|
115
|
+
│ ▼ │
|
|
116
|
+
│ ┌──────────────┐ │
|
|
117
|
+
│ │ SMTP │ │
|
|
118
|
+
│ │ Server │ │
|
|
119
|
+
│ └──────────────┘ │
|
|
120
|
+
│ │
|
|
121
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## EmailService
|
|
125
|
+
|
|
126
|
+
### Send HTML Email
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
async sendEmail(options: {
|
|
130
|
+
to: string | string[];
|
|
131
|
+
subject: string;
|
|
132
|
+
html: string;
|
|
133
|
+
from?: string;
|
|
134
|
+
replyTo?: string;
|
|
135
|
+
cc?: string | string[];
|
|
136
|
+
bcc?: string | string[];
|
|
137
|
+
attachments?: Attachment[];
|
|
138
|
+
}): Promise<SentMessageInfo>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Send with Template
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
import { MailerService } from "@nestjs-modules/mailer";
|
|
145
|
+
import { context } from "@handlebars/noder";
|
|
146
|
+
|
|
147
|
+
@Injectable()
|
|
148
|
+
export class NotificationService {
|
|
149
|
+
constructor(
|
|
150
|
+
private readonly emailService: EmailService,
|
|
151
|
+
private readonly mailerService: MailerService,
|
|
152
|
+
) {}
|
|
153
|
+
|
|
154
|
+
async sendWelcomeWithTemplate(user: User) {
|
|
155
|
+
await this.mailerService.sendMail({
|
|
156
|
+
to: user.email,
|
|
157
|
+
subject: "Welcome!",
|
|
158
|
+
template: "welcome", // Looks for templates/welcome.hbs
|
|
159
|
+
context: {
|
|
160
|
+
name: user.name,
|
|
161
|
+
verifyUrl: `https://example.com/verify/${user.token}`,
|
|
162
|
+
},
|
|
163
|
+
});
|
|
44
164
|
}
|
|
45
165
|
}
|
|
46
166
|
```
|
|
47
167
|
|
|
48
|
-
###
|
|
168
|
+
### Verify Connection
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
async testConnection() {
|
|
172
|
+
const isConnected = await this.emailService.verify();
|
|
173
|
+
if (!isConnected) {
|
|
174
|
+
throw new Error('Email service not available');
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Handlebars Templates
|
|
180
|
+
|
|
181
|
+
### Template Location
|
|
182
|
+
|
|
183
|
+
Templates should be placed in `process.cwd() + '/templates/'`:
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
project/
|
|
187
|
+
├── templates/
|
|
188
|
+
│ ├── welcome.hbs
|
|
189
|
+
│ ├── reset-password.hbs
|
|
190
|
+
│ └── invoice.hbs
|
|
191
|
+
├── src/
|
|
192
|
+
│ └── app.module.ts
|
|
193
|
+
└── package.json
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Template Example
|
|
49
197
|
|
|
50
|
-
|
|
198
|
+
```handlebars
|
|
199
|
+
{{! templates/welcome.hbs }}
|
|
200
|
+
|
|
201
|
+
<html>
|
|
202
|
+
<head>
|
|
203
|
+
<meta charset="utf-8" />
|
|
204
|
+
<title>Welcome</title>
|
|
205
|
+
<style>
|
|
206
|
+
body {
|
|
207
|
+
font-family: Arial, sans-serif;
|
|
208
|
+
}
|
|
209
|
+
.container {
|
|
210
|
+
max-width: 600px;
|
|
211
|
+
margin: 0 auto;
|
|
212
|
+
}
|
|
213
|
+
</style>
|
|
214
|
+
</head>
|
|
215
|
+
<body>
|
|
216
|
+
<div class="container">
|
|
217
|
+
<h1>Welcome, {{name}}!</h1>
|
|
218
|
+
<p>Thank you for joining {{appName}}.</p>
|
|
219
|
+
<a href="{{verifyUrl}}">Verify your email</a>
|
|
220
|
+
<p>Best,<br />The Team</p>
|
|
221
|
+
</div>
|
|
222
|
+
</body>
|
|
223
|
+
</html>
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Template Features
|
|
227
|
+
|
|
228
|
+
```handlebars
|
|
229
|
+
{{! Conditionals }}
|
|
230
|
+
{{#if user.isPremium}}
|
|
231
|
+
<p>You have premium access!</p>
|
|
232
|
+
{{else}}
|
|
233
|
+
<p>Upgrade to premium for more features.</p>
|
|
234
|
+
{{/if}}
|
|
235
|
+
|
|
236
|
+
{{! Loops }}
|
|
237
|
+
{{#each items}}
|
|
238
|
+
<li>{{ this.name }} - {{ this.price }}</li>
|
|
239
|
+
{{/each}}
|
|
240
|
+
|
|
241
|
+
{{! Partials }}
|
|
242
|
+
{{> header }}
|
|
243
|
+
{{> footer }}
|
|
244
|
+
|
|
245
|
+
{{! Helpers }}
|
|
246
|
+
{{formatDate date 'MMMM Do YYYY'}}
|
|
247
|
+
{{currency amount}}
|
|
248
|
+
{{uppercase name}}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## EmailConfig
|
|
252
|
+
|
|
253
|
+
### Default Configuration
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
{
|
|
257
|
+
transport: {
|
|
258
|
+
host: process.env.EMAIL_SMTP,
|
|
259
|
+
port: parseInt(process.env.EMAIL_SMTP_PORT, 10),
|
|
260
|
+
secure: false, // true for 465, false for other ports
|
|
261
|
+
auth: {
|
|
262
|
+
user: process.env.EMAIL_SMTP_USERNAME,
|
|
263
|
+
pass: process.env.EMAIL_SMTP_PASSWORD,
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
defaults: {
|
|
267
|
+
from: process.env.EMAIL_SMTP_DISPLAY_NAME || 'noreply@example.com',
|
|
268
|
+
},
|
|
269
|
+
template: {
|
|
270
|
+
dir: process.cwd() + '/templates/',
|
|
271
|
+
adapter: new HandlebarsAdapter(),
|
|
272
|
+
options: {
|
|
273
|
+
strict: true,
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
}
|
|
277
|
+
```
|
|
51
278
|
|
|
52
|
-
###
|
|
279
|
+
### Custom Configuration
|
|
53
280
|
|
|
54
281
|
```typescript
|
|
55
|
-
|
|
282
|
+
import { Module } from "@nestjs/common";
|
|
283
|
+
import { EmailModule, EmailConfig } from "@zola_do/email";
|
|
284
|
+
|
|
285
|
+
@Module({
|
|
286
|
+
imports: [
|
|
287
|
+
EmailModule.forRoot({
|
|
288
|
+
transport: {
|
|
289
|
+
host: "smtp.gmail.com",
|
|
290
|
+
port: 465,
|
|
291
|
+
secure: true,
|
|
292
|
+
auth: {
|
|
293
|
+
user: "your-email@gmail.com",
|
|
294
|
+
pass: "your-app-password",
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
defaults: {
|
|
298
|
+
from: '"My App" <noreply@myapp.com>',
|
|
299
|
+
},
|
|
300
|
+
template: {
|
|
301
|
+
dir: __dirname + "/templates",
|
|
302
|
+
adapter: new HandlebarsAdapter(),
|
|
303
|
+
},
|
|
304
|
+
} as EmailConfig),
|
|
305
|
+
],
|
|
306
|
+
})
|
|
307
|
+
export class AppModule {}
|
|
56
308
|
```
|
|
57
309
|
|
|
58
310
|
## Environment Variables
|
|
59
311
|
|
|
60
|
-
| Variable
|
|
61
|
-
|
|
62
|
-
| `EMAIL_SMTP`
|
|
63
|
-
| `EMAIL_SMTP_PORT`
|
|
64
|
-
| `EMAIL_SMTP_USERNAME`
|
|
65
|
-
| `EMAIL_SMTP_PASSWORD`
|
|
66
|
-
| `EMAIL_SMTP_DISPLAY_NAME` |
|
|
312
|
+
| Variable | Description | Default |
|
|
313
|
+
| ------------------------- | ----------------- | --------------------- |
|
|
314
|
+
| `EMAIL_SMTP` | SMTP host | Required |
|
|
315
|
+
| `EMAIL_SMTP_PORT` | SMTP port | `587` |
|
|
316
|
+
| `EMAIL_SMTP_USERNAME` | SMTP username | Required |
|
|
317
|
+
| `EMAIL_SMTP_PASSWORD` | SMTP password | Required |
|
|
318
|
+
| `EMAIL_SMTP_DISPLAY_NAME` | Default from name | `noreply@example.com` |
|
|
319
|
+
|
|
320
|
+
## SMTP Ports
|
|
321
|
+
|
|
322
|
+
| Port | Use | Secure |
|
|
323
|
+
| ------ | ----------- | -------- |
|
|
324
|
+
| `25` | Legacy | No |
|
|
325
|
+
| `465` | SMTPS | Yes |
|
|
326
|
+
| `587` | Submission | No (TLS) |
|
|
327
|
+
| `2525` | Alternative | No |
|
|
328
|
+
|
|
329
|
+
## Common Email Patterns
|
|
330
|
+
|
|
331
|
+
### Welcome Email
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
async sendWelcomeEmail(user: User) {
|
|
335
|
+
await this.mailerService.sendMail({
|
|
336
|
+
to: user.email,
|
|
337
|
+
subject: 'Welcome to Our Platform!',
|
|
338
|
+
template: 'welcome',
|
|
339
|
+
context: {
|
|
340
|
+
name: user.firstName,
|
|
341
|
+
verifyUrl: `${BASE_URL}/verify/${user.verificationToken}`,
|
|
342
|
+
},
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### Password Reset
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
async sendPasswordReset(user: User) {
|
|
351
|
+
await this.mailerService.sendMail({
|
|
352
|
+
to: user.email,
|
|
353
|
+
subject: 'Password Reset Request',
|
|
354
|
+
template: 'reset-password',
|
|
355
|
+
context: {
|
|
356
|
+
name: user.name,
|
|
357
|
+
resetUrl: `${BASE_URL}/reset-password/${user.resetToken}`,
|
|
358
|
+
expiresIn: '1 hour',
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Invoice Email
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
async sendInvoice(user: User, invoice: Invoice) {
|
|
368
|
+
await this.mailerService.sendMail({
|
|
369
|
+
to: user.email,
|
|
370
|
+
subject: `Invoice #${invoice.number}`,
|
|
371
|
+
template: 'invoice',
|
|
372
|
+
context: {
|
|
373
|
+
invoiceNumber: invoice.number,
|
|
374
|
+
amount: invoice.amount,
|
|
375
|
+
dueDate: invoice.dueDate,
|
|
376
|
+
items: invoice.items,
|
|
377
|
+
total: invoice.total,
|
|
378
|
+
},
|
|
379
|
+
attachments: [
|
|
380
|
+
{
|
|
381
|
+
filename: `invoice-${invoice.number}.pdf`,
|
|
382
|
+
path: invoice.pdfPath,
|
|
383
|
+
},
|
|
384
|
+
],
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
## API Reference
|
|
390
|
+
|
|
391
|
+
### EmailModule
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
// Default configuration from environment
|
|
395
|
+
EmailModule.forRoot()
|
|
396
|
+
|
|
397
|
+
// Custom configuration
|
|
398
|
+
EmailModule.forRoot(config: EmailConfig)
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
### EmailService
|
|
402
|
+
|
|
403
|
+
```typescript
|
|
404
|
+
class EmailService {
|
|
405
|
+
constructor(private readonly mailerService: MailerService) {}
|
|
406
|
+
|
|
407
|
+
async sendEmail(options: SendEmailOptions): Promise<SentMessageInfo>;
|
|
408
|
+
async verify(): Promise<boolean>;
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### SendEmailOptions
|
|
413
|
+
|
|
414
|
+
```typescript
|
|
415
|
+
interface SendEmailOptions {
|
|
416
|
+
to: string | string[];
|
|
417
|
+
subject: string;
|
|
418
|
+
html?: string;
|
|
419
|
+
text?: string;
|
|
420
|
+
from?: string;
|
|
421
|
+
replyTo?: string;
|
|
422
|
+
cc?: string | string[];
|
|
423
|
+
bcc?: string | string[];
|
|
424
|
+
attachments?: Attachment[];
|
|
425
|
+
}
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
## Troubleshooting
|
|
429
|
+
|
|
430
|
+
### Q: Emails not sending?
|
|
431
|
+
|
|
432
|
+
1. Check SMTP credentials in environment
|
|
433
|
+
2. Verify SMTP server is accessible
|
|
434
|
+
3. Test connection: `await emailService.verify()`
|
|
435
|
+
|
|
436
|
+
### Q: Templates not found?
|
|
437
|
+
|
|
438
|
+
Ensure templates are in `process.cwd() + '/templates/'`:
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
// Check template path
|
|
442
|
+
console.log(process.cwd() + "/templates/");
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
### Q: Handlebars syntax errors?
|
|
446
|
+
|
|
447
|
+
Enable strict mode to see detailed errors:
|
|
448
|
+
|
|
449
|
+
```typescript
|
|
450
|
+
template: {
|
|
451
|
+
options: { strict: true },
|
|
452
|
+
}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
### Q: Gmail authentication fails?
|
|
456
|
+
|
|
457
|
+
If using Gmail with 2FA, create an App Password:
|
|
458
|
+
|
|
459
|
+
1. Google Account → Security
|
|
460
|
+
2. 2-Step Verification → App Passwords
|
|
461
|
+
3. Generate new app password for your app
|
|
462
|
+
|
|
463
|
+
## Related Packages
|
|
464
|
+
|
|
465
|
+
None - standalone email package
|
|
67
466
|
|
|
68
|
-
##
|
|
467
|
+
## License
|
|
69
468
|
|
|
70
|
-
|
|
71
|
-
- `EmailService` — Send emails and verify connection
|
|
72
|
-
- `EmailConfig` — Mailer configuration factory
|
|
469
|
+
ISC
|
|
73
470
|
|
|
74
471
|
## Community
|
|
75
472
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email-transport.interface.js","sourceRoot":"","sources":["../src/email-transport.interface.ts"],"names":[],"mappings":""}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -17,4 +17,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
__exportStar(require("./email.config"), exports);
|
|
18
18
|
__exportStar(require("./email.service"), exports);
|
|
19
19
|
__exportStar(require("./email.module"), exports);
|
|
20
|
+
__exportStar(require("./email-transport.interface"), exports);
|
|
20
21
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,iDAA+B;AAC/B,kDAAgC;AAChC,iDAA+B"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,iDAA+B;AAC/B,kDAAgC;AAChC,iDAA+B;AAC/B,8DAA4C"}
|