Meru Node.js SDK
A comprehensive Node.js/TypeScript SDK for the Meru email webhook service API. This SDK provides a fluent, type-safe interface for managing temporary and permanent email addresses that forward incoming emails to configured webhook URLs.
Features
- 🚀 Type-safe: Full TypeScript support with strict typing
- 🔄 Auto-retry: Configurable retry logic with exponential backoff
- ⚡ Modern: Built with latest Node.js features using native fetch
- 🛡️ Error handling: Comprehensive error handling with custom exceptions
- 📦 Zero dependencies: No external HTTP libraries required
- 🧪 Well tested: Comprehensive test suite with Bun test
- 📖 Great DX: Excellent developer experience with IDE autocompletion
Installation
npm install @meruhook/node-sdk
yarn add @meruhook/node-sdk
pnpm add @meruhook/node-sdk
bun add @meruhook/node-sdk
Quick Start
import { MeruClient } from '@meruhook/node-sdk';
const client = new MeruClient({
apiToken: 'your-api-token-here',
baseUrl: 'https://api.meruhook.com',
timeout: 30000,
debug: false,
});
const address = await client.addresses.create({
webhookUrl: 'https://your-app.com/webhook',
isPermanent: true,
});
console.log(`Created address: ${address.address}`);
const addresses = await client.addresses.list();
console.log(`You have ${addresses.length} addresses`);
const usage = await client.usage.get();
console.log(`Emails received this month: ${usage.totalEmails}`);
const billing = await client.billing.get();
console.log(`Current month cost: $${billing.currentCost}`);
Configuration
Environment Variables
You can use environment variables to configure the client:
MERU_API_TOKEN=your-api-token-here
MERU_BASE_URL=https://api.meruhook.com
MERU_TIMEOUT=30000
MERU_DEBUG=false
Configuration Object
const client = new MeruClient({
apiToken: process.env.MERU_API_TOKEN!,
baseUrl: process.env.MERU_BASE_URL,
timeout: 30000,
retry: {
times: 3,
delay: 100,
maxDelay: 5000,
backoffMultiplier: 2,
},
debug: false,
});
API Reference
Address Management
const address = await client.addresses.create({
webhookUrl: 'https://your-app.com/webhook',
isPermanent: true,
});
const address = await client.addresses.create('https://your-app.com/webhook', true);
const addresses = await client.addresses.list();
const address = await client.addresses.get('addr_123');
const updated = await client.addresses.update('addr_123', {
webhookUrl: 'https://new-webhook.com',
isEnabled: false,
});
await client.addresses.delete('addr_123');
await client.addresses.enable('addr_123');
await client.addresses.disable('addr_123');
await client.addresses.updateWebhookUrl('addr_123', 'https://new-webhook.com');
Usage Statistics
const usage = await client.usage.get();
console.log({
totalEmails: usage.totalEmails,
successfulEmails: usage.successfulEmails,
failedWebhooks: usage.failedWebhooks,
successRate: usage.successRate,
});
const events = await client.usage.events(100);
const januaryUsage = await client.usage.period('2024-01');
const lastMonth = await client.usage.previousMonth();
const specificMonth = await client.usage.forMonth(2024, 1);
Billing Information
const billing = await client.billing.get();
console.log({
currentCost: billing.currentCost,
projectedCost: billing.projectedCost,
hasSpendingLimit: billing.spendingLimit.hasLimit,
isOverLimit: billing.spendingLimit.isOverLimit,
});
const breakdown = await client.billing.breakdown();
const isOverLimit = await client.billing.isOverSpendingLimit();
const remaining = await client.billing.getRemainingBudget();
const projected = await client.billing.getProjectedCost();
Account Overview
const overview = await client.account.overview();
console.log({
addresses: overview.addresses,
usage: overview.usage,
billing: overview.billing,
summary: overview.summary,
});
const summary = await client.account.summary();
const isHealthy = await client.account.isHealthy();
const activeCount = await client.account.getActiveAddressCount();
const monthlyEmails = await client.account.getCurrentMonthEmailCount();
const monthlyCost = await client.account.getCurrentMonthCost();
Error Handling
The SDK provides comprehensive error handling with specific exception types:
import {
MeruException,
AuthenticationException,
ValidationException,
RateLimitException,
} from '@meruhook/node-sdk';
try {
const address = await client.addresses.create({
webhookUrl: 'invalid-url',
isPermanent: true,
});
} catch (error) {
if (error instanceof AuthenticationException) {
console.error('Authentication failed:', error.message);
} else if (error instanceof ValidationException) {
console.error('Validation errors:', error.getAllErrorMessages());
const emailErrors = error.getFieldErrors('email');
} else if (error instanceof RateLimitException) {
const retryAfter = error.getRetryAfterSeconds();
console.error(`Rate limited. Retry after ${retryAfter} seconds`);
} else if (error instanceof MeruException) {
console.error('API error:', error.getErrorDetails());
} else {
console.error('Unexpected error:', error);
}
}
TypeScript Support
The SDK is built with TypeScript and provides comprehensive type definitions:
import type {
Address,
Usage,
Billing,
AccountOverview,
CreateAddressRequest,
UpdateAddressRequest,
MeruClientConfig,
} from '@meruhook/node-sdk';
const address: Address = await client.addresses.get('addr_123');
const usage: Usage = await client.usage.get();
const params: CreateAddressRequest = {
webhookUrl: 'https://example.com/webhook',
isPermanent: true,
};
const newAddress = await client.addresses.create(params);
Security
The Meru Node.js SDK is built with security as a top priority. It includes comprehensive security measures to protect against common vulnerabilities.
Security Features
🔒 Input Validation & Sanitization
- All webhook URLs are validated to ensure they use HTTPS in production
- UUID format validation for all address IDs
- Header injection prevention through strict character filtering
- JSON bomb protection with configurable size and depth limits
- Private network and localhost blocking to prevent SSRF attacks
🛡️ Token Security
- API tokens are automatically masked in debug logs
- Sensitive headers are never logged
- Authorization information is redacted from URLs in logs
- Configurable token redaction for enhanced privacy
⚡ Rate Limiting & Performance
- Intelligent rate limiting with automatic Retry-After header respect
- Exponential backoff with jitter for optimal retry behavior
- Request queuing during rate limit periods
- Configurable response size limits (default: 10MB)
🌐 URL Security
- HTTPS enforcement for all production webhooks
- URL shortener detection and blocking
- Metadata service hostname blocking (AWS, GCP, Azure)
- Query parameter validation and sanitization
Security Configuration
const client = new MeruClient({
apiToken: process.env.MERU_API_TOKEN!,
baseUrl: 'https://api.meruhook.com',
maxResponseSize: 10 * 1024 * 1024,
timeout: 30000,
debug: false,
});
Security Best Practices
🔑 API Token Management
const client = new MeruClient({
apiToken: process.env.MERU_API_TOKEN!
});
const client = new MeruClient({
apiToken: 'sk_live_abc123...'
});
🌍 Webhook URL Validation
await client.addresses.create({
webhookUrl: 'https://your-app.com/webhook',
isPermanent: true
});
await client.addresses.create({
webhookUrl: 'http://your-app.com/webhook',
isPermanent: true
});
🔒 Environment-Specific Configuration
const isDevelopment = process.env.NODE_ENV === 'development';
const client = new MeruClient({
apiToken: process.env.MERU_API_TOKEN!,
baseUrl: isDevelopment
? 'http://localhost:8080'
: 'https://api.meruhook.com',
debug: isDevelopment
});
🛡️ Webhook Verification
import crypto from 'crypto';
function verifyWebhookSignature(payload: string, signature: string, secret: string): boolean {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
When deploying your application, ensure these security headers are set:
# nginx configuration
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
add_header Content-Security-Policy "default-src 'self'";
Error Handling & Security
The SDK provides specific exception types that help maintain security:
import {
AuthenticationException,
ValidationException,
RateLimitException,
} from '@meruhook/node-sdk';
try {
await client.addresses.create({
webhookUrl: 'https://your-app.com/webhook'
});
} catch (error) {
if (error instanceof ValidationException) {
console.error('Security validation failed:', error.getAllErrorMessages());
} else if (error instanceof RateLimitException) {
const retryAfter = error.getRetryAfterSeconds();
console.log(`Rate limited. Retry after ${retryAfter} seconds`);
}
}
Security Audit
The SDK includes built-in security auditing tools:
bun run security:audit
bun run lint:security
bun run precommit
Reporting Security Vulnerabilities
If you discover a security vulnerability, please report it responsibly:
- Do not create a public GitHub issue
- Email security details to: security@meruhook.com
- Include:
- Description of the vulnerability
- Steps to reproduce
- Potential impact assessment
- Suggested fix (if available)
We will respond within 24 hours and provide regular updates on the resolution progress.
Development
Prerequisites
- Node.js 18+ or Bun 1.0+
- TypeScript 5.0+
Setup
git clone https://github.com/meruhook/node-sdk.git
cd node-sdk
bun install
bun run type-check
bun test
bun run build
Testing
The SDK includes comprehensive tests using Bun test:
bun test
bun test --coverage
bun test --watch
bun test tests/address-resource.test.ts
Contributing
We welcome contributions! Please see our Contributing Guide for details.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature)
- Make your changes and add tests
- Ensure all tests pass (
bun test)
- Commit your changes (
git commit -m 'Add amazing feature')
- Push to the branch (
git push origin feature/amazing-feature)
- Open a Pull Request
License
This project is licensed under the MIT License - see the LICENSE file for details.
Support
Changelog
See CHANGELOG.md for a list of changes and version history.
Built with ❤️ by the Meru team.