@alis-kit/mailer 0.1.0
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 +284 -0
- package/dist/core/MailerKit.d.ts +90 -0
- package/dist/core/MailerKit.d.ts.map +1 -0
- package/dist/core/MailerKit.js +179 -0
- package/dist/core/MailerKit.js.map +1 -0
- package/dist/decorators/EmailTemplate.d.ts +39 -0
- package/dist/decorators/EmailTemplate.d.ts.map +1 -0
- package/dist/decorators/EmailTemplate.js +58 -0
- package/dist/decorators/EmailTemplate.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/BaseProvider.d.ts +14 -0
- package/dist/providers/BaseProvider.d.ts.map +1 -0
- package/dist/providers/BaseProvider.js +3 -0
- package/dist/providers/BaseProvider.js.map +1 -0
- package/dist/providers/NodemailerProvider.d.ts +7 -0
- package/dist/providers/NodemailerProvider.d.ts.map +1 -0
- package/dist/providers/NodemailerProvider.js +26 -0
- package/dist/providers/NodemailerProvider.js.map +1 -0
- package/dist/template/TemplateEngine.d.ts +8 -0
- package/dist/template/TemplateEngine.d.ts.map +1 -0
- package/dist/template/TemplateEngine.js +33 -0
- package/dist/template/TemplateEngine.js.map +1 -0
- package/dist/template/TemplatePath.d.ts +2 -0
- package/dist/template/TemplatePath.d.ts.map +1 -0
- package/dist/template/TemplatePath.js +9 -0
- package/dist/template/TemplatePath.js.map +1 -0
- package/dist/types/index.d.ts +51 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/vitest.config.d.ts +3 -0
- package/dist/vitest.config.d.ts.map +1 -0
- package/dist/vitest.config.js +13 -0
- package/dist/vitest.config.js.map +1 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
# Mailer Kit
|
|
2
|
+
|
|
3
|
+
Email delivery abstraction with a template engine and TC39 native decorators (Stage 3).
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **TC39 Native Decorators** — Uses stage 3 decorators with `Symbol.metadata`, no `reflect-metadata` needed
|
|
8
|
+
- **Template Engine** — Dynamic `${variable}` and `${nested.variable}` interpolation
|
|
9
|
+
- **Provider Agnostic** — Switch between Nodemailer, Resend, or custom providers
|
|
10
|
+
- **Async Queues** — Send emails synchronously or queue them for background delivery
|
|
11
|
+
- **Type Safe** — Full TypeScript support with strict mode
|
|
12
|
+
|
|
13
|
+
## Requirements
|
|
14
|
+
|
|
15
|
+
- **Node.js** >= 18.0.0
|
|
16
|
+
- **TypeScript** >= 5.5.0
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @alis-kit/mailer
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
### 1. Setup
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { MailerKit } from "@alis-kit/mailer"
|
|
30
|
+
|
|
31
|
+
MailerKit.setup({
|
|
32
|
+
provider: "nodemailer",
|
|
33
|
+
config: {
|
|
34
|
+
host: "smtp.gmail.com",
|
|
35
|
+
port: 587,
|
|
36
|
+
auth: {
|
|
37
|
+
user: process.env.SMTP_USER,
|
|
38
|
+
pass: process.env.SMTP_PASS
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
from: "noreply@example.com"
|
|
42
|
+
})
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 2. Define a Template
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { EmailTemplate } from "@alis-kit/mailer"
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Welcome email sent to new users after registration.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* await MailerKit.send(WelcomeEmail, {
|
|
56
|
+
* to: "user@example.com",
|
|
57
|
+
* name: "John"
|
|
58
|
+
* })
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
@EmailTemplate("welcome", {
|
|
62
|
+
template: "./templates/welcome.html",
|
|
63
|
+
subject: "Welcome to Our Platform!"
|
|
64
|
+
})
|
|
65
|
+
export class WelcomeEmail {
|
|
66
|
+
to!: string
|
|
67
|
+
name!: string
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 3. Create the HTML Template
|
|
72
|
+
|
|
73
|
+
```html
|
|
74
|
+
<!-- templates/welcome.html -->
|
|
75
|
+
<h1>Welcome, ${name}!</h1>
|
|
76
|
+
<p>We're excited to have you on board.</p>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 4. Send
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
await MailerKit.send(WelcomeEmail, {
|
|
83
|
+
to: "user@example.com",
|
|
84
|
+
name: "John"
|
|
85
|
+
})
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## API Reference
|
|
89
|
+
|
|
90
|
+
### `MailerKit.setup(config)`
|
|
91
|
+
|
|
92
|
+
Initialize the mailer service. Must be called before sending.
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
MailerKit.setup({
|
|
96
|
+
provider: "nodemailer",
|
|
97
|
+
config: {
|
|
98
|
+
host: "smtp.example.com",
|
|
99
|
+
port: 587,
|
|
100
|
+
auth: { user: "...", pass: "..." }
|
|
101
|
+
},
|
|
102
|
+
from: "sender@example.com",
|
|
103
|
+
queue: {
|
|
104
|
+
engine: "memory" // or "bullmq"
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
| Option | Type | Required | Description |
|
|
110
|
+
|--------|------|----------|-------------|
|
|
111
|
+
| `provider` | `"nodemailer"` | Yes | Email provider to use |
|
|
112
|
+
| `config` | `object` | Yes | Provider-specific configuration |
|
|
113
|
+
| `from` | `string` | Yes | Default sender email address |
|
|
114
|
+
| `queue.engine` | `"memory" \| "bullmq"` | No | Queue engine for async delivery |
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
### `@EmailTemplate(name, options)`
|
|
119
|
+
|
|
120
|
+
Class decorator that links a class to an HTML template file. Uses TC39 stage 3 native decorators.
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
@EmailTemplate("notification", {
|
|
124
|
+
template: "./templates/notification.html",
|
|
125
|
+
subject: "You have a new notification"
|
|
126
|
+
})
|
|
127
|
+
export class NotificationEmail {
|
|
128
|
+
to!: string
|
|
129
|
+
title!: string
|
|
130
|
+
message!: string
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
| Parameter | Type | Required | Description |
|
|
135
|
+
|-----------|------|----------|-------------|
|
|
136
|
+
| `name` | `string` | Yes | Unique identifier for the template |
|
|
137
|
+
| `options.template` | `string` | Yes | Path to the HTML template file |
|
|
138
|
+
| `options.subject` | `string` | No | Default subject line |
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
### `MailerKit.send(TemplateClass, data)`
|
|
143
|
+
|
|
144
|
+
Send an email immediately.
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
await MailerKit.send(NotificationEmail, {
|
|
148
|
+
to: "user@example.com",
|
|
149
|
+
title: "New Message",
|
|
150
|
+
message: "You have a new notification!"
|
|
151
|
+
})
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
| Parameter | Type | Description |
|
|
155
|
+
|-----------|------|-------------|
|
|
156
|
+
| `TemplateClass` | `Class` | A class decorated with `@EmailTemplate` |
|
|
157
|
+
| `data` | `object` | Email data including `to`, template variables, and optional overrides |
|
|
158
|
+
|
|
159
|
+
**Data properties:**
|
|
160
|
+
|
|
161
|
+
| Property | Type | Description |
|
|
162
|
+
|----------|------|-------------|
|
|
163
|
+
| `to` | `string \| string[]` | Recipient(s) — **required** |
|
|
164
|
+
| `subject` | `string` | Override default subject |
|
|
165
|
+
| `cc` | `string \| string[]` | CC recipients |
|
|
166
|
+
| `bcc` | `string \| string[]` | BCC recipients |
|
|
167
|
+
| `replyTo` | `string` | Reply-to address |
|
|
168
|
+
| `attachments` | `Attachment[]` | File attachments (pdf, doc, docx, xls, xlsx, jpg, jpeg, png, zip) |
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
### `MailerKit.queue(TemplateClass, data, options?)`
|
|
173
|
+
|
|
174
|
+
Queue an email for asynchronous delivery.
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
await MailerKit.queue(WelcomeEmail, {
|
|
178
|
+
to: "user@example.com",
|
|
179
|
+
name: "John"
|
|
180
|
+
}, { delay: "5s" })
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
| Option | Type | Description |
|
|
184
|
+
|--------|------|-------------|
|
|
185
|
+
| `delay` | `string` | Delay before sending (e.g., `"5s"`, `"2m"`, `"1h"`) |
|
|
186
|
+
| `priority` | `number` | Priority level (for BullMQ) |
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
### `TemplateEngine.render(templatePath, variables)`
|
|
191
|
+
|
|
192
|
+
Render an HTML template with variable interpolation.
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
import { TemplateEngine } from "@alis-kit/mailer"
|
|
196
|
+
|
|
197
|
+
const html = await TemplateEngine.render("./template.html", {
|
|
198
|
+
name: "John",
|
|
199
|
+
user: { email: "john@example.com" }
|
|
200
|
+
})
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**Supported syntax:**
|
|
204
|
+
|
|
205
|
+
| Syntax | Example | Description |
|
|
206
|
+
|--------|---------|-------------|
|
|
207
|
+
| `${var}` | `${name}` | Simple variable |
|
|
208
|
+
| `${nested}` | `${user.email}` | Dot-notation for nested objects |
|
|
209
|
+
| `${array}` | `${items}` | Arrays are JSON-stringified |
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
### `MailerKit.getTemplateMetadata(TemplateClass)`
|
|
214
|
+
|
|
215
|
+
Retrieve the metadata attached to a decorated class.
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
const metadata = MailerKit.getTemplateMetadata(WelcomeEmail)
|
|
219
|
+
// { name: "welcome", template: "./templates/welcome.html", subject: "Welcome!" }
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Template Variables
|
|
223
|
+
|
|
224
|
+
Properties defined in your decorated class act as template variables:
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
@EmailTemplate("invoice", {
|
|
228
|
+
template: "./templates/invoice.html",
|
|
229
|
+
subject: "Your Invoice"
|
|
230
|
+
})
|
|
231
|
+
export class InvoiceEmail {
|
|
232
|
+
to!: string
|
|
233
|
+
invoiceNumber!: string
|
|
234
|
+
total!: number
|
|
235
|
+
items!: Array<{ name: string; price: number }>
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
```html
|
|
240
|
+
<!-- templates/invoice.html -->
|
|
241
|
+
<h1>Invoice #${invoiceNumber}</h1>
|
|
242
|
+
<p>Total: $${total}</p>
|
|
243
|
+
<ul>
|
|
244
|
+
${items}
|
|
245
|
+
</ul>
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## Error Handling
|
|
249
|
+
|
|
250
|
+
| Error | Cause |
|
|
251
|
+
|-------|-------|
|
|
252
|
+
| `MailerKit not initialized. Call setup() first.` | `send()` or `queue()` called before `setup()` |
|
|
253
|
+
| `Unsupported mail provider: <name>` | Invalid provider in `setup()` config |
|
|
254
|
+
| `Class <Name> is not a valid @EmailTemplate` | Class not decorated with `@EmailTemplate` |
|
|
255
|
+
| `Class <Name> is already decorated with @EmailTemplate` | Duplicate `@EmailTemplate` on same class |
|
|
256
|
+
| `Unsupported attachment type: .<ext>` | Attachment file extension not in allowed list |
|
|
257
|
+
| `Template file not found at: <path>` | HTML template file does not exist |
|
|
258
|
+
| `Queue not configured` | `queue()` called without `queue` in config |
|
|
259
|
+
| `BullMQ integration not implemented` | Using `"bullmq"` engine (not yet supported) |
|
|
260
|
+
|
|
261
|
+
## Testing
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
# Run all tests
|
|
265
|
+
npm test
|
|
266
|
+
|
|
267
|
+
# Run tests in watch mode
|
|
268
|
+
npm run test:watch
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Architecture
|
|
272
|
+
|
|
273
|
+
This project follows the **Functional Core + Decorator Sugar** pattern:
|
|
274
|
+
|
|
275
|
+
- **`core/`** — Pure functions and classes with all business logic
|
|
276
|
+
- **`decorators/`** — Thin wrappers that store metadata via `Symbol.metadata`
|
|
277
|
+
- **`providers/`** — Email provider adapters (Nodemailer, Resend, etc.)
|
|
278
|
+
- **`template/`** — Template engine and path resolution
|
|
279
|
+
|
|
280
|
+
Decorators never contain business logic — they only attach metadata that `core/` reads.
|
|
281
|
+
|
|
282
|
+
## License
|
|
283
|
+
|
|
284
|
+
ISC
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { MailerKitConfig, SendData, QueueOptions } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Core mailer service that orchestrates email sending and queuing.
|
|
4
|
+
*
|
|
5
|
+
* Reads template metadata from `@EmailTemplate` decorated classes via WeakMap
|
|
6
|
+
* storage — no `reflect-metadata` or `Symbol.metadata` runtime required.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* MailerKit.setup({
|
|
11
|
+
* provider: "nodemailer",
|
|
12
|
+
* config: { host: "smtp.gmail.com", port: 587, auth: { user: "...", pass: "..." } },
|
|
13
|
+
* from: "noreply@example.com"
|
|
14
|
+
* })
|
|
15
|
+
*
|
|
16
|
+
* await MailerKit.send(WelcomeEmail, { to: "user@example.com", name: "John" })
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare class MailerKit {
|
|
20
|
+
private static config;
|
|
21
|
+
private static provider;
|
|
22
|
+
/**
|
|
23
|
+
* Initialize MailerKit with a provider configuration.
|
|
24
|
+
*
|
|
25
|
+
* @param config - MailerKit configuration including provider and credentials.
|
|
26
|
+
* @throws {Error} If the specified provider is not supported.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* MailerKit.setup({
|
|
31
|
+
* provider: "nodemailer",
|
|
32
|
+
* config: { host: "smtp.example.com", port: 587, auth: { user: "u", pass: "p" } },
|
|
33
|
+
* from: "sender@example.com"
|
|
34
|
+
* })
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
static setup(config: MailerKitConfig): void;
|
|
38
|
+
/**
|
|
39
|
+
* Retrieve the template metadata attached to a decorated class.
|
|
40
|
+
*
|
|
41
|
+
* Uses WeakMap storage (compatible with all transpilers) — no `reflect-metadata` needed.
|
|
42
|
+
*
|
|
43
|
+
* @param TemplateClass - The class constructor to inspect.
|
|
44
|
+
* @returns The template metadata object with `name`, `template`, and optional `subject`.
|
|
45
|
+
* @throws {Error} If the class does not have `@EmailTemplate` metadata.
|
|
46
|
+
*/
|
|
47
|
+
static getTemplateMetadata<T>(TemplateClass: new () => T): {
|
|
48
|
+
name: string;
|
|
49
|
+
template: string;
|
|
50
|
+
subject?: string;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Send an email immediately using a template class.
|
|
54
|
+
*
|
|
55
|
+
* @param TemplateClass - A class decorated with `@EmailTemplate`.
|
|
56
|
+
* @param data - Email data including recipient(s), template variables, and optional overrides.
|
|
57
|
+
* @throws {Error} If MailerKit is not initialized or the class lacks `@EmailTemplate` metadata.
|
|
58
|
+
* @throws {Error} If an attachment has an unsupported file extension.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* await MailerKit.send(WelcomeEmail, {
|
|
63
|
+
* to: "user@example.com",
|
|
64
|
+
* name: "John"
|
|
65
|
+
* })
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
static send<T>(TemplateClass: new () => T, data: SendData<T>): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Queue an email for asynchronous delivery.
|
|
71
|
+
*
|
|
72
|
+
* Currently supports the `"memory"` engine with configurable delay.
|
|
73
|
+
* BullMQ support is planned for a future release.
|
|
74
|
+
*
|
|
75
|
+
* @param TemplateClass - A class decorated with `@EmailTemplate`.
|
|
76
|
+
* @param data - Email data including recipient(s) and template variables.
|
|
77
|
+
* @param options - Optional queue options (delay, priority).
|
|
78
|
+
* @throws {Error} If queue is not configured or uses an unsupported engine.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```ts
|
|
82
|
+
* await MailerKit.queue(WelcomeEmail, {
|
|
83
|
+
* to: "user@example.com",
|
|
84
|
+
* name: "John"
|
|
85
|
+
* }, { delay: "5s" })
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
static queue<T>(TemplateClass: new () => T, data: SendData<T>, options?: QueueOptions): Promise<void>;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=MailerKit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MailerKit.d.ts","sourceRoot":"","sources":["../../core/MailerKit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAW3E;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAC,MAAM,CAAiB;IACtC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAc;IAErC;;;;;;;;;;;;;;OAcG;IACH,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,eAAe;IAYpC;;;;;;;;OAQG;IACH,MAAM,CAAC,mBAAmB,CAAC,CAAC,EAC1B,aAAa,EAAE,UAAU,CAAC,GACzB;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE;IAWvD;;;;;;;;;;;;;;;OAeG;WACU,IAAI,CAAC,CAAC,EAAE,aAAa,EAAE,UAAU,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAyBlF;;;;;;;;;;;;;;;;;;OAkBG;WACU,KAAK,CAAC,CAAC,EAClB,aAAa,EAAE,UAAU,CAAC,EAC1B,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,EACjB,OAAO,CAAC,EAAE,YAAY,GACrB,OAAO,CAAC,IAAI,CAAC;CAcjB"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { getEmailTemplateMetadata } from "../decorators/EmailTemplate.js";
|
|
2
|
+
import { TemplateEngine } from "../template/TemplateEngine.js";
|
|
3
|
+
import { TemplatePath } from "../template/TemplatePath.js";
|
|
4
|
+
import { NodemailerProvider } from "../providers/NodemailerProvider.js";
|
|
5
|
+
const ALLOWED_ATTACHMENT_EXTENSIONS = [
|
|
6
|
+
"pdf", "doc", "docx", "xls", "xlsx", "jpg", "jpeg", "png", "zip"
|
|
7
|
+
];
|
|
8
|
+
/**
|
|
9
|
+
* Core mailer service that orchestrates email sending and queuing.
|
|
10
|
+
*
|
|
11
|
+
* Reads template metadata from `@EmailTemplate` decorated classes via WeakMap
|
|
12
|
+
* storage — no `reflect-metadata` or `Symbol.metadata` runtime required.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* MailerKit.setup({
|
|
17
|
+
* provider: "nodemailer",
|
|
18
|
+
* config: { host: "smtp.gmail.com", port: 587, auth: { user: "...", pass: "..." } },
|
|
19
|
+
* from: "noreply@example.com"
|
|
20
|
+
* })
|
|
21
|
+
*
|
|
22
|
+
* await MailerKit.send(WelcomeEmail, { to: "user@example.com", name: "John" })
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export class MailerKit {
|
|
26
|
+
static config;
|
|
27
|
+
static provider;
|
|
28
|
+
/**
|
|
29
|
+
* Initialize MailerKit with a provider configuration.
|
|
30
|
+
*
|
|
31
|
+
* @param config - MailerKit configuration including provider and credentials.
|
|
32
|
+
* @throws {Error} If the specified provider is not supported.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* MailerKit.setup({
|
|
37
|
+
* provider: "nodemailer",
|
|
38
|
+
* config: { host: "smtp.example.com", port: 587, auth: { user: "u", pass: "p" } },
|
|
39
|
+
* from: "sender@example.com"
|
|
40
|
+
* })
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
static setup(config) {
|
|
44
|
+
this.config = config;
|
|
45
|
+
switch (config.provider) {
|
|
46
|
+
case "nodemailer":
|
|
47
|
+
this.provider = new NodemailerProvider(config.config);
|
|
48
|
+
break;
|
|
49
|
+
default:
|
|
50
|
+
throw new Error(`Unsupported mail provider: ${config.provider}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Retrieve the template metadata attached to a decorated class.
|
|
55
|
+
*
|
|
56
|
+
* Uses WeakMap storage (compatible with all transpilers) — no `reflect-metadata` needed.
|
|
57
|
+
*
|
|
58
|
+
* @param TemplateClass - The class constructor to inspect.
|
|
59
|
+
* @returns The template metadata object with `name`, `template`, and optional `subject`.
|
|
60
|
+
* @throws {Error} If the class does not have `@EmailTemplate` metadata.
|
|
61
|
+
*/
|
|
62
|
+
static getTemplateMetadata(TemplateClass) {
|
|
63
|
+
const metadata = getEmailTemplateMetadata(TemplateClass);
|
|
64
|
+
if (!metadata) {
|
|
65
|
+
throw new Error(`Class ${TemplateClass.name} is not a valid @EmailTemplate. ` +
|
|
66
|
+
`Ensure the class is decorated with @EmailTemplate("name", { template: "..." }).`);
|
|
67
|
+
}
|
|
68
|
+
return metadata;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Send an email immediately using a template class.
|
|
72
|
+
*
|
|
73
|
+
* @param TemplateClass - A class decorated with `@EmailTemplate`.
|
|
74
|
+
* @param data - Email data including recipient(s), template variables, and optional overrides.
|
|
75
|
+
* @throws {Error} If MailerKit is not initialized or the class lacks `@EmailTemplate` metadata.
|
|
76
|
+
* @throws {Error} If an attachment has an unsupported file extension.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* await MailerKit.send(WelcomeEmail, {
|
|
81
|
+
* to: "user@example.com",
|
|
82
|
+
* name: "John"
|
|
83
|
+
* })
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
static async send(TemplateClass, data) {
|
|
87
|
+
if (!this.provider) {
|
|
88
|
+
throw new Error("MailerKit not initialized. Call setup() first.");
|
|
89
|
+
}
|
|
90
|
+
const metadata = this.getTemplateMetadata(TemplateClass);
|
|
91
|
+
const html = await TemplateEngine.render(TemplatePath(metadata.template), data);
|
|
92
|
+
const subject = data.subject || metadata.subject || "No Subject";
|
|
93
|
+
if (data.attachments) {
|
|
94
|
+
validateAttachments(data.attachments);
|
|
95
|
+
}
|
|
96
|
+
await this.provider.send({
|
|
97
|
+
from: this.config.from,
|
|
98
|
+
to: data.to,
|
|
99
|
+
subject,
|
|
100
|
+
html,
|
|
101
|
+
cc: data.cc,
|
|
102
|
+
bcc: data.bcc,
|
|
103
|
+
replyTo: data.replyTo,
|
|
104
|
+
attachments: data.attachments
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Queue an email for asynchronous delivery.
|
|
109
|
+
*
|
|
110
|
+
* Currently supports the `"memory"` engine with configurable delay.
|
|
111
|
+
* BullMQ support is planned for a future release.
|
|
112
|
+
*
|
|
113
|
+
* @param TemplateClass - A class decorated with `@EmailTemplate`.
|
|
114
|
+
* @param data - Email data including recipient(s) and template variables.
|
|
115
|
+
* @param options - Optional queue options (delay, priority).
|
|
116
|
+
* @throws {Error} If queue is not configured or uses an unsupported engine.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```ts
|
|
120
|
+
* await MailerKit.queue(WelcomeEmail, {
|
|
121
|
+
* to: "user@example.com",
|
|
122
|
+
* name: "John"
|
|
123
|
+
* }, { delay: "5s" })
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
static async queue(TemplateClass, data, options) {
|
|
127
|
+
if (!this.config.queue) {
|
|
128
|
+
throw new Error("Queue not configured");
|
|
129
|
+
}
|
|
130
|
+
if (this.config.queue.engine === "memory") {
|
|
131
|
+
const delayMs = parseDelay(options?.delay || "0s");
|
|
132
|
+
setTimeout(() => {
|
|
133
|
+
this.send(TemplateClass, data).catch(console.error);
|
|
134
|
+
}, delayMs);
|
|
135
|
+
}
|
|
136
|
+
else if (this.config.queue.engine === "bullmq") {
|
|
137
|
+
throw new Error("BullMQ integration not implemented in this version");
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Validate that all attachments have allowed file extensions.
|
|
143
|
+
*
|
|
144
|
+
* @param attachments - Array of attachment objects to validate.
|
|
145
|
+
* @throws {Error} If any attachment has an unsupported file extension.
|
|
146
|
+
*/
|
|
147
|
+
function validateAttachments(attachments) {
|
|
148
|
+
for (const att of attachments) {
|
|
149
|
+
const ext = att.path.split(".").pop()?.toLowerCase();
|
|
150
|
+
if (!ext || !ALLOWED_ATTACHMENT_EXTENSIONS.includes(ext)) {
|
|
151
|
+
throw new Error(`Unsupported attachment type: .${ext}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Parse a human-readable delay string into milliseconds.
|
|
157
|
+
*
|
|
158
|
+
* @param delay - Delay string like `"5s"`, `"2m"`, `"1h"`, `"1d"`.
|
|
159
|
+
* @returns The delay in milliseconds.
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* ```ts
|
|
163
|
+
* parseDelay("5s") // 5000
|
|
164
|
+
* parseDelay("2m") // 120000
|
|
165
|
+
* parseDelay("1h") // 3600000
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
function parseDelay(delay) {
|
|
169
|
+
const unit = delay.slice(-1);
|
|
170
|
+
const value = parseInt(delay.slice(0, -1));
|
|
171
|
+
switch (unit) {
|
|
172
|
+
case "s": return value * 1000;
|
|
173
|
+
case "m": return value * 60 * 1000;
|
|
174
|
+
case "h": return value * 60 * 60 * 1000;
|
|
175
|
+
case "d": return value * 24 * 60 * 60 * 1000;
|
|
176
|
+
default: return value;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=MailerKit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MailerKit.js","sourceRoot":"","sources":["../../core/MailerKit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { EmailTemplateOptions } from "../types/index.js";
|
|
2
|
+
export declare const EMAIL_TEMPLATE_METADATA = "mailer:template";
|
|
3
|
+
/**
|
|
4
|
+
* Class decorator that links a class to an email template file.
|
|
5
|
+
*
|
|
6
|
+
* Stores template metadata using WeakMap (compatible with all transpilers)
|
|
7
|
+
* and optionally on `Symbol.metadata` when the runtime supports it.
|
|
8
|
+
*
|
|
9
|
+
* @param name - Unique identifier for the template.
|
|
10
|
+
* @param options - Configuration object for the template.
|
|
11
|
+
* @param options.template - Path to the HTML template file.
|
|
12
|
+
* @param options.subject - Default subject line for the email.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* @EmailTemplate("welcome", {
|
|
17
|
+
* template: "./templates/welcome.html",
|
|
18
|
+
* subject: "Welcome to our platform!"
|
|
19
|
+
* })
|
|
20
|
+
* export class WelcomeEmail {
|
|
21
|
+
* to!: string
|
|
22
|
+
* name!: string
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare function EmailTemplate(name: string, options: EmailTemplateOptions): (target: unknown, context: ClassDecoratorContext) => void;
|
|
27
|
+
/**
|
|
28
|
+
* Retrieve the template metadata attached to a decorated class.
|
|
29
|
+
*
|
|
30
|
+
* Reads from WeakMap storage first (always available),
|
|
31
|
+
* then falls back to `Symbol.metadata` if present.
|
|
32
|
+
*
|
|
33
|
+
* @param TemplateClass - The class constructor to inspect.
|
|
34
|
+
* @returns The template metadata object, or `undefined` if not decorated.
|
|
35
|
+
*/
|
|
36
|
+
export declare function getEmailTemplateMetadata(TemplateClass: object): (EmailTemplateOptions & {
|
|
37
|
+
name: string;
|
|
38
|
+
}) | undefined;
|
|
39
|
+
//# sourceMappingURL=EmailTemplate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EmailTemplate.d.ts","sourceRoot":"","sources":["../../decorators/EmailTemplate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAExD,eAAO,MAAM,uBAAuB,oBAAoB,CAAA;AAWxD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB,IAC/D,QAAQ,OAAO,EAAE,SAAS,qBAAqB,UAgBxD;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACtC,aAAa,EAAE,MAAM,GACpB,CAAC,oBAAoB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,SAAS,CAEvD"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export const EMAIL_TEMPLATE_METADATA = "mailer:template";
|
|
2
|
+
/**
|
|
3
|
+
* WeakMap storage for template metadata, keyed by class constructor.
|
|
4
|
+
*
|
|
5
|
+
* TC39 decorators (stage 3) with `context.metadata` require full runtime support.
|
|
6
|
+
* Since esbuild/Vite doesn't fully implement `context.metadata` yet,
|
|
7
|
+
* we use WeakMap as the primary storage mechanism.
|
|
8
|
+
*/
|
|
9
|
+
const templateMetadataStore = new WeakMap();
|
|
10
|
+
/**
|
|
11
|
+
* Class decorator that links a class to an email template file.
|
|
12
|
+
*
|
|
13
|
+
* Stores template metadata using WeakMap (compatible with all transpilers)
|
|
14
|
+
* and optionally on `Symbol.metadata` when the runtime supports it.
|
|
15
|
+
*
|
|
16
|
+
* @param name - Unique identifier for the template.
|
|
17
|
+
* @param options - Configuration object for the template.
|
|
18
|
+
* @param options.template - Path to the HTML template file.
|
|
19
|
+
* @param options.subject - Default subject line for the email.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* @EmailTemplate("welcome", {
|
|
24
|
+
* template: "./templates/welcome.html",
|
|
25
|
+
* subject: "Welcome to our platform!"
|
|
26
|
+
* })
|
|
27
|
+
* export class WelcomeEmail {
|
|
28
|
+
* to!: string
|
|
29
|
+
* name!: string
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function EmailTemplate(name, options) {
|
|
34
|
+
return (target, context) => {
|
|
35
|
+
const key = target;
|
|
36
|
+
if (templateMetadataStore.has(key)) {
|
|
37
|
+
throw new Error(`Class ${String(context.name)} is already decorated with @EmailTemplate`);
|
|
38
|
+
}
|
|
39
|
+
const metadata = { name, ...options };
|
|
40
|
+
templateMetadataStore.set(key, metadata);
|
|
41
|
+
if (context.metadata) {
|
|
42
|
+
context.metadata[EMAIL_TEMPLATE_METADATA] = metadata;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Retrieve the template metadata attached to a decorated class.
|
|
48
|
+
*
|
|
49
|
+
* Reads from WeakMap storage first (always available),
|
|
50
|
+
* then falls back to `Symbol.metadata` if present.
|
|
51
|
+
*
|
|
52
|
+
* @param TemplateClass - The class constructor to inspect.
|
|
53
|
+
* @returns The template metadata object, or `undefined` if not decorated.
|
|
54
|
+
*/
|
|
55
|
+
export function getEmailTemplateMetadata(TemplateClass) {
|
|
56
|
+
return templateMetadataStore.get(TemplateClass);
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=EmailTemplate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EmailTemplate.js","sourceRoot":"","sources":["../../decorators/EmailTemplate.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,uBAAuB,GAAG,iBAAiB,CAAA;AAExD;;;;;;GAMG;AACH,MAAM,qBAAqB,GAAG,IAAI,OAAO,EAAmD,CAAA;AAE5F;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,OAA6B;IACvE,OAAO,CAAC,MAAe,EAAE,OAA8B,EAAE,EAAE;QACzD,MAAM,GAAG,GAAG,MAAgB,CAAA;QAE5B,IAAI,qBAAqB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACnC,MAAM,IAAI,KAAK,CACb,SAAS,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,2CAA2C,CACzE,CAAA;QACH,CAAC;QAED,MAAM,QAAQ,GAAG,EAAE,IAAI,EAAE,GAAG,OAAO,EAAE,CAAA;QACrC,qBAAqB,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAA;QAExC,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YACrB,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAC,GAAG,QAAQ,CAAA;QACtD,CAAC;IACH,CAAC,CAAA;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,wBAAwB,CACtC,aAAqB;IAErB,OAAO,qBAAqB,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;AACjD,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,cAAc,+BAA+B,CAAA;AAC7C,cAAc,qBAAqB,CAAA;AACnC,cAAc,kBAAkB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,cAAc,+BAA+B,CAAA;AAC7C,cAAc,qBAAqB,CAAA;AACnC,cAAc,kBAAkB,CAAA"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Attachment } from "../types/index.js";
|
|
2
|
+
export declare abstract class BaseProvider {
|
|
3
|
+
abstract send(options: {
|
|
4
|
+
from: string;
|
|
5
|
+
to: string | string[];
|
|
6
|
+
subject: string;
|
|
7
|
+
html: string;
|
|
8
|
+
cc?: string | string[];
|
|
9
|
+
bcc?: string | string[];
|
|
10
|
+
replyTo?: string;
|
|
11
|
+
attachments?: Attachment[];
|
|
12
|
+
}): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=BaseProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BaseProvider.d.ts","sourceRoot":"","sources":["../../providers/BaseProvider.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAE9C,8BAAsB,YAAY;IAChC,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE;QACrB,IAAI,EAAE,MAAM,CAAA;QACZ,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;QACrB,OAAO,EAAE,MAAM,CAAA;QACf,IAAI,EAAE,MAAM,CAAA;QACZ,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;QACtB,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;QACvB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,WAAW,CAAC,EAAE,UAAU,EAAE,CAAA;KAC3B,GAAG,OAAO,CAAC,IAAI,CAAC;CAClB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BaseProvider.js","sourceRoot":"","sources":["../../providers/BaseProvider.ts"],"names":[],"mappings":"AAGA,MAAM,OAAgB,YAAY;CAWjC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NodemailerProvider.d.ts","sourceRoot":"","sources":["../../providers/NodemailerProvider.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGhD,qBAAa,kBAAmB,SAAQ,YAAY;IAClD,OAAO,CAAC,WAAW,CAAK;gBAEZ,MAAM,EAAE,GAAG;IAKjB,IAAI,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;CAexC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// MONAGE-PATCH: add .js extensions to relative imports + replace require() with ESM import for NodeNext compatibility
|
|
2
|
+
import nodemailer from "nodemailer";
|
|
3
|
+
import { BaseProvider } from "./BaseProvider.js";
|
|
4
|
+
export class NodemailerProvider extends BaseProvider {
|
|
5
|
+
transporter;
|
|
6
|
+
constructor(config) {
|
|
7
|
+
super();
|
|
8
|
+
this.transporter = nodemailer.createTransport(config);
|
|
9
|
+
}
|
|
10
|
+
async send(options) {
|
|
11
|
+
await this.transporter.sendMail({
|
|
12
|
+
from: options.from,
|
|
13
|
+
to: options.to,
|
|
14
|
+
subject: options.subject,
|
|
15
|
+
html: options.html,
|
|
16
|
+
cc: options.cc,
|
|
17
|
+
bcc: options.bcc,
|
|
18
|
+
replyTo: options.replyTo,
|
|
19
|
+
attachments: options.attachments?.map((a) => ({
|
|
20
|
+
filename: a.filename,
|
|
21
|
+
path: a.path
|
|
22
|
+
}))
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=NodemailerProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NodemailerProvider.js","sourceRoot":"","sources":["../../providers/NodemailerProvider.ts"],"names":[],"mappings":"AAAA,sHAAsH;AACtH,OAAO,UAAU,MAAM,YAAY,CAAA;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGhD,MAAM,OAAO,kBAAmB,SAAQ,YAAY;IAC1C,WAAW,CAAK;IAExB,YAAY,MAAW;QACrB,KAAK,EAAE,CAAA;QACP,IAAI,CAAC,WAAW,GAAG,UAAU,CAAC,eAAe,CAAC,MAAM,CAAC,CAAA;IACvD,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAAY;QACrB,MAAM,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC;YAC9B,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,EAAE,EAAE,OAAO,CAAC,EAAE;YACd,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,EAAE,EAAE,OAAO,CAAC,EAAE;YACd,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,CAAa,EAAE,EAAE,CAAC,CAAC;gBACxD,QAAQ,EAAE,CAAC,CAAC,QAAQ;gBACpB,IAAI,EAAE,CAAC,CAAC,IAAI;aACb,CAAC,CAAC;SACJ,CAAC,CAAA;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TemplateEngine.d.ts","sourceRoot":"","sources":["../../template/TemplateEngine.ts"],"names":[],"mappings":"AAGA,qBAAa,cAAc;IACzB;;OAEG;WACU,MAAM,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IAoB1F,OAAO,CAAC,MAAM,CAAC,cAAc;CAG9B"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as fs from "fs/promises";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
export class TemplateEngine {
|
|
4
|
+
/**
|
|
5
|
+
* Render a template file with variables.
|
|
6
|
+
*/
|
|
7
|
+
static async render(templatePath, variables) {
|
|
8
|
+
let content;
|
|
9
|
+
try {
|
|
10
|
+
const fullPath = path.isAbsolute(templatePath)
|
|
11
|
+
? templatePath
|
|
12
|
+
: path.join(process.cwd(), templatePath);
|
|
13
|
+
content = await fs.readFile(fullPath, "utf-8");
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
throw new Error(`Template file not found at: ${templatePath}`);
|
|
17
|
+
}
|
|
18
|
+
return content.replace(/\$\{([^}]+)\}/g, (_, key) => {
|
|
19
|
+
const value = this.getNestedValue(variables, key.trim());
|
|
20
|
+
if (value === undefined || value === null)
|
|
21
|
+
return "";
|
|
22
|
+
if (Array.isArray(value))
|
|
23
|
+
return JSON.stringify(value);
|
|
24
|
+
if (typeof value === "object")
|
|
25
|
+
return JSON.stringify(value);
|
|
26
|
+
return String(value);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
static getNestedValue(obj, path) {
|
|
30
|
+
return path.split(".").reduce((acc, part) => acc && acc[part], obj);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=TemplateEngine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TemplateEngine.js","sourceRoot":"","sources":["../../template/TemplateEngine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,aAAa,CAAA;AACjC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAA;AAE5B,MAAM,OAAO,cAAc;IACzB;;OAEG;IACH,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,YAAoB,EAAE,SAA8B;QACtE,IAAI,OAAe,CAAA;QACnB,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC;gBAC5C,CAAC,CAAC,YAAY;gBACd,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,YAAY,CAAC,CAAA;YAC1C,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;QAChD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,+BAA+B,YAAY,EAAE,CAAC,CAAA;QAChE,CAAC;QAED,OAAO,OAAO,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE;YAClD,MAAM,KAAK,GAAG,IAAI,CAAC,cAAc,CAAC,SAAS,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAA;YACxD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;gBAAE,OAAO,EAAE,CAAA;YACpD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;YACtD,IAAI,OAAO,KAAK,KAAK,QAAQ;gBAAE,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;YAC3D,OAAO,MAAM,CAAC,KAAK,CAAC,CAAA;QACtB,CAAC,CAAC,CAAA;IACJ,CAAC;IAEO,MAAM,CAAC,cAAc,CAAC,GAAQ,EAAE,IAAY;QAClD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAA;IACrE,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TemplatePath.d.ts","sourceRoot":"","sources":["../../template/TemplatePath.ts"],"names":[],"mappings":"AAGA,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAMzD"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { fileURLToPath } from "url";
|
|
2
|
+
import { dirname, join } from "path";
|
|
3
|
+
export function TemplatePath(templateName) {
|
|
4
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
5
|
+
const __dirname = dirname(__filename);
|
|
6
|
+
// Sesuaikan path relatif ke lokasi file utils ini
|
|
7
|
+
return join(__dirname, "../../../", templateName);
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=TemplatePath.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TemplatePath.js","sourceRoot":"","sources":["../../template/TemplatePath.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAErC,MAAM,UAAU,YAAY,CAAC,YAAoB;IAC7C,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAEtC,kDAAkD;IAClD,OAAO,IAAI,CAAC,SAAS,EAAE,WAAW,EAAE,YAAY,CAAC,CAAC;AACtD,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export type ProviderType = "nodemailer" | "resend" | "sendgrid";
|
|
2
|
+
export interface EmailTemplateOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Path to the HTML template file.
|
|
5
|
+
*/
|
|
6
|
+
template: string;
|
|
7
|
+
/**
|
|
8
|
+
* Default subject for the email.
|
|
9
|
+
*/
|
|
10
|
+
subject?: string;
|
|
11
|
+
}
|
|
12
|
+
export type AllowedAttachmentExt = "pdf" | "doc" | "docx" | "xls" | "xlsx" | "jpg" | "jpeg" | "png" | "zip";
|
|
13
|
+
export interface Attachment {
|
|
14
|
+
filename: string;
|
|
15
|
+
path: string;
|
|
16
|
+
}
|
|
17
|
+
export type SendData<T> = Omit<T, never> & {
|
|
18
|
+
to: string | string[];
|
|
19
|
+
subject?: string;
|
|
20
|
+
cc?: string | string[];
|
|
21
|
+
bcc?: string | string[];
|
|
22
|
+
replyTo?: string;
|
|
23
|
+
attachments?: Attachment[];
|
|
24
|
+
};
|
|
25
|
+
export interface QueueOptions {
|
|
26
|
+
delay?: string;
|
|
27
|
+
priority?: number;
|
|
28
|
+
}
|
|
29
|
+
export interface MailerKitConfig {
|
|
30
|
+
provider: ProviderType;
|
|
31
|
+
config: any;
|
|
32
|
+
from: string;
|
|
33
|
+
queue?: {
|
|
34
|
+
engine: "bullmq" | "memory";
|
|
35
|
+
engineConfig?: {
|
|
36
|
+
redis?: {
|
|
37
|
+
host: string;
|
|
38
|
+
port: number;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
rateLimit?: {
|
|
42
|
+
max: number;
|
|
43
|
+
per: "second" | "minute" | "hour";
|
|
44
|
+
};
|
|
45
|
+
retry?: {
|
|
46
|
+
attempts: number;
|
|
47
|
+
backoff: "exponential" | "fixed";
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../types/index.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,YAAY,GAAG,QAAQ,GAAG,UAAU,CAAA;AAE/D,MAAM,WAAW,oBAAoB;IACnC;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAA;IAChB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,MAAM,oBAAoB,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,CAAA;AAE3G,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG;IACzC,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IACtB,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IACvB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,UAAU,EAAE,CAAA;CAC3B,CAAA;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,YAAY,CAAA;IACtB,MAAM,EAAE,GAAG,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE;QACN,MAAM,EAAE,QAAQ,GAAG,QAAQ,CAAA;QAC3B,YAAY,CAAC,EAAE;YACb,KAAK,CAAC,EAAE;gBAAE,IAAI,EAAE,MAAM,CAAC;gBAAC,IAAI,EAAE,MAAM,CAAA;aAAE,CAAA;SACvC,CAAA;QACD,SAAS,CAAC,EAAE;YACV,GAAG,EAAE,MAAM,CAAA;YACX,GAAG,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAA;SAClC,CAAA;QACD,KAAK,CAAC,EAAE;YACN,QAAQ,EAAE,MAAM,CAAA;YAChB,OAAO,EAAE,aAAa,GAAG,OAAO,CAAA;SACjC,CAAA;KACF,CAAA;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../types/index.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vitest.config.d.ts","sourceRoot":"","sources":["../vitest.config.ts"],"names":[],"mappings":";AAEA,wBAUE"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineConfig } from "vitest/config";
|
|
2
|
+
export default defineConfig({
|
|
3
|
+
test: {
|
|
4
|
+
globals: true,
|
|
5
|
+
environment: "node",
|
|
6
|
+
include: ["**/*.test.ts"],
|
|
7
|
+
coverage: {
|
|
8
|
+
provider: "v8",
|
|
9
|
+
include: ["core/**/*.ts", "decorators/**/*.ts", "template/**/*.ts"]
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
//# sourceMappingURL=vitest.config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vitest.config.js","sourceRoot":"","sources":["../vitest.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAE5C,eAAe,YAAY,CAAC;IAC1B,IAAI,EAAE;QACJ,OAAO,EAAE,IAAI;QACb,WAAW,EAAE,MAAM;QACnB,OAAO,EAAE,CAAC,cAAc,CAAC;QACzB,QAAQ,EAAE;YACR,QAAQ,EAAE,IAAI;YACd,OAAO,EAAE,CAAC,cAAc,EAAE,oBAAoB,EAAE,kBAAkB,CAAC;SACpE;KACF;CACF,CAAC,CAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@alis-kit/mailer",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Email delivery abstraction with template engine and TC39 native decorators",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=18.0.0"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"nodemailer": "^6.9.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^20.0.0",
|
|
25
|
+
"@types/nodemailer": "^6.4.0",
|
|
26
|
+
"typescript": "^5.5.0",
|
|
27
|
+
"vitest": "^3.0.0"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"typescript": ">=5.5.0"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsc",
|
|
34
|
+
"test": "vitest run",
|
|
35
|
+
"test:watch": "vitest",
|
|
36
|
+
"lint": "tsc --noEmit"
|
|
37
|
+
}
|
|
38
|
+
}
|