npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

hazo_notify

v1.1.1

Published

Email that acts as a notification center as well for schedules too

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.ini file using hazo_config package
  • Test UI: Optional test UI at /hazo_notify/emailer_test for testing email functionality
  • TypeScript: Fully typed with TypeScript
  • Testing: Comprehensive test coverage with Jest

Installation

npm install hazo_notify

The 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_logs

Then 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 = 14d

If hazo_logs is not installed, hazo_notify will use a built-in console logger.

Quick Start

  1. Create .env.local file with your Zeptomail API key:

    ZEPTOMAIL_API_KEY=your_zeptomail_api_key
  2. Create config/hazo_notify_config.ini file (in a config/ 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=false
  3. Use 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_key

Security 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=false

Configuration Options

Required Configuration

  • emailer_module: Emailer module (zeptoemail_api, smtp, or pop3)
  • from_email: Default sender email address (must be verified in your Zeptomail account)
  • from_name: Default sender name displayed in email clients
  • zeptomail_api_endpoint: Zeptomail API endpoint (required when emailer_module=zeptoemail_api) - Default: https://api.zeptomail.com.au/v1.1/email
  • ZEPTOMAIL_API_KEY: Zeptomail API key (required when emailer_module=zeptoemail_api) - Store in .env.local file

Optional Configuration

  • reply_to_email: Reply-to email address
  • bounce_email: Bounce handling email
  • return_path_email: Return path email
  • enable_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:

Subject Validation

  • Required field
  • Maximum length: 255 characters (RFC 5322 standard)
  • Examples:
    • ✅ Valid: 'Welcome Email' (14 characters)
    • ❌ Invalid: '' (empty), 'A'.repeat(256) (exceeds limit)

Body Content Validation

  • At least one of text or html must 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)

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)

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:

  1. 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
  2. Configuration Errors (500):

    • Missing API key
    • Invalid configuration
    • Missing required config values
  3. 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)
  4. Rate Limiting (429):

    • Too many requests per time window
    • Configurable via rate_limit_requests and rate_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

  • config (optional): EmailerConfig - Emailer configuration

    • If not provided, configuration is loaded from config/hazo_notify_config.ini
    • Useful for programmatic configuration or testing

Returns

  • Promise<EmailSendResponse>: Promise that resolves to email send response

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

  1. Set enable_ui=true in config/hazo_notify_config.ini
  2. Start your Next.js development server: npm run dev
  3. 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:

  1. HTML Sanitization: All HTML content is sanitized using DOMPurify to prevent XSS attacks
  2. Email Injection Protection: Email headers are sanitized to prevent header injection attacks
  3. Rate Limiting: Configurable rate limiting to prevent abuse (default: 10 requests/minute)
  4. Input Validation: Comprehensive validation of all inputs (email format, length, size)
  5. Attachment Limits: Size and count limits for attachments
  6. Request Timeouts: Configurable timeouts for external API calls (default: 30 seconds)
  7. Error Masking: Stack traces and sensitive data are masked in production
  8. CORS Support: Configurable CORS headers for API routes

Testing

Run tests with:

npm test

Run tests in watch mode:

npm run test:watch

Run tests with coverage:

npm run test:coverage

Development

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.json

License

MIT

Author

Pubs Abayasiri

Support

For issues and questions, please visit the GitHub repository.