@metigan/angular
v1.0.3
Published
Official Metigan SDK for Angular - Email, Forms, Contacts, and Audiences management
Downloads
39
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]
