hazo_notify 1.0.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/.cursor/rules/db_schema.mdc +0 -0
- package/.cursor/rules/design.mdc +16 -0
- package/.cursor/rules/general.mdc +49 -0
- package/README.md +765 -0
- package/components/emailer-html-editor.tsx +94 -0
- package/components/ui/button.tsx +53 -0
- package/components/ui/card.tsx +78 -0
- package/components/ui/input.tsx +24 -0
- package/components/ui/label.tsx +21 -0
- package/components/ui/sidebar.tsx +121 -0
- package/components/ui/spinner.tsx +54 -0
- package/components/ui/textarea.tsx +23 -0
- package/components/ui/tooltip.tsx +30 -0
- package/components.json +20 -0
- package/hazo_notify_config.ini +153 -0
- package/jest.config.js +27 -0
- package/jest.setup.js +1 -0
- package/next.config.js +22 -0
- package/package.json +72 -0
- package/postcss.config.js +6 -0
- package/src/app/api/hazo_notify/emailer/send/__tests__/route.test.ts +227 -0
- package/src/app/api/hazo_notify/emailer/send/route.ts +537 -0
- package/src/app/editor-00/page.tsx +47 -0
- package/src/app/globals.css +69 -0
- package/src/app/hazo_notify/emailer_test/layout.tsx +53 -0
- package/src/app/hazo_notify/emailer_test/page.tsx +369 -0
- package/src/app/hazo_notify/layout.tsx +77 -0
- package/src/app/hazo_notify/page.tsx +12 -0
- package/src/app/layout.tsx +26 -0
- package/src/app/page.tsx +14 -0
- package/src/components/blocks/editor-00/editor.tsx +61 -0
- package/src/components/blocks/editor-00/nodes.ts +11 -0
- package/src/components/blocks/editor-00/plugins.tsx +36 -0
- package/src/components/editor/editor-ui/content-editable.tsx +34 -0
- package/src/components/editor/themes/editor-theme.css +91 -0
- package/src/components/editor/themes/editor-theme.ts +130 -0
- package/src/components/ui/button.tsx +53 -0
- package/src/components/ui/card.tsx +78 -0
- package/src/components/ui/input.tsx +24 -0
- package/src/components/ui/label.tsx +21 -0
- package/src/components/ui/sidebar.tsx +121 -0
- package/src/components/ui/spinner.tsx +54 -0
- package/src/components/ui/textarea.tsx +23 -0
- package/src/components/ui/tooltip.tsx +30 -0
- package/src/lib/emailer/__tests__/emailer.test.ts +200 -0
- package/src/lib/emailer/emailer.ts +263 -0
- package/src/lib/emailer/index.ts +11 -0
- package/src/lib/emailer/providers/__tests__/zeptomail_provider.test.ts +196 -0
- package/src/lib/emailer/providers/index.ts +33 -0
- package/src/lib/emailer/providers/pop3_provider.ts +30 -0
- package/src/lib/emailer/providers/smtp_provider.ts +30 -0
- package/src/lib/emailer/providers/zeptomail_provider.ts +299 -0
- package/src/lib/emailer/types.ts +119 -0
- package/src/lib/emailer/utils/constants.ts +24 -0
- package/src/lib/emailer/utils/index.ts +9 -0
- package/src/lib/emailer/utils/logger.ts +71 -0
- package/src/lib/emailer/utils/validation.ts +84 -0
- package/src/lib/index.ts +6 -0
- package/src/lib/utils.ts +6 -0
- package/tailwind.config.ts +65 -0
- package/tsconfig.json +27 -0
package/README.md
ADDED
|
@@ -0,0 +1,765 @@
|
|
|
1
|
+
# Hazo Notify
|
|
2
|
+
|
|
3
|
+
A reusable component library for sending email notifications with support for multiple integration methods. Currently implements Zeptomail API integration with support for text and HTML emails, multiple attachments, and comprehensive security features.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Multiple Integration Methods**: Support for API (Zeptomail - implemented), SMTP (placeholder), and POP3 (placeholder)
|
|
8
|
+
- **Text and HTML Emails**: Send both plain text and HTML formatted emails
|
|
9
|
+
- **Multiple Attachments**: Support for sending multiple file attachments (up to 10, configurable)
|
|
10
|
+
- **Security Features**: HTML sanitization, email injection protection, rate limiting, input validation
|
|
11
|
+
- **Configurable**: Configuration via `hazo_notify_config.ini` file using `hazo_config` package
|
|
12
|
+
- **Test UI**: Optional test UI at `/hazo_notify/emailer_test` for testing email functionality
|
|
13
|
+
- **TypeScript**: Fully typed with TypeScript
|
|
14
|
+
- **Testing**: Comprehensive test coverage with Jest
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install hazo_notify
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Dependencies
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install hazo_config
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
1. **Create `.env.local` file** with your Zeptomail API key:
|
|
31
|
+
```env
|
|
32
|
+
ZEPTOMAIL_API_KEY=your_zeptomail_api_key
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
2. **Create `hazo_notify_config.ini` file**:
|
|
36
|
+
```ini
|
|
37
|
+
[emailer]
|
|
38
|
+
emailer_module=zeptoemail_api
|
|
39
|
+
zeptomail_api_endpoint=https://api.zeptomail.com.au/v1.1/email
|
|
40
|
+
from_email=noreply@example.com
|
|
41
|
+
from_name=Hazo Notify
|
|
42
|
+
|
|
43
|
+
[ui]
|
|
44
|
+
enable_ui=false
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
3. **Use in your code**:
|
|
48
|
+
```typescript
|
|
49
|
+
import { send_email } from 'hazo_notify';
|
|
50
|
+
|
|
51
|
+
const result = await send_email({
|
|
52
|
+
to: 'recipient@example.com',
|
|
53
|
+
subject: 'Hello',
|
|
54
|
+
content: { text: 'This is a test email' }
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
if (result.success) {
|
|
58
|
+
console.log('Email sent!', result.message_id);
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Configuration
|
|
63
|
+
|
|
64
|
+
### Step 1: Create Environment Variables File
|
|
65
|
+
|
|
66
|
+
**IMPORTANT: For security, store sensitive credentials in environment variables.**
|
|
67
|
+
|
|
68
|
+
Create a `.env.local` file in your project root:
|
|
69
|
+
|
|
70
|
+
```env
|
|
71
|
+
# Zeptomail API Configuration
|
|
72
|
+
# Only the API key is required (no token needed)
|
|
73
|
+
ZEPTOMAIL_API_KEY=your_zeptomail_api_key
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Security Note:** The `.env.local` file is automatically excluded from git (via `.gitignore`). Never commit sensitive credentials to version control.
|
|
77
|
+
|
|
78
|
+
### Step 2: Create Configuration File
|
|
79
|
+
|
|
80
|
+
Create a `hazo_notify_config.ini` file in your project root. See `hazo_notify_config.ini` in the project root for a complete template with all available options.
|
|
81
|
+
|
|
82
|
+
**Minimum required configuration:**
|
|
83
|
+
|
|
84
|
+
```ini
|
|
85
|
+
[emailer]
|
|
86
|
+
# Emailer module: zeptoemail_api, smtp, pop3
|
|
87
|
+
emailer_module=zeptoemail_api
|
|
88
|
+
|
|
89
|
+
# Zeptomail API Provider Configuration (required when emailer_module=zeptoemail_api)
|
|
90
|
+
zeptomail_api_endpoint=https://api.zeptomail.com.au/v1.1/email
|
|
91
|
+
# API key is read from .env.local file (ZEPTOMAIL_API_KEY)
|
|
92
|
+
# If not set in .env.local, you can uncomment and set it here (not recommended for production)
|
|
93
|
+
# zeptomail_api_key=your_zeptomail_api_key
|
|
94
|
+
|
|
95
|
+
# Required: Default sender email address (must be verified in your Zeptomail account)
|
|
96
|
+
from_email=noreply@example.com
|
|
97
|
+
|
|
98
|
+
# Required: Default sender name displayed in email clients
|
|
99
|
+
from_name=Hazo Notify
|
|
100
|
+
|
|
101
|
+
[ui]
|
|
102
|
+
# Enable UI component and all routes (e.g., /hazo_notify/emailer_test)
|
|
103
|
+
# Default: false
|
|
104
|
+
enable_ui=false
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Configuration Options
|
|
108
|
+
|
|
109
|
+
#### Required Configuration
|
|
110
|
+
|
|
111
|
+
- `emailer_module`: Emailer module (`zeptoemail_api`, `smtp`, or `pop3`)
|
|
112
|
+
- `from_email`: Default sender email address (must be verified in your Zeptomail account)
|
|
113
|
+
- `from_name`: Default sender name displayed in email clients
|
|
114
|
+
- `zeptomail_api_endpoint`: Zeptomail API endpoint (required when `emailer_module=zeptoemail_api`) - Default: `https://api.zeptomail.com.au/v1.1/email`
|
|
115
|
+
- `ZEPTOMAIL_API_KEY`: Zeptomail API key (required when `emailer_module=zeptoemail_api`) - **Store in `.env.local` file**
|
|
116
|
+
|
|
117
|
+
#### Optional Configuration
|
|
118
|
+
|
|
119
|
+
- `reply_to_email`: Reply-to email address
|
|
120
|
+
- `bounce_email`: Bounce handling email
|
|
121
|
+
- `return_path_email`: Return path email
|
|
122
|
+
- `enable_ui`: Enable UI component and all routes (default: `false`)
|
|
123
|
+
- `rate_limit_requests`: Maximum requests per minute (default: `10`)
|
|
124
|
+
- `rate_limit_window`: Time window for rate limiting in seconds (default: `60`)
|
|
125
|
+
- `max_attachment_size`: Maximum size per attachment in bytes (default: `10485760` = 10MB)
|
|
126
|
+
- `max_attachments`: Maximum number of attachments (default: `10`)
|
|
127
|
+
- `request_timeout`: Timeout for API requests in milliseconds (default: `30000` = 30 seconds)
|
|
128
|
+
- `max_subject_length`: Maximum length for email subject (default: `255`)
|
|
129
|
+
- `max_body_length`: Maximum size for email body content in bytes (default: `1048576` = 1MB)
|
|
130
|
+
- `cors_allowed_origins`: Comma-separated list of allowed origins for CORS (default: empty)
|
|
131
|
+
|
|
132
|
+
## Usage
|
|
133
|
+
|
|
134
|
+
### Import the Library
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
import { send_email } from 'hazo_notify';
|
|
138
|
+
// or
|
|
139
|
+
import { send_email } from 'hazo_notify/emailer';
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Basic Usage Examples
|
|
143
|
+
|
|
144
|
+
#### 1. Send a Text Email
|
|
145
|
+
|
|
146
|
+
**Input:**
|
|
147
|
+
```typescript
|
|
148
|
+
const result = await send_email({
|
|
149
|
+
to: 'recipient@example.com',
|
|
150
|
+
subject: 'Welcome to Our Service',
|
|
151
|
+
content: {
|
|
152
|
+
text: 'Thank you for signing up! We are excited to have you on board.'
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Expected Output (Success):**
|
|
158
|
+
```typescript
|
|
159
|
+
{
|
|
160
|
+
success: true,
|
|
161
|
+
message_id: 'msg_abc123def456',
|
|
162
|
+
message: 'Email sent successfully',
|
|
163
|
+
raw_response: {
|
|
164
|
+
status: 200,
|
|
165
|
+
status_text: 'OK',
|
|
166
|
+
headers: { /* response headers */ },
|
|
167
|
+
body: {
|
|
168
|
+
data: {
|
|
169
|
+
message_id: 'msg_abc123def456'
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Expected Output (Error):**
|
|
177
|
+
```typescript
|
|
178
|
+
{
|
|
179
|
+
success: false,
|
|
180
|
+
error: 'Invalid recipient email address: invalid-email',
|
|
181
|
+
message: 'Invalid recipient email address: invalid-email',
|
|
182
|
+
raw_response: undefined // In production, raw_response is masked for security
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
#### 2. Send an HTML Email
|
|
187
|
+
|
|
188
|
+
**Input:**
|
|
189
|
+
```typescript
|
|
190
|
+
const result = await send_email({
|
|
191
|
+
to: 'recipient@example.com',
|
|
192
|
+
subject: 'Welcome Email',
|
|
193
|
+
content: {
|
|
194
|
+
html: '<h1>Welcome!</h1><p>Thank you for joining us.</p><p>We are excited to have you on board.</p>'
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**Note:** HTML content is automatically sanitized to prevent XSS attacks.
|
|
200
|
+
|
|
201
|
+
**Expected Output:**
|
|
202
|
+
Same structure as text email example above.
|
|
203
|
+
|
|
204
|
+
#### 3. Send Both Text and HTML Email
|
|
205
|
+
|
|
206
|
+
**Input:**
|
|
207
|
+
```typescript
|
|
208
|
+
const result = await send_email({
|
|
209
|
+
to: 'recipient@example.com',
|
|
210
|
+
subject: 'Newsletter',
|
|
211
|
+
content: {
|
|
212
|
+
text: 'This is the plain text version of the email.',
|
|
213
|
+
html: '<html><body><h1>Newsletter</h1><p>This is the HTML version of the email.</p></body></html>'
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**Expected Output:**
|
|
219
|
+
Same structure as text email example above.
|
|
220
|
+
|
|
221
|
+
#### 4. Send Email with Single Attachment
|
|
222
|
+
|
|
223
|
+
**Input:**
|
|
224
|
+
```typescript
|
|
225
|
+
import fs from 'fs';
|
|
226
|
+
|
|
227
|
+
// Read file and convert to base64
|
|
228
|
+
const file_content = fs.readFileSync('document.pdf');
|
|
229
|
+
const base64_content = file_content.toString('base64');
|
|
230
|
+
|
|
231
|
+
const result = await send_email({
|
|
232
|
+
to: 'recipient@example.com',
|
|
233
|
+
subject: 'Document Attached',
|
|
234
|
+
content: {
|
|
235
|
+
text: 'Please find the attached document.',
|
|
236
|
+
html: '<p>Please find the attached document.</p>'
|
|
237
|
+
},
|
|
238
|
+
attachments: [
|
|
239
|
+
{
|
|
240
|
+
filename: 'document.pdf',
|
|
241
|
+
content: base64_content, // Base64 encoded file content
|
|
242
|
+
mime_type: 'application/pdf'
|
|
243
|
+
}
|
|
244
|
+
]
|
|
245
|
+
});
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**Attachment Format:**
|
|
249
|
+
- `filename`: String - The name of the file (e.g., `'document.pdf'`)
|
|
250
|
+
- `content`: String - Base64 encoded file content (e.g., `'JVBERi0xLjQKJeLjz9MK...'`)
|
|
251
|
+
- `mime_type`: String - MIME type of the file (e.g., `'application/pdf'`, `'image/jpeg'`, `'text/plain'`)
|
|
252
|
+
|
|
253
|
+
**Common MIME Types:**
|
|
254
|
+
- PDF: `'application/pdf'`
|
|
255
|
+
- JPEG: `'image/jpeg'`
|
|
256
|
+
- PNG: `'image/png'`
|
|
257
|
+
- Text: `'text/plain'`
|
|
258
|
+
- CSV: `'text/csv'`
|
|
259
|
+
- ZIP: `'application/zip'`
|
|
260
|
+
|
|
261
|
+
**Expected Output:**
|
|
262
|
+
Same structure as text email example above.
|
|
263
|
+
|
|
264
|
+
#### 5. Send Email with Multiple Attachments
|
|
265
|
+
|
|
266
|
+
**Input:**
|
|
267
|
+
```typescript
|
|
268
|
+
import fs from 'fs';
|
|
269
|
+
|
|
270
|
+
const pdf_content = fs.readFileSync('document.pdf').toString('base64');
|
|
271
|
+
const image_content = fs.readFileSync('image.jpg').toString('base64');
|
|
272
|
+
|
|
273
|
+
const result = await send_email({
|
|
274
|
+
to: 'recipient@example.com',
|
|
275
|
+
subject: 'Multiple Attachments',
|
|
276
|
+
content: {
|
|
277
|
+
text: 'Please find the attached files.',
|
|
278
|
+
},
|
|
279
|
+
attachments: [
|
|
280
|
+
{
|
|
281
|
+
filename: 'document.pdf',
|
|
282
|
+
content: pdf_content,
|
|
283
|
+
mime_type: 'application/pdf'
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
filename: 'image.jpg',
|
|
287
|
+
content: image_content,
|
|
288
|
+
mime_type: 'image/jpeg'
|
|
289
|
+
}
|
|
290
|
+
]
|
|
291
|
+
});
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
**Expected Output:**
|
|
295
|
+
Same structure as text email example above.
|
|
296
|
+
|
|
297
|
+
**Limits:**
|
|
298
|
+
- Maximum attachments: 10 (configurable via `max_attachments`)
|
|
299
|
+
- Maximum size per attachment: 10MB (configurable via `max_attachment_size`)
|
|
300
|
+
|
|
301
|
+
#### 6. Send Email to Multiple Recipients
|
|
302
|
+
|
|
303
|
+
**Input:**
|
|
304
|
+
```typescript
|
|
305
|
+
const result = await send_email({
|
|
306
|
+
to: ['user1@example.com', 'user2@example.com', 'user3@example.com'],
|
|
307
|
+
subject: 'Group Announcement',
|
|
308
|
+
content: {
|
|
309
|
+
text: 'This email is sent to multiple recipients.',
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
**Expected Output:**
|
|
315
|
+
Same structure as text email example above.
|
|
316
|
+
|
|
317
|
+
#### 7. Send Email with CC and BCC
|
|
318
|
+
|
|
319
|
+
**Input:**
|
|
320
|
+
```typescript
|
|
321
|
+
const result = await send_email({
|
|
322
|
+
to: 'recipient@example.com',
|
|
323
|
+
cc: ['cc1@example.com', 'cc2@example.com'],
|
|
324
|
+
bcc: 'bcc@example.com',
|
|
325
|
+
subject: 'Email with CC and BCC',
|
|
326
|
+
content: {
|
|
327
|
+
text: 'This email has CC and BCC recipients.',
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
**Expected Output:**
|
|
333
|
+
Same structure as text email example above.
|
|
334
|
+
|
|
335
|
+
#### 8. Send Email with Custom From Address
|
|
336
|
+
|
|
337
|
+
**Input:**
|
|
338
|
+
```typescript
|
|
339
|
+
const result = await send_email({
|
|
340
|
+
to: 'recipient@example.com',
|
|
341
|
+
subject: 'Custom Sender',
|
|
342
|
+
content: {
|
|
343
|
+
text: 'This email is from a custom sender.',
|
|
344
|
+
},
|
|
345
|
+
from: 'custom@example.com',
|
|
346
|
+
from_name: 'Custom Sender Name'
|
|
347
|
+
});
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
**Expected Output:**
|
|
351
|
+
Same structure as text email example above.
|
|
352
|
+
|
|
353
|
+
**Note:** The `from` email must be verified in your Zeptomail account.
|
|
354
|
+
|
|
355
|
+
#### 9. Send Email with Reply-To Address
|
|
356
|
+
|
|
357
|
+
**Input:**
|
|
358
|
+
```typescript
|
|
359
|
+
const result = await send_email({
|
|
360
|
+
to: 'recipient@example.com',
|
|
361
|
+
subject: 'Support Request',
|
|
362
|
+
content: {
|
|
363
|
+
text: 'Please reply to this email for support.',
|
|
364
|
+
},
|
|
365
|
+
reply_to: 'support@example.com'
|
|
366
|
+
});
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
**Expected Output:**
|
|
370
|
+
Same structure as text email example above.
|
|
371
|
+
|
|
372
|
+
#### 10. Complete Example with All Options
|
|
373
|
+
|
|
374
|
+
**Input:**
|
|
375
|
+
```typescript
|
|
376
|
+
import fs from 'fs';
|
|
377
|
+
|
|
378
|
+
const attachment_content = fs.readFileSync('report.pdf').toString('base64');
|
|
379
|
+
|
|
380
|
+
const result = await send_email({
|
|
381
|
+
to: ['primary@example.com', 'secondary@example.com'],
|
|
382
|
+
cc: 'manager@example.com',
|
|
383
|
+
bcc: 'archive@example.com',
|
|
384
|
+
subject: 'Monthly Report - December 2024',
|
|
385
|
+
content: {
|
|
386
|
+
text: 'Please find the monthly report attached. This is the plain text version.',
|
|
387
|
+
html: `
|
|
388
|
+
<html>
|
|
389
|
+
<body>
|
|
390
|
+
<h1>Monthly Report</h1>
|
|
391
|
+
<p>Please find the monthly report attached.</p>
|
|
392
|
+
<p>This is the <strong>HTML version</strong> of the email.</p>
|
|
393
|
+
<p>Best regards,<br>Team</p>
|
|
394
|
+
</body>
|
|
395
|
+
</html>
|
|
396
|
+
`
|
|
397
|
+
},
|
|
398
|
+
attachments: [
|
|
399
|
+
{
|
|
400
|
+
filename: 'monthly-report-december-2024.pdf',
|
|
401
|
+
content: attachment_content,
|
|
402
|
+
mime_type: 'application/pdf'
|
|
403
|
+
}
|
|
404
|
+
],
|
|
405
|
+
from: 'reports@example.com',
|
|
406
|
+
from_name: 'Reporting Team',
|
|
407
|
+
reply_to: 'support@example.com'
|
|
408
|
+
});
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
**Expected Output (Success):**
|
|
412
|
+
```typescript
|
|
413
|
+
{
|
|
414
|
+
success: true,
|
|
415
|
+
message_id: 'msg_xyz789abc123',
|
|
416
|
+
message: 'Email sent successfully',
|
|
417
|
+
raw_response: {
|
|
418
|
+
status: 200,
|
|
419
|
+
status_text: 'OK',
|
|
420
|
+
headers: {
|
|
421
|
+
'content-type': 'application/json',
|
|
422
|
+
'content-length': '156',
|
|
423
|
+
// ... other headers
|
|
424
|
+
},
|
|
425
|
+
body: {
|
|
426
|
+
data: {
|
|
427
|
+
message_id: 'msg_xyz789abc123'
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
**Expected Output (Error - Validation):**
|
|
435
|
+
```typescript
|
|
436
|
+
{
|
|
437
|
+
success: false,
|
|
438
|
+
error: 'Email subject exceeds maximum length of 255 characters',
|
|
439
|
+
message: 'Email subject exceeds maximum length of 255 characters',
|
|
440
|
+
raw_response: undefined
|
|
441
|
+
}
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
**Expected Output (Error - API Failure):**
|
|
445
|
+
```typescript
|
|
446
|
+
{
|
|
447
|
+
success: false,
|
|
448
|
+
error: 'HTTP 400: Bad Request',
|
|
449
|
+
message: 'HTTP 400: Bad Request',
|
|
450
|
+
raw_response: {
|
|
451
|
+
status: 400,
|
|
452
|
+
status_text: 'Bad Request',
|
|
453
|
+
headers: { /* response headers */ },
|
|
454
|
+
body: {
|
|
455
|
+
error: 'Invalid email address format'
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
## Input/Output Reference
|
|
462
|
+
|
|
463
|
+
### Input Parameters
|
|
464
|
+
|
|
465
|
+
#### `SendEmailOptions` Interface
|
|
466
|
+
|
|
467
|
+
| Parameter | Type | Required | Description | Example |
|
|
468
|
+
|-----------|------|----------|-------------|---------|
|
|
469
|
+
| `to` | `string \| string[]` | Yes | Recipient email address(es) | `'user@example.com'` or `['user1@example.com', 'user2@example.com']` |
|
|
470
|
+
| `subject` | `string` | Yes | Email subject line | `'Welcome Email'` |
|
|
471
|
+
| `content` | `EmailContent` | Yes | Email content (text and/or HTML) | `{ text: 'Hello', html: '<p>Hello</p>' }` |
|
|
472
|
+
| `content.text` | `string` | No* | Plain text email content | `'This is plain text'` |
|
|
473
|
+
| `content.html` | `string` | No* | HTML email content | `'<h1>Title</h1><p>Content</p>'` |
|
|
474
|
+
| `attachments` | `EmailAttachment[]` | No | Array of file attachments | See attachment format below |
|
|
475
|
+
| `from` | `string` | No | Override default from email | `'custom@example.com'` |
|
|
476
|
+
| `from_name` | `string` | No | Override default from name | `'Custom Sender'` |
|
|
477
|
+
| `reply_to` | `string` | No | Reply-to email address | `'support@example.com'` |
|
|
478
|
+
| `cc` | `string \| string[]` | No | CC recipient(s) | `'cc@example.com'` or `['cc1@example.com', 'cc2@example.com']` |
|
|
479
|
+
| `bcc` | `string \| string[]` | No | BCC recipient(s) | `'bcc@example.com'` or `['bcc1@example.com', 'bcc2@example.com']` |
|
|
480
|
+
|
|
481
|
+
\* At least one of `content.text` or `content.html` must be provided.
|
|
482
|
+
|
|
483
|
+
#### `EmailAttachment` Interface
|
|
484
|
+
|
|
485
|
+
| Parameter | Type | Required | Description | Example |
|
|
486
|
+
|-----------|------|----------|-------------|---------|
|
|
487
|
+
| `filename` | `string` | Yes | Attachment filename | `'document.pdf'` |
|
|
488
|
+
| `content` | `string` | Yes | Base64 encoded file content | `'JVBERi0xLjQKJeLjz9MK...'` |
|
|
489
|
+
| `mime_type` | `string` | Yes | MIME type of the file | `'application/pdf'` |
|
|
490
|
+
|
|
491
|
+
### Output Response
|
|
492
|
+
|
|
493
|
+
#### `EmailSendResponse` Interface
|
|
494
|
+
|
|
495
|
+
| Field | Type | Description | Example (Success) | Example (Error) |
|
|
496
|
+
|-------|------|-------------|-------------------|-----------------|
|
|
497
|
+
| `success` | `boolean` | Whether the email was sent successfully | `true` | `false` |
|
|
498
|
+
| `message_id` | `string?` | Message ID from the email provider | `'msg_abc123def456'` | `undefined` |
|
|
499
|
+
| `message` | `string?` | Success or error message | `'Email sent successfully'` | `'Invalid email address'` |
|
|
500
|
+
| `raw_response` | `Record<string, unknown> \| string?` | Raw response from the provider (masked in production) | See raw_response example below | See raw_response example below |
|
|
501
|
+
| `error` | `string?` | Error message if failed | `undefined` | `'Invalid email address'` |
|
|
502
|
+
|
|
503
|
+
#### Raw Response Structure (Development)
|
|
504
|
+
|
|
505
|
+
```typescript
|
|
506
|
+
{
|
|
507
|
+
status: 200, // HTTP status code
|
|
508
|
+
status_text: 'OK', // HTTP status text
|
|
509
|
+
headers: {
|
|
510
|
+
'content-type': 'application/json',
|
|
511
|
+
'content-length': '156',
|
|
512
|
+
// ... other response headers
|
|
513
|
+
},
|
|
514
|
+
body: {
|
|
515
|
+
data: {
|
|
516
|
+
message_id: 'msg_abc123def456'
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
**Note:** In production (`NODE_ENV=production`), `raw_response` is masked for security and only contains `status` and `status_text`.
|
|
523
|
+
|
|
524
|
+
### Input Validation
|
|
525
|
+
|
|
526
|
+
The library performs comprehensive validation on all inputs:
|
|
527
|
+
|
|
528
|
+
#### Email Address Validation
|
|
529
|
+
- Format validation using RFC 5322 compliant regex
|
|
530
|
+
- Maximum length: 254 characters
|
|
531
|
+
- Examples:
|
|
532
|
+
- ✅ Valid: `'user@example.com'`, `'user.name@example.co.uk'`
|
|
533
|
+
- ❌ Invalid: `'invalid-email'`, `'user@'`, `'@example.com'`
|
|
534
|
+
|
|
535
|
+
#### Subject Validation
|
|
536
|
+
- Required field
|
|
537
|
+
- Maximum length: 255 characters (RFC 5322 standard)
|
|
538
|
+
- Examples:
|
|
539
|
+
- ✅ Valid: `'Welcome Email'` (14 characters)
|
|
540
|
+
- ❌ Invalid: `''` (empty), `'A'.repeat(256)` (exceeds limit)
|
|
541
|
+
|
|
542
|
+
#### Body Content Validation
|
|
543
|
+
- At least one of `text` or `html` must be provided
|
|
544
|
+
- Maximum size: 1MB (1,048,576 bytes) per content type
|
|
545
|
+
- HTML content is automatically sanitized to prevent XSS attacks
|
|
546
|
+
- Examples:
|
|
547
|
+
- ✅ Valid: `{ text: 'Hello' }` or `{ html: '<p>Hello</p>' }` or both
|
|
548
|
+
- ❌ Invalid: `{}` (empty content)
|
|
549
|
+
|
|
550
|
+
#### Attachment Validation
|
|
551
|
+
- Maximum count: 10 attachments (configurable)
|
|
552
|
+
- Maximum size per attachment: 10MB (10,485,760 bytes, configurable)
|
|
553
|
+
- Content must be valid base64 encoded string
|
|
554
|
+
- Examples:
|
|
555
|
+
- ✅ Valid: `[{ filename: 'doc.pdf', content: 'JVBERi0x...', mime_type: 'application/pdf' }]`
|
|
556
|
+
- ❌ Invalid: `[]` with 11 items (exceeds count), attachment > 10MB (exceeds size)
|
|
557
|
+
|
|
558
|
+
### Error Handling
|
|
559
|
+
|
|
560
|
+
All errors are returned in a consistent format:
|
|
561
|
+
|
|
562
|
+
```typescript
|
|
563
|
+
{
|
|
564
|
+
success: false,
|
|
565
|
+
error: 'Error message describing what went wrong',
|
|
566
|
+
message: 'Error message describing what went wrong',
|
|
567
|
+
raw_response: undefined // or detailed response in development
|
|
568
|
+
}
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
**Common Error Scenarios:**
|
|
572
|
+
|
|
573
|
+
1. **Validation Errors** (400):
|
|
574
|
+
- Invalid email address format
|
|
575
|
+
- Missing required fields (to, subject, content)
|
|
576
|
+
- Subject exceeds maximum length
|
|
577
|
+
- Body content exceeds maximum size
|
|
578
|
+
- Attachment count/size exceeds limits
|
|
579
|
+
|
|
580
|
+
2. **Configuration Errors** (500):
|
|
581
|
+
- Missing API key
|
|
582
|
+
- Invalid configuration
|
|
583
|
+
- Missing required config values
|
|
584
|
+
|
|
585
|
+
3. **API Errors** (varies):
|
|
586
|
+
- HTTP 400: Bad Request (invalid data)
|
|
587
|
+
- HTTP 401: Unauthorized (invalid API key)
|
|
588
|
+
- HTTP 429: Rate Limited (too many requests)
|
|
589
|
+
- HTTP 500: Server Error (provider issue)
|
|
590
|
+
- Timeout errors (request took too long)
|
|
591
|
+
|
|
592
|
+
4. **Rate Limiting** (429):
|
|
593
|
+
- Too many requests per time window
|
|
594
|
+
- Configurable via `rate_limit_requests` and `rate_limit_window`
|
|
595
|
+
|
|
596
|
+
## API Reference
|
|
597
|
+
|
|
598
|
+
### `send_email(options: SendEmailOptions, config?: EmailerConfig): Promise<EmailSendResponse>`
|
|
599
|
+
|
|
600
|
+
Send an email using the configured provider.
|
|
601
|
+
|
|
602
|
+
#### Parameters
|
|
603
|
+
|
|
604
|
+
- **`options`** (required): `SendEmailOptions` - Email send options
|
|
605
|
+
- See [Input Parameters](#input-parameters) section above for detailed field descriptions
|
|
606
|
+
|
|
607
|
+
- **`config`** (optional): `EmailerConfig` - Emailer configuration
|
|
608
|
+
- If not provided, configuration is loaded from `hazo_notify_config.ini`
|
|
609
|
+
- Useful for programmatic configuration or testing
|
|
610
|
+
|
|
611
|
+
#### Returns
|
|
612
|
+
|
|
613
|
+
- **`Promise<EmailSendResponse>`**: Promise that resolves to email send response
|
|
614
|
+
- See [Output Response](#output-response) section above for detailed field descriptions
|
|
615
|
+
|
|
616
|
+
#### Example
|
|
617
|
+
|
|
618
|
+
```typescript
|
|
619
|
+
import { send_email } from 'hazo_notify';
|
|
620
|
+
|
|
621
|
+
try {
|
|
622
|
+
const result = await send_email({
|
|
623
|
+
to: 'recipient@example.com',
|
|
624
|
+
subject: 'Test Email',
|
|
625
|
+
content: {
|
|
626
|
+
text: 'This is a test email',
|
|
627
|
+
html: '<p>This is a test email</p>'
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
if (result.success) {
|
|
632
|
+
console.log('Email sent successfully!');
|
|
633
|
+
console.log('Message ID:', result.message_id);
|
|
634
|
+
} else {
|
|
635
|
+
console.error('Failed to send email:', result.error);
|
|
636
|
+
}
|
|
637
|
+
} catch (error) {
|
|
638
|
+
console.error('Unexpected error:', error);
|
|
639
|
+
}
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
### `load_emailer_config(): EmailerConfig`
|
|
643
|
+
|
|
644
|
+
Load emailer configuration from `hazo_notify_config.ini`.
|
|
645
|
+
|
|
646
|
+
#### Returns
|
|
647
|
+
|
|
648
|
+
- **`EmailerConfig`**: Emailer configuration object
|
|
649
|
+
|
|
650
|
+
#### Example
|
|
651
|
+
|
|
652
|
+
```typescript
|
|
653
|
+
import { load_emailer_config } from 'hazo_notify';
|
|
654
|
+
|
|
655
|
+
const config = load_emailer_config();
|
|
656
|
+
console.log('Emailer module:', config.emailer_module);
|
|
657
|
+
console.log('From email:', config.from_email);
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
## Test UI
|
|
661
|
+
|
|
662
|
+
To enable the UI component and all routes, set `enable_ui=true` in the `[ui]` section of `hazo_notify_config.ini`. The test UI will be available at `/hazo_notify/emailer_test`.
|
|
663
|
+
|
|
664
|
+
### Test UI Features
|
|
665
|
+
|
|
666
|
+
- **Form Interface**: Easy-to-use form for testing email functionality
|
|
667
|
+
- **Text and HTML Support**: Test both plain text and HTML emails
|
|
668
|
+
- **Newline Conversion**: HTML input automatically converts newlines to `<br>` tags
|
|
669
|
+
- **Raw Request Display**: View the exact request payload that will be sent
|
|
670
|
+
- **Response Display**: View the complete response from the email provider
|
|
671
|
+
- **Error Handling**: Clear error messages for validation and API errors
|
|
672
|
+
|
|
673
|
+
### Accessing the Test UI
|
|
674
|
+
|
|
675
|
+
1. Set `enable_ui=true` in `hazo_notify_config.ini`
|
|
676
|
+
2. Start your Next.js development server: `npm run dev`
|
|
677
|
+
3. Navigate to: `http://localhost:3000/hazo_notify/emailer_test`
|
|
678
|
+
|
|
679
|
+
**Note:** When `enable_ui=false`, both the UI and API routes are disabled for security.
|
|
680
|
+
|
|
681
|
+
## Security Features
|
|
682
|
+
|
|
683
|
+
The library includes comprehensive security features:
|
|
684
|
+
|
|
685
|
+
1. **HTML Sanitization**: All HTML content is sanitized using DOMPurify to prevent XSS attacks
|
|
686
|
+
2. **Email Injection Protection**: Email headers are sanitized to prevent header injection attacks
|
|
687
|
+
3. **Rate Limiting**: Configurable rate limiting to prevent abuse (default: 10 requests/minute)
|
|
688
|
+
4. **Input Validation**: Comprehensive validation of all inputs (email format, length, size)
|
|
689
|
+
5. **Attachment Limits**: Size and count limits for attachments
|
|
690
|
+
6. **Request Timeouts**: Configurable timeouts for external API calls (default: 30 seconds)
|
|
691
|
+
7. **Error Masking**: Stack traces and sensitive data are masked in production
|
|
692
|
+
8. **CORS Support**: Configurable CORS headers for API routes
|
|
693
|
+
|
|
694
|
+
## Testing
|
|
695
|
+
|
|
696
|
+
Run tests with:
|
|
697
|
+
|
|
698
|
+
```bash
|
|
699
|
+
npm test
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
Run tests in watch mode:
|
|
703
|
+
|
|
704
|
+
```bash
|
|
705
|
+
npm run test:watch
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
Run tests with coverage:
|
|
709
|
+
|
|
710
|
+
```bash
|
|
711
|
+
npm run test:coverage
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
## Development
|
|
715
|
+
|
|
716
|
+
### Project Structure
|
|
717
|
+
|
|
718
|
+
```
|
|
719
|
+
hazo_notify/
|
|
720
|
+
├── src/
|
|
721
|
+
│ ├── lib/
|
|
722
|
+
│ │ ├── index.ts # Main library entry point
|
|
723
|
+
│ │ └── emailer/
|
|
724
|
+
│ │ ├── index.ts # Emailer entry point
|
|
725
|
+
│ │ ├── emailer.ts # Main emailer service
|
|
726
|
+
│ │ ├── types.ts # TypeScript type definitions
|
|
727
|
+
│ │ ├── providers/
|
|
728
|
+
│ │ │ ├── index.ts # Provider factory
|
|
729
|
+
│ │ │ ├── zeptomail_provider.ts # Zeptomail API implementation
|
|
730
|
+
│ │ │ ├── smtp_provider.ts # SMTP placeholder
|
|
731
|
+
│ │ │ └── pop3_provider.ts # POP3 placeholder
|
|
732
|
+
│ │ └── utils/
|
|
733
|
+
│ │ ├── constants.ts # Constants and defaults
|
|
734
|
+
│ │ ├── validation.ts # Input validation utilities
|
|
735
|
+
│ │ └── logger.ts # Centralized logging
|
|
736
|
+
│ └── app/
|
|
737
|
+
│ ├── api/
|
|
738
|
+
│ │ └── hazo_notify/
|
|
739
|
+
│ │ └── emailer/
|
|
740
|
+
│ │ └── send/
|
|
741
|
+
│ │ └── route.ts # Next.js API route
|
|
742
|
+
│ └── hazo_notify/
|
|
743
|
+
│ ├── page.tsx # Default page
|
|
744
|
+
│ ├── layout.tsx # Layout with sidebar
|
|
745
|
+
│ └── emailer_test/
|
|
746
|
+
│ ├── page.tsx # Test UI page
|
|
747
|
+
│ └── layout.tsx # Test UI layout
|
|
748
|
+
├── components/
|
|
749
|
+
│ └── ui/ # Shadcn UI components
|
|
750
|
+
├── hazo_notify_config.ini # Configuration file template
|
|
751
|
+
├── .env.local.example # Environment variables example
|
|
752
|
+
└── package.json
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
## License
|
|
756
|
+
|
|
757
|
+
MIT
|
|
758
|
+
|
|
759
|
+
## Author
|
|
760
|
+
|
|
761
|
+
Pubs Abayasiri
|
|
762
|
+
|
|
763
|
+
## Support
|
|
764
|
+
|
|
765
|
+
For issues and questions, please visit the [GitHub repository](https://github.com/pub12/hazo_notify/issues).
|