@metigan/nestjs
v1.0.3
Published
Official Metigan SDK for NestJS - Email, Forms, Contacts, and Audiences management
Maintainers
Readme
Metigan NestJS SDK
Official NestJS SDK for the Metigan API. Send emails, manage contacts, audiences, templates, and forms with ease in your NestJS 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
- 🔄 Automatic Retry - Built-in retry logic for failed requests
- ⚡ TypeScript - Full TypeScript support with type definitions
- 🛡️ Error Handling - Comprehensive exception handling
- 🏗️ NestJS Integration - Native NestJS module with dependency injection
- 🔌 Async Configuration - Support for async module configuration
📋 Requirements
- Node.js: 16.0.0 or higher
- NestJS: 10.0.0 or higher
- TypeScript: 4.5.0 or higher
- RxJS: 7.8.0 or higher (peer dependency)
📦 Installation
Install via npm:
npm install @metigan/nestjsOr via yarn:
yarn add @metigan/nestjsOr via pnpm:
pnpm add @metigan/nestjs🔑 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. Use environment variables for configuration.
🚀 Quick Start
1. Import the Module
In your app.module.ts:
import { Module } from '@nestjs/common';
import { MetiganModule } from '@metigan/nestjs';
@Module({
imports: [
MetiganModule.forRoot({
apiKey: process.env.METIGAN_API_KEY,
}),
],
})
export class AppModule {}2. Use in Your Service
import { Injectable } from '@nestjs/common';
import { MetiganService } from '@metigan/nestjs';
@Injectable()
export class EmailService {
constructor(private readonly metigan: MetiganService) {}
async sendWelcomeEmail(email: string) {
const result = await this.metigan.email.sendEmail({
from: 'Sender <[email protected]>',
recipients: [email],
subject: 'Welcome!',
content: '<h1>Hello!</h1><p>Thank you for signing up.</p>',
});
if (result.success) {
console.log('Email sent successfully!');
console.log(`Emails remaining: ${result.emailsRemaining}`);
}
return result;
}
}⚙️ Configuration
Basic Configuration
MetiganModule.forRoot({
apiKey: process.env.METIGAN_API_KEY,
timeout: 30000, // Optional, defaults to 30000ms
retryCount: 3, // Optional, defaults to 3 retries
retryDelay: 2000, // Optional, defaults to 2000ms between retries
debug: false, // Optional, enables debug logging
})Async Configuration
For dynamic configuration using @nestjs/config:
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { MetiganModule } from '@metigan/nestjs';
@Module({
imports: [
ConfigModule.forRoot(),
MetiganModule.forRootAsync({
useFactory: (configService: ConfigService) => ({
apiKey: configService.get<string>('METIGAN_API_KEY'),
timeout: configService.get<number>('METIGAN_TIMEOUT', 30000),
retryCount: configService.get<number>('METIGAN_RETRY_COUNT', 3),
retryDelay: configService.get<number>('METIGAN_RETRY_DELAY', 2000),
debug: configService.get<boolean>('METIGAN_DEBUG', false),
}),
inject: [ConfigService],
}),
],
})
export class AppModule {}Environment Variables
Create a .env file:
METIGAN_API_KEY=your-api-key-here
METIGAN_TIMEOUT=30000
METIGAN_RETRY_COUNT=3
METIGAN_RETRY_DELAY=2000
METIGAN_DEBUG=false📧 Sending Emails
Basic Email
const result = await this.metigan.email.sendEmail({
from: 'Sender <[email protected]>',
recipients: ['[email protected]'],
subject: 'Email Subject',
content: '<h1>HTML Content</h1><p>This is the email body.</p>',
});
if (result.success) {
console.log(`Email sent! Remaining: ${result.emailsRemaining}`);
}Email with CC and BCC
const result = await this.metigan.email.sendEmail({
from: 'Company <[email protected]>',
recipients: ['[email protected]'],
subject: 'Meeting Invitation',
content: 'You\'re invited to our meeting.',
cc: ['[email protected]'],
bcc: ['[email protected]'],
replyTo: '[email protected]',
});Email with Attachments
import * as fs from 'fs';
const fileData = fs.readFileSync('document.pdf');
const result = await this.metigan.email.sendEmail({
from: 'Company <[email protected]>',
recipients: ['[email protected]'],
subject: 'Important Document',
content: 'Please find the document attached.',
attachments: [
{
buffer: fileData,
originalname: 'document.pdf',
mimetype: 'application/pdf',
},
],
});Email with Template
const variables = {
name: 'John Doe',
company: 'Acme Inc',
};
const result = await this.metigan.email.sendEmailWithTemplate(
'template-123',
variables,
{
from: 'Sender <[email protected]>',
recipients: ['[email protected]'],
replyTo: '[email protected]',
},
);👥 Managing Contacts
Create Contact
const contact = await this.metigan.contacts.create({
email: '[email protected]',
audienceId: 'audience-123',
firstName: 'Jane',
lastName: 'Doe',
phone: '+1234567890',
tags: ['customer', 'newsletter'],
customFields: { company: 'Acme Inc' },
status: 'subscribed',
});Get Contact
// Get by ID
const contact = await this.metigan.contacts.get('contact-456');
// Get by email
const contact = await this.metigan.contacts.getByEmail(
'[email protected]',
'audience-123',
);Update Contact
const updated = await this.metigan.contacts.update('contact-456', {
firstName: 'Jane Marie',
lastName: 'Smith',
tags: ['customer', 'vip'],
status: 'subscribed',
});List Contacts
const result = await this.metigan.contacts.list({
audienceId: 'audience-123',
status: 'subscribed',
tag: 'customer',
search: 'john',
page: 1,
limit: 50,
});
console.log(`Total contacts: ${result.pagination.total}`);
result.contacts.forEach((contact) => {
console.log(`${contact.email}: ${contact.firstName}`);
});Manage Subscription
// Subscribe
await this.metigan.contacts.subscribe('contact-456');
// Unsubscribe
await this.metigan.contacts.unsubscribe('contact-456');Manage Tags
// Add tags
await this.metigan.contacts.addTags('contact-456', ['vip', 'black-friday']);
// Remove tags
await this.metigan.contacts.removeTags('contact-456', ['test']);Delete Contact
await this.metigan.contacts.delete('contact-456');🎯 Managing Audiences
Create Audience
const audience = await this.metigan.audiences.create({
name: 'Main Newsletter',
description: 'Main subscriber list',
});Get Audience
const audience = await this.metigan.audiences.get('audience-123');List Audiences
const result = await this.metigan.audiences.list({
page: 1,
limit: 10,
});
result.audiences.forEach((audience) => {
console.log(`${audience.name}: ${audience.count} contacts`);
});Get Audience Statistics
const stats = await this.metigan.audiences.getStats('audience-123');
console.log(`Total: ${stats.total}`);
console.log(`Subscribed: ${stats.subscribed}`);
console.log(`Unsubscribed: ${stats.unsubscribed}`);Delete Audience
await this.metigan.audiences.delete('audience-123');🎨 Managing Templates
Get Template
const template = await this.metigan.templates.get('template-123');List Templates
const templates = await this.metigan.templates.list({
page: 1,
limit: 10,
});
templates.forEach((template) => {
console.log(`${template.name}: ${template.id}`);
});📝 Managing Forms
Submit Form
const result = await this.metigan.forms.submit({
formId: 'form-123', // or form slug
data: {
name: 'John Doe',
email: '[email protected]',
message: 'Hello, I would like more information.',
},
});
console.log(result.message);Get Form
const form = await this.metigan.forms.get('form-123');List Forms
const result = await this.metigan.forms.list({
page: 1,
limit: 10,
});
result.forms.forEach((form) => {
console.log(`${form.title}: ${form.id}`);
});🛡️ Error Handling
The SDK provides specific error types for different scenarios:
import { ApiError, ValidationError } from '@metigan/nestjs';
try {
const result = await this.metigan.email.sendEmail(options);
} catch (error) {
if (error instanceof ValidationError) {
// Handle validation errors (422)
console.error(`Validation Error: ${error.message}`);
if (error.field) {
console.error(`Field: ${error.field}`);
}
} else if (error instanceof ApiError) {
// Handle API errors (4xx, 5xx)
console.error(`API Error: ${error.statusCode} - ${error.message}`);
if (error.error) {
console.error(`Error code: ${error.error}`);
}
// Handle specific status codes
if (error.statusCode === 401) {
// Unauthorized - check API key
} else if (error.statusCode === 429) {
// Rate limited - retry after delay
}
} else {
// Handle other errors
console.error(`Unexpected error: ${error}`);
}
}Error Types
ValidationError- Thrown when request validation fails (422)message: Error messagefield: Field name that failed validation (optional)
ApiError- Thrown for API errors (4xx, 5xx)statusCode: HTTP status codemessage: Error messageerror: Error code (optional)
📋 Response Format
Important: The API returns all fields in camelCase format, not snake_case. Always use camelCase when accessing response data:
// ✅ Correct
result.emailsRemaining
result.recipientCount
result.successfulEmails
// ❌ Incorrect
result.emails_remaining // Will be undefined
result.recipient_count // Will be undefined🧪 Testing
When testing services that use Metigan, you can mock the MetiganService:
import { Test, TestingModule } from '@nestjs/testing';
import { MetiganService } from '@metigan/nestjs';
describe('EmailService', () => {
let service: EmailService;
let metiganService: jest.Mocked<MetiganService>;
beforeEach(async () => {
const mockMetiganService = {
email: {
sendEmail: jest.fn().mockResolvedValue({
success: true,
message: 'Email sent',
emailsRemaining: 999,
}),
},
};
const module: TestingModule = await Test.createTestingModule({
providers: [
EmailService,
{
provide: MetiganService,
useValue: mockMetiganService,
},
],
}).compile();
service = module.get<EmailService>(EmailService);
metiganService = module.get(MetiganService);
});
it('should send email', async () => {
const result = await service.sendWelcomeEmail('[email protected]');
expect(metiganService.email.sendEmail).toHaveBeenCalled();
expect(result.success).toBe(true);
});
});🐛 Troubleshooting
"API key is required" Error
Make sure you've configured the module with an API key:
// Check your .env file
METIGAN_API_KEY=your-api-key-here
// Verify in your module
MetiganModule.forRoot({
apiKey: process.env.METIGAN_API_KEY, // Make sure this is set
})Module Not Found
Make sure you've imported MetiganModule in your root module:
@Module({
imports: [MetiganModule.forRoot({ apiKey: '...' })],
})
export class AppModule {}Service Not Injected
The MetiganService is available globally after importing MetiganModule. Make sure:
- The module is imported in your root module
- You're using dependency injection correctly
- The service is provided in your module (if using a feature module)
Timeout Errors
If you're experiencing timeout errors, increase the timeout:
MetiganModule.forRoot({
apiKey: '...',
timeout: 60000, // Increase to 60 seconds
})Retry Logic
The SDK automatically retries failed requests. Configure retry behavior:
MetiganModule.forRoot({
apiKey: '...',
retryCount: 5, // Increase retry attempts
retryDelay: 3000, // Increase delay between retries
})📚 Additional Resources
- NestJS Documentation
- TypeScript Documentation
- Axios Documentation (used internally)
📄 License
MIT © Metigan
🔗 Links
💬 Support
For support, please:
- Check the Documentation
- Open an issue on GitHub
- Contact support at [email protected]
