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