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

metigan

v2.3.0

Published

Official Metigan SDK - Email, OTP, Transactional, Forms, Contacts, and Audiences management with built-in security features

Readme

Metigan SDK

The official Metigan library for Node.js and browsers. Send emails, manage forms, contacts, and audiences with ease.

📦 Installation

npm install metigan
# or
yarn add metigan

🚀 Quick Start

import Metigan from 'metigan';

// Initialize the client with all features
const metigan = new Metigan({
  apiKey: 'your-api-key'
});

// Send email
await metigan.email.sendEmail({
  from: 'Your Company <[email protected]>',
  recipients: ['[email protected]'],
  subject: 'Welcome!',
  content: '<h1>Hello!</h1><p>Thank you for signing up.</p>'
});

// Send OTP (fast lane)
await metigan.email.sendOtp({
  from: 'Your Company <[email protected]>',
  to: '[email protected]',
  code: '348921',
  appName: 'Metigan',
  expiresInMinutes: 10
});

// Send transactional email (fast lane)
await metigan.email.sendTransactional({
  from: 'Your Company <[email protected]>',
  to: '[email protected]',
  subject: 'Your receipt',
  content: '<p>Thanks for your purchase.</p>'
});

// Submit form
await metigan.forms.submit({
  formId: 'form-123',
  data: {
    email: '[email protected]',
    name: 'John Doe'
  }
});

// Create contact
await metigan.contacts.create({
  email: '[email protected]',
  firstName: 'Jane',
  audienceId: 'audience-456'
});

📧 Email Module

Basic Send

const response = await metigan.email.sendEmail({
  from: 'Your Company <[email protected]>',
  recipients: ['[email protected]'],
  subject: 'Email Subject',
  content: '<h1>HTML Content</h1>'
});

With Attachments (Node.js)

import fs from 'fs';

const response = await metigan.email.sendEmail({
  from: '[email protected]',
  recipients: ['[email protected]'],
  subject: 'Important Document',
  content: 'Please find the document attached.',
  attachments: [
    {
      buffer: fs.readFileSync('./document.pdf'),
      originalname: 'document.pdf',
      mimetype: 'application/pdf'
    }
  ]
});

With CC and BCC

await metigan.email.sendEmail({
  from: '[email protected]',
  recipients: ['[email protected]'],
  subject: 'Meeting',
  content: 'Email content',
  cc: ['[email protected]'],
  bcc: ['[email protected]'],
  replyTo: '[email protected]'
});

OTP Send (Fast Lane)

await metigan.email.sendOtp({
  from: 'Your Company <[email protected]>',
  to: '[email protected]',
  code: '482193',
  appName: 'Metigan',
  expiresInMinutes: 10
});

Transactional Send (Fast Lane)

await metigan.email.sendTransactional({
  from: 'Your Company <[email protected]>',
  to: '[email protected]',
  subject: 'Password changed',
  content: '<p>Your password was updated successfully.</p>'
});

📋 Forms Module

Submit Response

const response = await metigan.forms.submit({
  formId: 'form-123', // or form slug
  data: {
    'field-email': '[email protected]',
    'field-name': 'John Doe',
    'field-message': 'Hello, I would like more information.'
  }
});

console.log(response.message); // "Thank you for your submission!"

Get Public Form

// By slug (for public display)
const form = await metigan.forms.getPublicForm('my-form');

console.log(form.title);
console.log(form.fields);

List Forms

const { forms, pagination } = await metigan.forms.listForms({
  page: 1,
  limit: 10
});

forms.forEach(form => {
  console.log(`${form.title} - ${form.analytics?.submissions || 0} responses`);
});

Create Form

const newForm = await metigan.forms.createForm({
  title: 'Contact Form',
  description: 'Get in touch with us',
  fields: [
    {
      id: 'field-email',
      type: 'email',
      label: 'Your Email',
      required: true
    },
    {
      id: 'field-name',
      type: 'text',
      label: 'Your Name',
      required: true
    },
    {
      id: 'field-subject',
      type: 'select',
      label: 'Subject',
      options: ['Support', 'Sales', 'Partnerships']
    },
    {
      id: 'field-message',
      type: 'textarea',
      label: 'Message',
      required: true
    }
  ],
  settings: {
    successMessage: 'Thank you! We will get back to you soon.',
    notifyEmail: '[email protected]'
  }
});

Publish Form

const { publishedUrl, slug } = await metigan.forms.publishForm('form-123', 'contact');
console.log(`Form published at: ${publishedUrl}`);

Form Analytics

const analytics = await metigan.forms.getAnalytics('form-123');

console.log(`Views: ${analytics.views}`);
console.log(`Submissions: ${analytics.submissions}`);
console.log(`Conversion rate: ${analytics.conversionRate}%`);

👥 Contacts Module

Create Contact

const contact = await metigan.contacts.create({
  email: '[email protected]',
  firstName: 'Jane',
  lastName: 'Doe',
  audienceId: 'audience-123',
  tags: ['customer', 'newsletter']
});

Get Contact

// By ID
const contact = await metigan.contacts.get('contact-456');

// By email
const contact = await metigan.contacts.getByEmail('[email protected]', 'audience-123');

Update Contact

const updated = await metigan.contacts.update('contact-456', {
  firstName: 'Jane Marie',
  tags: ['customer', 'vip']
});

Manage Subscription

// Unsubscribe
await metigan.contacts.unsubscribe('contact-456');

// Resubscribe
await metigan.contacts.subscribe('contact-456');

Manage Tags

// Add tags
await metigan.contacts.addTags('contact-456', ['vip', 'black-friday']);

// Remove tags
await metigan.contacts.removeTags('contact-456', ['test']);

List Contacts

const { contacts, pagination } = await metigan.contacts.list({
  audienceId: 'audience-123',
  status: 'subscribed',
  tag: 'customer',
  page: 1,
  limit: 50
});

Bulk Import

const result = await metigan.contacts.bulkImport(
  [
    { email: '[email protected]', firstName: 'John' },
    { email: '[email protected]', firstName: 'Jane' },
    { email: '[email protected]', firstName: 'Peter', tags: ['vip'] }
  ],
  'audience-123'
);

console.log(`Imported: ${result.imported}`);
console.log(`Failed: ${result.failed}`);

Search Contacts

const results = await metigan.contacts.search('doe', 'audience-123');

📊 Audiences Module

Create Audience

const audience = await metigan.audiences.create({
  name: 'Main Newsletter',
  description: 'Main subscriber list'
});

List Audiences

const { audiences, pagination } = await metigan.audiences.list({
  page: 1,
  limit: 10
});

audiences.forEach(audience => {
  console.log(`${audience.name}: ${audience.count} contacts`);
});

Audience Statistics

const stats = await metigan.audiences.getStats('audience-123');

console.log(`Total: ${stats.total}`);
console.log(`Subscribed: ${stats.subscribed}`);
console.log(`Unsubscribed: ${stats.unsubscribed}`);
console.log(`Bounced: ${stats.bounced}`);

Clean Audience

// Remove bounced and unsubscribed contacts
const { removed } = await metigan.audiences.clean('audience-123');
console.log(`${removed} contacts removed`);

Merge Audiences

// Merge source into target (source is deleted)
const merged = await metigan.audiences.merge(
  'source-audience-id',
  'target-audience-id'
);

⚙️ Advanced Configuration

const metigan = new Metigan({
  apiKey: 'your-api-key',
  
  // User ID for logging
  userId: 'user-123',
  
  // Disable logging
  disableLogs: true,
  
  // Timeout in milliseconds
  timeout: 60000,
  
  // Number of retries on failure
  retryCount: 5,
  
  // Delay between retries (ms)
  retryDelay: 2000,
  
  // Security options (see Security section)
  debug: false,
  sanitizeHtml: true,
  enableRateLimit: true,
  maxRequestsPerSecond: 10
});

🔒 Security Features

The SDK includes built-in security features:

HTML Sanitization

Automatically removes dangerous HTML tags and attributes from email content to prevent XSS attacks:

// HTML sanitization is enabled by default
const metigan = new Metigan({
  apiKey: 'your-api-key',
  sanitizeHtml: true // default
});

// Dangerous content is automatically sanitized
await metigan.email.sendEmail({
  from: '[email protected]',
  recipients: ['[email protected]'],
  subject: 'Newsletter',
  content: '<h1>Hello</h1><script>alert("xss")</script>' // Script tag will be removed
});

Client-Side Rate Limiting

Prevents accidental API abuse with built-in rate limiting:

const metigan = new Metigan({
  apiKey: 'your-api-key',
  enableRateLimit: true, // default
  maxRequestsPerSecond: 10 // default
});

// Check rate limit before making requests
if (metigan.email.canMakeRequest()) {
  await metigan.email.sendEmail({...});
} else {
  const waitTime = metigan.email.getTimeUntilNextRequest();
  console.log(`Please wait ${waitTime}ms before next request`);
}

// Reset rate limiter if needed
metigan.email.resetRateLimit();

Attachment Validation

Validates file attachments for security:

import { isAllowedMimeType, isSafeFileExtension, ALLOWED_MIME_TYPES } from 'metigan';

// Check if file type is safe
const filename = 'document.pdf';
const mimetype = 'application/pdf';

if (isSafeFileExtension(filename) && isAllowedMimeType(mimetype)) {
  // Safe to attach
}

// View allowed MIME types
console.log(ALLOWED_MIME_TYPES);

Email Header Injection Prevention

Email addresses and subjects are automatically sanitized to prevent header injection attacks:

import { sanitizeEmail, sanitizeSubject } from 'metigan';

// Remove newlines and dangerous characters
const safeEmail = sanitizeEmail('[email protected]\r\nBcc: [email protected]');
const safeSubject = sanitizeSubject('Subject\r\nFrom: [email protected]');

Debug Mode

Enable debug mode for troubleshooting (logs are hidden by default in production):

const metigan = new Metigan({
  apiKey: 'your-api-key',
  debug: true // Shows internal logs
});

// Or enable/disable at runtime
metigan.email.enableDebug();
metigan.email.disableDebug();

Custom Rate Limiter

For advanced use cases, you can create your own rate limiter:

import { RateLimiter } from 'metigan';

const limiter = new RateLimiter({
  maxRequests: 5,
  windowMs: 1000 // 5 requests per second
});

if (limiter.tryRequest()) {
  // Request allowed and recorded
  await makeApiCall();
} else {
  // Rate limited
  console.log(`Wait ${limiter.getTimeUntilNextRequest()}ms`);
}

🔧 Using Individual Modules

If you only need a specific module:

import { MetiganForms, MetiganContacts, MetiganAudiences } from 'metigan';

// Forms only
const forms = new MetiganForms({
  apiKey: 'your-api-key'
});

// Contacts only
const contacts = new MetiganContacts({
  apiKey: 'your-api-key'
});

// Audiences only
const audiences = new MetiganAudiences({
  apiKey: 'your-api-key'
});

🌐 Browser Usage

<script src="https://unpkg.com/metigan/dist/index.js"></script>
<script>
  const metigan = new Metigan({ apiKey: 'your-api-key' });
  
  // Submit form
  document.getElementById('my-form').addEventListener('submit', async (e) => {
    e.preventDefault();
    
    const formData = new FormData(e.target);
    const data = Object.fromEntries(formData);
    
    try {
      const response = await metigan.forms.submit({
        formId: 'form-123',
        data
      });
      alert(response.message);
    } catch (error) {
      alert('Error submitting: ' + error.message);
    }
  });
</script>

🛡️ Error Handling

import { MetiganError, ValidationError, ApiError } from 'metigan';

try {
  await metigan.email.sendEmail({
    from: 'invalid',
    recipients: [],
    subject: '',
    content: ''
  });
} catch (error) {
  if (error instanceof ValidationError) {
    console.error('Invalid data:', error.message);
  } else if (error instanceof ApiError) {
    console.error(`API error (${error.status}):`, error.message);
  } else if (error instanceof MetiganError) {
    console.error('Metigan error:', error.message);
  } else {
    console.error('Unknown error:', error);
  }
}

📝 TypeScript

The library includes full TypeScript definitions:

import Metigan, {
  EmailOptions,
  OtpSendOptions,
  TransactionalSendOptions,
  FormConfig,
  Contact,
  Audience,
  FormFieldType
} from 'metigan';

const emailOptions: EmailOptions = {
  from: '[email protected]',
  recipients: ['[email protected]'],
  subject: 'Test',
  content: '<p>Content</p>'
};

const fieldType: FormFieldType = 'email';

const otpOptions: OtpSendOptions = {
  from: '[email protected]',
  to: '[email protected]',
  code: '123456'
};

const transactionalOptions: TransactionalSendOptions = {
  from: '[email protected]',
  to: '[email protected]',
  subject: 'Invoice ready',
  content: '<p>Your invoice is ready.</p>'
};

📄 License

MIT © Metigan

🔗 Links