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/angular

v1.0.3

Published

Official Metigan SDK for Angular - Email, Forms, Contacts, and Audiences management

Readme

Metigan Angular SDK

npm version npm downloads License

Official Angular SDK for the Metigan API. Send emails, manage contacts, audiences, templates, and forms with ease in your Angular applications.

✨ Features

  • 📧 Send Emails - Send HTML emails with attachments, CC, BCC, and templates
  • 👥 Manage Contacts - Create, update, list, and manage contact subscriptions
  • 🎯 Audiences - Organize contacts into audiences and track statistics
  • 📝 Forms - Submit and manage form data
  • 🎨 Templates - Use email templates with dynamic variables
  • 🔄 RxJS Observables - All methods return Observables for reactive programming
  • TypeScript - Full TypeScript support with type definitions
  • 🛡️ Error Handling - Comprehensive error handling with typed exceptions
  • 🔌 Angular 15+ - Supports Angular 15, 16, 17, 18, 19, 20, and 21
  • 📦 Standalone Support - Works with both NgModules and Standalone Components

📋 Requirements

  • Angular: 15.0.0 or higher (15, 16, 17, 18, 19, 20, 21)
  • RxJS: 7.0.0 or higher
  • TypeScript: 4.9.0 or higher

📦 Installation

Install via npm:

npm install @metigan/angular

Or via yarn:

yarn add @metigan/angular

🔑 Getting Your API Key

Get your API key from the Metigan Dashboard.

  1. Sign in to your Metigan account
  2. Navigate to SettingsAPI Keys
  3. Create a new API key or use an existing one
  4. Copy the API key and use it in your application

⚠️ Security Note: Never expose your API key in client-side code. For client-side usage, consider using environment-specific keys or implementing a proxy server.

🚀 Quick Start

Using NgModules (Angular 15+)

1. Import the Module

In your app.module.ts:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { MetiganModule } from '@metigan/angular';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    MetiganModule.forRoot({
      apiKey: 'your-api-key'
    })
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

2. Use in Components

import { Component } from '@angular/core';
import { MetiganService } from '@metigan/angular';

@Component({
  selector: 'app-my-component',
  template: '<button (click)="sendEmail()">Send Email</button>'
})
export class MyComponent {
  constructor(private metigan: MetiganService) {}

  sendEmail() {
    this.metigan.email.sendEmail({
      from: 'Your Company <[email protected]>',
      recipients: ['[email protected]'],
      subject: 'Welcome!',
      content: '<h1>Hello!</h1><p>Thank you for signing up.</p>'
    }).subscribe({
      next: (response) => {
        console.log('Email sent:', response.message);
        console.log('Emails remaining:', response.emailsRemaining);
      },
      error: (error) => {
        console.error('Error sending email:', error);
      }
    });
  }
}

Using Standalone Components (Angular 17+)

For Standalone Components, use manual initialization:

For Standalone Components, you can initialize manually:

import { Component, OnInit } from '@angular/core';
import { MetiganService } from '@metigan/angular';

@Component({
  selector: 'app-root',
  standalone: true,
  template: '<button (click)="sendEmail()">Send Email</button>'
})
export class AppComponent implements OnInit {
  constructor(private metigan: MetiganService) {}

  ngOnInit() {
    // Initialize with your API key
    this.metigan.initialize({
      apiKey: 'your-api-key',
      timeout: 30000,
      retryCount: 3
    });
  }

  sendEmail() {
    this.metigan.email.sendEmail({
      from: '[email protected]',
      recipients: ['[email protected]'],
      subject: 'Hello!',
      content: '<h1>Welcome</h1><p>Thank you for signing up.</p>'
    }).subscribe({
      next: (response) => console.log('Sent:', response),
      error: (error) => console.error('Error:', error)
    });
  }
}

📧 Email Module

Send Email

this.metigan.email.sendEmail({
  from: '[email protected]',
  recipients: ['[email protected]'],
  subject: 'Email Subject',
  content: '<h1>HTML Content</h1>'
}).subscribe({
  next: (response) => console.log('Sent:', response),
  error: (error) => console.error('Error:', error)
});

Send Email with Attachments

const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
const files = Array.from(fileInput.files || []);

this.metigan.email.sendEmail({
  from: '[email protected]',
  recipients: ['[email protected]'],
  subject: 'Important Document',
  content: 'Please find the document attached.',
  attachments: files
}).subscribe({
  next: (response) => console.log('Sent with attachments'),
  error: (error) => console.error('Error:', error)
});

Send Email with CC and BCC

this.metigan.email.sendEmail({
  from: '[email protected]',
  recipients: ['[email protected]'],
  subject: 'Meeting',
  content: 'Email content',
  cc: ['[email protected]'],
  bcc: ['[email protected]'],
  replyTo: '[email protected]'
}).subscribe({
  next: (response) => console.log('Sent'),
  error: (error) => console.error('Error:', error)
});

Send Email with Template

this.metigan.email.sendEmail({
  from: '[email protected]',
  recipients: ['[email protected]'],
  subject: 'Welcome Email',
  templateId: 'template-123',
  variables: {
    name: 'John Doe',
    company: 'Acme Inc'
  }
}).subscribe({
  next: (response) => console.log('Sent'),
  error: (error) => console.error('Error:', error)
});

📋 Forms Module

Submit Form

this.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.'
  }
}).subscribe({
  next: (response) => {
    console.log(response.message); // "Thank you for your submission!"
  },
  error: (error) => console.error('Error:', error)
});

Get Public Form

// By slug (for public display, no API key required)
this.metigan.forms.getPublicForm('my-form').subscribe({
  next: (form) => {
    console.log(form.title);
    console.log(form.fields);
  },
  error: (error) => console.error('Error:', error)
});

List Forms

this.metigan.forms.listForms({
  page: 1,
  limit: 10
}).subscribe({
  next: (response) => {
    response.forms.forEach(form => {
      console.log(`${form.title} - ${form.analytics?.submissions || 0} responses`);
    });
  },
  error: (error) => console.error('Error:', error)
});

Create Form

this.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-message',
      type: 'textarea',
      label: 'Message',
      required: true
    }
  ],
  settings: {
    successMessage: 'Thank you! We will get back to you soon.',
    notifyEmail: '[email protected]'
  }
}).subscribe({
  next: (form) => console.log('Form created:', form),
  error: (error) => console.error('Error:', error)
});

👥 Contacts Module

Create Contact

this.metigan.contacts.create({
  email: '[email protected]',
  firstName: 'Jane',
  lastName: 'Doe',
  audienceId: 'audience-123',
  tags: ['customer', 'newsletter']
}).subscribe({
  next: (contact) => console.log('Contact created:', contact),
  error: (error) => console.error('Error:', error)
});

Get Contact

// By ID
this.metigan.contacts.get('contact-456').subscribe({
  next: (contact) => console.log('Contact:', contact),
  error: (error) => console.error('Error:', error)
});

// By email
this.metigan.contacts.getByEmail('[email protected]', 'audience-123').subscribe({
  next: (contact) => console.log('Contact:', contact),
  error: (error) => console.error('Error:', error)
});

Update Contact

this.metigan.contacts.update('contact-456', {
  firstName: 'Jane Marie',
  tags: ['customer', 'vip']
}).subscribe({
  next: (updated) => console.log('Contact updated:', updated),
  error: (error) => console.error('Error:', error)
});

List Contacts

this.metigan.contacts.list({
  audienceId: 'audience-123',
  status: 'subscribed',
  tag: 'customer',
  page: 1,
  limit: 50
}).subscribe({
  next: (response) => {
    response.contacts.forEach(contact => {
      console.log(`${contact.email}: ${contact.firstName}`);
    });
  },
  error: (error) => console.error('Error:', error)
});

Manage Subscription

// Unsubscribe
this.metigan.contacts.unsubscribe('contact-456').subscribe({
  next: (contact) => console.log('Unsubscribed:', contact),
  error: (error) => console.error('Error:', error)
});

// Resubscribe
this.metigan.contacts.subscribe('contact-456').subscribe({
  next: (contact) => console.log('Subscribed:', contact),
  error: (error) => console.error('Error:', error)
});

Manage Tags

// Add tags
this.metigan.contacts.addTags('contact-456', ['vip', 'black-friday']).subscribe({
  next: (contact) => console.log('Tags added:', contact),
  error: (error) => console.error('Error:', error)
});

// Remove tags
this.metigan.contacts.removeTags('contact-456', ['test']).subscribe({
  next: (contact) => console.log('Tags removed:', contact),
  error: (error) => console.error('Error:', error)
});

Bulk Import

this.metigan.contacts.bulkImport(
  [
    { email: '[email protected]', firstName: 'John', audienceId: 'audience-123' },
    { email: '[email protected]', firstName: 'Jane', audienceId: 'audience-123' },
    { email: '[email protected]', firstName: 'Peter', audienceId: 'audience-123', tags: ['vip'] }
  ],
  'audience-123'
).subscribe({
  next: (result) => {
    console.log(`Imported: ${result.imported}`);
    console.log(`Failed: ${result.failed}`);
  },
  error: (error) => console.error('Error:', error)
});

📊 Audiences Module

Create Audience

this.metigan.audiences.create({
  name: 'Main Newsletter',
  description: 'Main subscriber list'
}).subscribe({
  next: (audience) => console.log('Audience created:', audience),
  error: (error) => console.error('Error:', error)
});

List Audiences

this.metigan.audiences.list({
  page: 1,
  limit: 10
}).subscribe({
  next: (response) => {
    response.audiences.forEach(audience => {
      console.log(`${audience.name}: ${audience.count} contacts`);
    });
  },
  error: (error) => console.error('Error:', error)
});

Get Audience Statistics

this.metigan.audiences.getStats('audience-123').subscribe({
  next: (stats) => {
    console.log(`Total: ${stats.total}`);
    console.log(`Subscribed: ${stats.subscribed}`);
    console.log(`Unsubscribed: ${stats.unsubscribed}`);
  },
  error: (error) => console.error('Error:', error)
});

Clean Audience

// Remove bounced and unsubscribed contacts
this.metigan.audiences.clean('audience-123').subscribe({
  next: (result) => console.log(`${result.removed} contacts removed`),
  error: (error) => console.error('Error:', error)
});

Merge Audiences

// Merge source into target (source is deleted)
this.metigan.audiences.merge(
  'source-audience-id',
  'target-audience-id'
).subscribe({
  next: (merged) => console.log('Audiences merged:', merged),
  error: (error) => console.error('Error:', error)
});

🎨 Templates Module

List Templates

this.metigan.templates.list({
  page: 1,
  limit: 10
}).subscribe({
  next: (response) => {
    response.templates.forEach(template => {
      console.log(`${template.name}: ${template.subject}`);
    });
  },
  error: (error) => console.error('Error:', error)
});

Get Template

this.metigan.templates.get('template-123').subscribe({
  next: (template) => console.log('Template:', template),
  error: (error) => console.error('Error:', error)
});

⚙️ Configuration

Module Configuration (NgModules)

MetiganModule.forRoot({
  apiKey: 'your-api-key',
  timeout: 60000,                  // Optional: Request timeout in ms (default: 30000)
  retryCount: 5,                   // Optional: Number of retries (default: 3)
  retryDelay: 2000,                // Optional: Delay between retries in ms (default: 2000)
  apiUrl: 'https://api.metigan.com' // Optional: Custom API URL
})

Manual Initialization

this.metigan.initialize({
  apiKey: 'your-api-key',
  timeout: 60000,
  retryCount: 5,
  retryDelay: 2000,
  apiUrl: 'https://api.metigan.com'
});

Environment Configuration

For better security, use environment variables:

environment.ts:

export const environment = {
  production: false,
  metiganApiKey: 'your-api-key'
};

app.module.ts:

import { environment } from './environments/environment';

@NgModule({
  imports: [
    MetiganModule.forRoot({
      apiKey: environment.metiganApiKey
    })
  ]
})
export class AppModule { }

🔄 RxJS Observables

All service methods return RxJS Observables. Make sure to:

  1. Subscribe to the observable
  2. Handle both success and error cases
  3. Unsubscribe if needed (or use async pipe in templates)

Using Async Pipe

// In component
templates$ = this.metigan.templates.list();

// In template
<div *ngIf="templates$ | async as templates">
  <div *ngFor="let template of templates.templates">
    {{ template.name }}
  </div>
</div>

Manual Subscription with Cleanup

import { Component, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';

export class MyComponent implements OnDestroy {
  private subscription = new Subscription();

  sendEmail() {
    const sub = this.metigan.email.sendEmail({
      from: '[email protected]',
      recipients: ['[email protected]'],
      subject: 'Test',
      content: '<p>Hello</p>'
    }).subscribe({
      next: (response) => console.log('Sent'),
      error: (error) => console.error('Error:', error)
    });
    
    this.subscription.add(sub);
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

Using takeUntil Pattern

import { Component, OnDestroy } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

export class MyComponent implements OnDestroy {
  private destroy$ = new Subject<void>();

  loadTemplates() {
    this.metigan.templates.list()
      .pipe(takeUntil(this.destroy$))
      .subscribe({
        next: (response) => console.log('Templates:', response),
        error: (error) => console.error('Error:', error)
      });
  }

  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
  }
}

🛡️ Error Handling

All services return RxJS Observables, so use standard error handling:

import { MetiganError, ValidationError, ApiError } from '@metigan/angular';

this.metigan.email.sendEmail(options).subscribe({
  next: (response) => {
    // Success
    console.log('Email sent successfully');
  },
  error: (error) => {
    if (error instanceof ValidationError) {
      console.error('Invalid data:', error.message);
      // Handle validation errors
    } else if (error instanceof ApiError) {
      console.error(`API error (${error.statusCode}):`, error.message);
      // Handle API errors (4xx, 5xx)
      if (error.statusCode === 401) {
        // Unauthorized - check API key
      } else if (error.statusCode === 429) {
        // Rate limited - retry after delay
      }
    } else if (error instanceof MetiganError) {
      console.error('Metigan error:', error.message);
      // Handle general Metigan errors
    } else {
      console.error('Unknown error:', error);
      // Handle unexpected errors
    }
  }
});

📝 Using Individual Services

If you only need a specific service, you can inject it directly:

import { MetiganEmailService } from '@metigan/angular';

@Component({
  selector: 'app-email',
  template: ''
})
export class EmailComponent {
  constructor(private emailService: MetiganEmailService) {}

  ngOnInit() {
    // Initialize the service
    this.emailService.initialize('your-api-key');
    
    // Use the service
    this.emailService.sendEmail({
      from: '[email protected]',
      recipients: ['[email protected]'],
      subject: 'Test',
      content: '<p>Hello</p>'
    }).subscribe({
      next: (response) => console.log('Sent'),
      error: (error) => console.error('Error:', error)
    });
  }
}

Available services:

  • MetiganEmailService
  • MetiganFormsService
  • MetiganContactsService
  • MetiganAudiencesService
  • MetiganTemplatesService

🧪 Testing

When testing components that use Metigan services, you can mock the services:

import { TestBed } from '@angular/core/testing';
import { of, throwError } from 'rxjs';
import { MetiganService } from '@metigan/angular';

describe('MyComponent', () => {
  let mockMetiganService: jasmine.SpyObj<MetiganService>;

  beforeEach(() => {
    mockMetiganService = jasmine.createSpyObj('MetiganService', ['email']);
    mockMetiganService.email = jasmine.createSpyObj('email', ['sendEmail']);
    
    TestBed.configureTestingModule({
      providers: [
        { provide: MetiganService, useValue: mockMetiganService }
      ]
    });
  });

  it('should send email', () => {
    mockMetiganService.email.sendEmail.and.returnValue(
      of({ success: true, message: 'Email sent' })
    );
    
    // Test your component
  });
});

🐛 Troubleshooting

"API key is required" Error

Make sure you've initialized the service with an API key:

// For NgModules
MetiganModule.forRoot({ apiKey: 'your-api-key' })

// For manual initialization
this.metigan.initialize({ apiKey: 'your-api-key' })

Observable Not Subscribing

Make sure you're subscribing to the Observable:

// ❌ Wrong - nothing happens
this.metigan.email.sendEmail(options);

// ✅ Correct - subscribes to the Observable
this.metigan.email.sendEmail(options).subscribe({
  next: (response) => console.log('Sent'),
  error: (error) => console.error('Error:', error)
});

Memory Leaks

Always unsubscribe from Observables to prevent memory leaks:

// Using async pipe (recommended)
templates$ = this.metigan.templates.list();

// Using takeUntil pattern
private destroy$ = new Subject<void>();
this.metigan.templates.list()
  .pipe(takeUntil(this.destroy$))
  .subscribe(...);

// Using Subscription
private subscription = new Subscription();
this.subscription.add(
  this.metigan.email.sendEmail(...).subscribe(...)
);

TypeScript Errors

Make sure you have TypeScript 4.9.0 or higher and Angular 15+ installed:

npm install typescript@^4.9.0 --save-dev

📚 Additional Resources

📄 License

MIT © Metigan

🔗 Links

💬 Support

For support, please: