@metigan/angular
v1.0.3
Published
Official Metigan SDK for Angular - Email, Forms, Contacts, and Audiences management
Maintainers
Readme
Metigan Angular SDK
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/angularOr via yarn:
yarn add @metigan/angular🔑 Getting Your API Key
Get your API key from the Metigan Dashboard.
- Sign in to your Metigan account
- Navigate to Settings → API Keys
- Create a new API key or use an existing one
- 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:
- Subscribe to the observable
- Handle both success and error cases
- Unsubscribe if needed (or use
asyncpipe 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:
MetiganEmailServiceMetiganFormsServiceMetiganContactsServiceMetiganAudiencesServiceMetiganTemplatesService
🧪 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:
- Check the Documentation
- Open an issue on GitHub
- Contact support at [email protected]
