hazo_notify
v1.1.1
Published
Email that acts as a notification center as well for schedules too
Maintainers
Readme
Hazo Notify
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.
Features
- Multiple Integration Methods: Support for API (Zeptomail - implemented), SMTP (placeholder), and POP3 (placeholder)
- Text and HTML Emails: Send both plain text and HTML formatted emails
- Multiple Attachments: Support for sending multiple file attachments (up to 10, configurable)
- Security Features: HTML sanitization, email injection protection, rate limiting, input validation
- Configurable: Configuration via
config/hazo_notify_config.inifile usinghazo_configpackage - Test UI: Optional test UI at
/hazo_notify/emailer_testfor testing email functionality - TypeScript: Fully typed with TypeScript
- Testing: Comprehensive test coverage with Jest
Installation
npm install hazo_notifyThe package automatically installs required dependencies including hazo_config for configuration management and isomorphic-dompurify for HTML sanitization.
Optional: Enhanced Logging with hazo_logs
For structured logging with file rotation, install the optional hazo_logs peer dependency:
npm install hazo_logsThen create config/hazo_logs_config.ini:
[hazo_logs]
log_directory = ./logs
log_level = debug
enable_console = true
enable_file = true
max_file_size = 20m
max_files = 14dIf hazo_logs is not installed, hazo_notify will use a built-in console logger.
Quick Start
Create
.env.localfile with your Zeptomail API key:ZEPTOMAIL_API_KEY=your_zeptomail_api_keyCreate
config/hazo_notify_config.inifile (in aconfig/directory):[emailer] emailer_module=zeptoemail_api zeptomail_api_endpoint=https://api.zeptomail.com.au/v1.1/email [email protected] from_name=Hazo Notify [ui] enable_ui=falseUse in your code:
import { send_email } from 'hazo_notify'; const result = await send_email({ to: '[email protected]', subject: 'Hello', content: { text: 'This is a test email' } }); if (result.success) { console.log('Email sent!', result.message_id); }
Configuration
Step 1: Create Environment Variables File
IMPORTANT: For security, store sensitive credentials in environment variables.
Create a .env.local file in your project root:
# Zeptomail API Configuration
# Only the API key is required (no token needed)
ZEPTOMAIL_API_KEY=your_zeptomail_api_keySecurity Note: The .env.local file is automatically excluded from git (via .gitignore). Never commit sensitive credentials to version control.
Step 2: Create Configuration File
Create a config/hazo_notify_config.ini file in your project's config/ directory. See config/hazo_notify_config.ini in the package for a complete template with all available options.
Minimum required configuration:
[emailer]
# Emailer module: zeptoemail_api, smtp, pop3
emailer_module=zeptoemail_api
# Zeptomail API Provider Configuration (required when emailer_module=zeptoemail_api)
zeptomail_api_endpoint=https://api.zeptomail.com.au/v1.1/email
# API key is read from .env.local file (ZEPTOMAIL_API_KEY)
# If not set in .env.local, you can uncomment and set it here (not recommended for production)
# zeptomail_api_key=your_zeptomail_api_key
# Required: Default sender email address (must be verified in your Zeptomail account)
[email protected]
# Required: Default sender name displayed in email clients
from_name=Hazo Notify
[ui]
# Enable UI component and all routes (e.g., /hazo_notify/emailer_test)
# Default: false
enable_ui=falseConfiguration Options
Required Configuration
emailer_module: Emailer module (zeptoemail_api,smtp, orpop3)from_email: Default sender email address (must be verified in your Zeptomail account)from_name: Default sender name displayed in email clientszeptomail_api_endpoint: Zeptomail API endpoint (required whenemailer_module=zeptoemail_api) - Default:https://api.zeptomail.com.au/v1.1/emailZEPTOMAIL_API_KEY: Zeptomail API key (required whenemailer_module=zeptoemail_api) - Store in.env.localfile
Optional Configuration
reply_to_email: Reply-to email addressbounce_email: Bounce handling emailreturn_path_email: Return path emailenable_ui: Enable UI component and all routes (default:false)rate_limit_requests: Maximum requests per minute (default:10)rate_limit_window: Time window for rate limiting in seconds (default:60)max_attachment_size: Maximum size per attachment in bytes (default:10485760= 10MB)max_attachments: Maximum number of attachments (default:10)request_timeout: Timeout for API requests in milliseconds (default:30000= 30 seconds)max_subject_length: Maximum length for email subject (default:255)max_body_length: Maximum size for email body content in bytes (default:1048576= 1MB)cors_allowed_origins: Comma-separated list of allowed origins for CORS (default: empty)
Usage
Import the Library
import { send_email } from 'hazo_notify';
// or
import { send_email } from 'hazo_notify/emailer';Basic Usage Examples
1. Send a Text Email
Input:
const result = await send_email({
to: '[email protected]',
subject: 'Welcome to Our Service',
content: {
text: 'Thank you for signing up! We are excited to have you on board.'
}
});Expected Output (Success):
{
success: true,
message_id: 'msg_abc123def456',
message: 'Email sent successfully',
raw_response: {
status: 200,
status_text: 'OK',
headers: { /* response headers */ },
body: {
data: {
message_id: 'msg_abc123def456'
}
}
}
}Expected Output (Error):
{
success: false,
error: 'Invalid recipient email address: invalid-email',
message: 'Invalid recipient email address: invalid-email',
raw_response: undefined // In production, raw_response is masked for security
}2. Send an HTML Email
Input:
const result = await send_email({
to: '[email protected]',
subject: 'Welcome Email',
content: {
html: '<h1>Welcome!</h1><p>Thank you for joining us.</p><p>We are excited to have you on board.</p>'
}
});Note: HTML content is automatically sanitized to prevent XSS attacks.
Expected Output: Same structure as text email example above.
3. Send Both Text and HTML Email
Input:
const result = await send_email({
to: '[email protected]',
subject: 'Newsletter',
content: {
text: 'This is the plain text version of the email.',
html: '<html><body><h1>Newsletter</h1><p>This is the HTML version of the email.</p></body></html>'
}
});Expected Output: Same structure as text email example above.
4. Send Email with Single Attachment
Input:
import fs from 'fs';
// Read file and convert to base64
const file_content = fs.readFileSync('document.pdf');
const base64_content = file_content.toString('base64');
const result = await send_email({
to: '[email protected]',
subject: 'Document Attached',
content: {
text: 'Please find the attached document.',
html: '<p>Please find the attached document.</p>'
},
attachments: [
{
filename: 'document.pdf',
content: base64_content, // Base64 encoded file content
mime_type: 'application/pdf'
}
]
});Attachment Format:
filename: String - The name of the file (e.g.,'document.pdf')content: String - Base64 encoded file content (e.g.,'JVBERi0xLjQKJeLjz9MK...')mime_type: String - MIME type of the file (e.g.,'application/pdf','image/jpeg','text/plain')
Common MIME Types:
- PDF:
'application/pdf' - JPEG:
'image/jpeg' - PNG:
'image/png' - Text:
'text/plain' - CSV:
'text/csv' - ZIP:
'application/zip'
Expected Output: Same structure as text email example above.
5. Send Email with Multiple Attachments
Input:
import fs from 'fs';
const pdf_content = fs.readFileSync('document.pdf').toString('base64');
const image_content = fs.readFileSync('image.jpg').toString('base64');
const result = await send_email({
to: '[email protected]',
subject: 'Multiple Attachments',
content: {
text: 'Please find the attached files.',
},
attachments: [
{
filename: 'document.pdf',
content: pdf_content,
mime_type: 'application/pdf'
},
{
filename: 'image.jpg',
content: image_content,
mime_type: 'image/jpeg'
}
]
});Expected Output: Same structure as text email example above.
Limits:
- Maximum attachments: 10 (configurable via
max_attachments) - Maximum size per attachment: 10MB (configurable via
max_attachment_size)
6. Send Email to Multiple Recipients
Input:
const result = await send_email({
to: ['[email protected]', '[email protected]', '[email protected]'],
subject: 'Group Announcement',
content: {
text: 'This email is sent to multiple recipients.',
}
});Expected Output: Same structure as text email example above.
7. Send Email with CC and BCC
Input:
const result = await send_email({
to: '[email protected]',
cc: ['[email protected]', '[email protected]'],
bcc: '[email protected]',
subject: 'Email with CC and BCC',
content: {
text: 'This email has CC and BCC recipients.',
}
});Expected Output: Same structure as text email example above.
8. Send Email with Custom From Address
Input:
const result = await send_email({
to: '[email protected]',
subject: 'Custom Sender',
content: {
text: 'This email is from a custom sender.',
},
from: '[email protected]',
from_name: 'Custom Sender Name'
});Expected Output: Same structure as text email example above.
Note: The from email must be verified in your Zeptomail account.
9. Send Email with Reply-To Address
Input:
const result = await send_email({
to: '[email protected]',
subject: 'Support Request',
content: {
text: 'Please reply to this email for support.',
},
reply_to: '[email protected]'
});Expected Output: Same structure as text email example above.
10. Complete Example with All Options
Input:
import fs from 'fs';
const attachment_content = fs.readFileSync('report.pdf').toString('base64');
const result = await send_email({
to: ['[email protected]', '[email protected]'],
cc: '[email protected]',
bcc: '[email protected]',
subject: 'Monthly Report - December 2024',
content: {
text: 'Please find the monthly report attached. This is the plain text version.',
html: `
<html>
<body>
<h1>Monthly Report</h1>
<p>Please find the monthly report attached.</p>
<p>This is the <strong>HTML version</strong> of the email.</p>
<p>Best regards,<br>Team</p>
</body>
</html>
`
},
attachments: [
{
filename: 'monthly-report-december-2024.pdf',
content: attachment_content,
mime_type: 'application/pdf'
}
],
from: '[email protected]',
from_name: 'Reporting Team',
reply_to: '[email protected]'
});Expected Output (Success):
{
success: true,
message_id: 'msg_xyz789abc123',
message: 'Email sent successfully',
raw_response: {
status: 200,
status_text: 'OK',
headers: {
'content-type': 'application/json',
'content-length': '156',
// ... other headers
},
body: {
data: {
message_id: 'msg_xyz789abc123'
}
}
}
}Expected Output (Error - Validation):
{
success: false,
error: 'Email subject exceeds maximum length of 255 characters',
message: 'Email subject exceeds maximum length of 255 characters',
raw_response: undefined
}Expected Output (Error - API Failure):
{
success: false,
error: 'HTTP 400: Bad Request',
message: 'HTTP 400: Bad Request',
raw_response: {
status: 400,
status_text: 'Bad Request',
headers: { /* response headers */ },
body: {
error: 'Invalid email address format'
}
}
}Input/Output Reference
Input Parameters
SendEmailOptions Interface
| Parameter | Type | Required | Description | Example |
|-----------|------|----------|-------------|---------|
| to | string \| string[] | Yes | Recipient email address(es) | '[email protected]' or ['[email protected]', '[email protected]'] |
| subject | string | Yes | Email subject line | 'Welcome Email' |
| content | EmailContent | Yes | Email content (text and/or HTML) | { text: 'Hello', html: '<p>Hello</p>' } |
| content.text | string | No* | Plain text email content | 'This is plain text' |
| content.html | string | No* | HTML email content | '<h1>Title</h1><p>Content</p>' |
| attachments | EmailAttachment[] | No | Array of file attachments | See attachment format below |
| from | string | No | Override default from email | '[email protected]' |
| from_name | string | No | Override default from name | 'Custom Sender' |
| reply_to | string | No | Reply-to email address | '[email protected]' |
| cc | string \| string[] | No | CC recipient(s) | '[email protected]' or ['[email protected]', '[email protected]'] |
| bcc | string \| string[] | No | BCC recipient(s) | '[email protected]' or ['[email protected]', '[email protected]'] |
* At least one of content.text or content.html must be provided.
EmailAttachment Interface
| Parameter | Type | Required | Description | Example |
|-----------|------|----------|-------------|---------|
| filename | string | Yes | Attachment filename | 'document.pdf' |
| content | string | Yes | Base64 encoded file content | 'JVBERi0xLjQKJeLjz9MK...' |
| mime_type | string | Yes | MIME type of the file | 'application/pdf' |
Output Response
EmailSendResponse Interface
| Field | Type | Description | Example (Success) | Example (Error) |
|-------|------|-------------|-------------------|-----------------|
| success | boolean | Whether the email was sent successfully | true | false |
| message_id | string? | Message ID from the email provider | 'msg_abc123def456' | undefined |
| message | string? | Success or error message | 'Email sent successfully' | 'Invalid email address' |
| raw_response | Record<string, unknown> \| string? | Raw response from the provider (masked in production) | See raw_response example below | See raw_response example below |
| error | string? | Error message if failed | undefined | 'Invalid email address' |
Raw Response Structure (Development)
{
status: 200, // HTTP status code
status_text: 'OK', // HTTP status text
headers: {
'content-type': 'application/json',
'content-length': '156',
// ... other response headers
},
body: {
data: {
message_id: 'msg_abc123def456'
}
}
}Note: In production (NODE_ENV=production), raw_response is masked for security and only contains status and status_text.
Input Validation
The library performs comprehensive validation on all inputs:
Email Address Validation
- Format validation using RFC 5322 compliant regex
- Maximum length: 254 characters
- Examples:
- ✅ Valid:
'[email protected]','[email protected]' - ❌ Invalid:
'invalid-email','user@','@example.com'
- ✅ Valid:
Subject Validation
- Required field
- Maximum length: 255 characters (RFC 5322 standard)
- Examples:
- ✅ Valid:
'Welcome Email'(14 characters) - ❌ Invalid:
''(empty),'A'.repeat(256)(exceeds limit)
- ✅ Valid:
Body Content Validation
- At least one of
textorhtmlmust be provided - Maximum size: 1MB (1,048,576 bytes) per content type
- HTML content is automatically sanitized to prevent XSS attacks
- Examples:
- ✅ Valid:
{ text: 'Hello' }or{ html: '<p>Hello</p>' }or both - ❌ Invalid:
{}(empty content)
- ✅ Valid:
Attachment Validation
- Maximum count: 10 attachments (configurable)
- Maximum size per attachment: 10MB (10,485,760 bytes, configurable)
- Content must be valid base64 encoded string
- Examples:
- ✅ Valid:
[{ filename: 'doc.pdf', content: 'JVBERi0x...', mime_type: 'application/pdf' }] - ❌ Invalid:
[]with 11 items (exceeds count), attachment > 10MB (exceeds size)
- ✅ Valid:
Error Handling
All errors are returned in a consistent format:
{
success: false,
error: 'Error message describing what went wrong',
message: 'Error message describing what went wrong',
raw_response: undefined // or detailed response in development
}Common Error Scenarios:
Validation Errors (400):
- Invalid email address format
- Missing required fields (to, subject, content)
- Subject exceeds maximum length
- Body content exceeds maximum size
- Attachment count/size exceeds limits
Configuration Errors (500):
- Missing API key
- Invalid configuration
- Missing required config values
API Errors (varies):
- HTTP 400: Bad Request (invalid data)
- HTTP 401: Unauthorized (invalid API key)
- HTTP 429: Rate Limited (too many requests)
- HTTP 500: Server Error (provider issue)
- Timeout errors (request took too long)
Rate Limiting (429):
- Too many requests per time window
- Configurable via
rate_limit_requestsandrate_limit_window
API Reference
send_email(options: SendEmailOptions, config?: EmailerConfig): Promise<EmailSendResponse>
Send an email using the configured provider.
Parameters
options(required):SendEmailOptions- Email send options- See Input Parameters section above for detailed field descriptions
config(optional):EmailerConfig- Emailer configuration- If not provided, configuration is loaded from
config/hazo_notify_config.ini - Useful for programmatic configuration or testing
- If not provided, configuration is loaded from
Returns
Promise<EmailSendResponse>: Promise that resolves to email send response- See Output Response section above for detailed field descriptions
Example
import { send_email } from 'hazo_notify';
try {
const result = await send_email({
to: '[email protected]',
subject: 'Test Email',
content: {
text: 'This is a test email',
html: '<p>This is a test email</p>'
}
});
if (result.success) {
console.log('Email sent successfully!');
console.log('Message ID:', result.message_id);
} else {
console.error('Failed to send email:', result.error);
}
} catch (error) {
console.error('Unexpected error:', error);
}load_emailer_config(): EmailerConfig
Load emailer configuration from config/hazo_notify_config.ini.
Returns
EmailerConfig: Emailer configuration object
Example
import { load_emailer_config } from 'hazo_notify';
const config = load_emailer_config();
console.log('Emailer module:', config.emailer_module);
console.log('From email:', config.from_email);Test UI
To enable the UI component and all routes, set enable_ui=true in the [ui] section of config/hazo_notify_config.ini. The test UI will be available at /hazo_notify/emailer_test.
Test UI Features
- Form Interface: Easy-to-use form for testing email functionality
- Text and HTML Support: Test both plain text and HTML emails
- Newline Conversion: HTML input automatically converts newlines to
<br>tags - Raw Request Display: View the exact request payload that will be sent
- Response Display: View the complete response from the email provider
- Error Handling: Clear error messages for validation and API errors
Accessing the Test UI
- Set
enable_ui=trueinconfig/hazo_notify_config.ini - Start your Next.js development server:
npm run dev - Navigate to:
http://localhost:3000/hazo_notify/emailer_test
Note: When enable_ui=false, both the UI and API routes are disabled for security.
Security Features
The library includes comprehensive security features:
- HTML Sanitization: All HTML content is sanitized using DOMPurify to prevent XSS attacks
- Email Injection Protection: Email headers are sanitized to prevent header injection attacks
- Rate Limiting: Configurable rate limiting to prevent abuse (default: 10 requests/minute)
- Input Validation: Comprehensive validation of all inputs (email format, length, size)
- Attachment Limits: Size and count limits for attachments
- Request Timeouts: Configurable timeouts for external API calls (default: 30 seconds)
- Error Masking: Stack traces and sensitive data are masked in production
- CORS Support: Configurable CORS headers for API routes
Testing
Run tests with:
npm testRun tests in watch mode:
npm run test:watchRun tests with coverage:
npm run test:coverageDevelopment
Project Structure
hazo_notify/
├── src/
│ ├── lib/
│ │ ├── index.ts # Main library entry point
│ │ └── emailer/
│ │ ├── index.ts # Emailer entry point
│ │ ├── emailer.ts # Main emailer service
│ │ ├── types.ts # TypeScript type definitions
│ │ ├── providers/
│ │ │ ├── index.ts # Provider factory
│ │ │ ├── zeptomail_provider.ts # Zeptomail API implementation
│ │ │ ├── smtp_provider.ts # SMTP placeholder
│ │ │ └── pop3_provider.ts # POP3 placeholder
│ │ └── utils/
│ │ ├── constants.ts # Constants and defaults
│ │ ├── validation.ts # Input validation utilities
│ │ └── logger.ts # Centralized logging
│ └── app/
│ ├── api/
│ │ └── hazo_notify/
│ │ └── emailer/
│ │ └── send/
│ │ └── route.ts # Next.js API route
│ └── hazo_notify/
│ ├── page.tsx # Default page
│ ├── layout.tsx # Layout with sidebar
│ └── emailer_test/
│ ├── page.tsx # Test UI page
│ └── layout.tsx # Test UI layout
├── components/
│ └── ui/ # Shadcn UI components
├── config/
│ ├── hazo_notify_config.ini # Configuration file template
│ └── hazo_logs_config.ini # Optional logging configuration
├── .env.local.example # Environment variables example
└── package.jsonLicense
MIT
Author
Pubs Abayasiri
Support
For issues and questions, please visit the GitHub repository.
