@blackleafmail/api (1.0.0)
Installation
@blackleafmail:registry=npm install @blackleafmail/api@1.0.0"@blackleafmail/api": "1.0.0"About this package
BlackLeaf Mail TypeScript SDK
TypeScript SDK for the BlackLeaf Mail API, providing easy access to email, calendar, and contact management features with full type safety.
Table of Contents
- Installation
- Authentication
- Quick Start
- Domain Management
- Email Operations
- Calendar Management
- Contacts Management
- Delta Sync
- Service Accounts
- Shared Mailboxes
- Error Handling
- Huly Integration
- Development
- Testing
Installation
Quick Install (with registry flag)
The fastest way to install is to specify the registry directly:
npm install @blackleafmail/api --registry=https://forge.blackleafdigital.com/api/packages/zachhandley/npm/
# or
pnpm add @blackleafmail/api --registry=https://forge.blackleafdigital.com/api/packages/zachhandley/npm/
# or
yarn add @blackleafmail/api --registry=https://forge.blackleafdigital.com/api/packages/zachhandley/npm/
Recommended: Configure Scope Registry
Set up the registry once for the @blackleafmail scope:
npm config set @blackleafmail:registry https://forge.blackleafdigital.com/api/packages/zachhandley/npm/
npm install @blackleafmail/api
# or with pnpm
pnpm config set @blackleafmail:registry https://forge.blackleafdigital.com/api/packages/zachhandley/npm/
pnpm add @blackleafmail/api
# or with yarn
yarn config set @blackleafmail:registry https://forge.blackleafdigital.com/api/packages/zachhandley/npm/
yarn add @blackleafmail/api
For Teams: Use Project .npmrc
Add a .npmrc file to your project root:
@blackleafmail:registry=https://forge.blackleafdigital.com/api/packages/zachhandley/npm/
Then install normally:
npm install @blackleafmail/api
# or
pnpm add @blackleafmail/api
# or
yarn add @blackleafmail/api
Package Reference in package.json
After publishing, you can reference the package in your package.json:
{
"dependencies": {
"@blackleafmail/api": "^1.0.0"
}
}
This replaces local file references like "file:../../../../BillionMail/sdk/typescript".
Authentication
The SDK supports two authentication methods:
API Key Authentication
import { BlackLeafMailClient } from '@blackleafmail/api';
const client = new BlackLeafMailClient({
baseURL: 'https://mail.yourserver.com',
apiKey: 'your-api-key'
});
OAuth 2.0 Authentication
import { BlackLeafMailClient, OAuthHelper } from '@blackleafmail/api';
// Initialize OAuth helper
const oauth = new OAuthHelper({
baseURL: 'https://mail.yourserver.com',
clientId: 'your-client-id',
clientSecret: 'your-client-secret'
});
// Generate authorization URL
const authUrl = oauth.getAuthorizationUrl(
'https://yourapp.com/callback',
'email calendar contacts',
'random-state-string'
);
// After user authorizes, exchange code for token
const tokens = await oauth.exchangeCodeForToken(code, redirectURI);
// Create client with access token
const client = new BlackLeafMailClient({
baseURL: 'https://mail.yourserver.com',
accessToken: tokens.access_token
});
// Refresh token when needed
const refreshed = await oauth.refreshToken(tokens.refresh_token);
client.updateAccessToken(refreshed.access_token);
Quick Start
import { BlackLeafMailClient } from '@blackleafmail/api';
const client = new BlackLeafMailClient({
baseURL: 'https://mail.yourserver.com',
apiKey: 'your-api-key'
});
// Add a domain
await client.domains.create({
domain: 'example.com',
hostname: 'mail.example.com',
mailboxes: 100
});
// Send an email
await client.emails.send('mailbox-id', {
to: ['user@example.com'],
subject: 'Hello World',
body: 'This is a test email'
});
// List calendars
const calendars = await client.calendar.listCalendars('mailbox-id');
// Get contacts
const contacts = await client.contacts.list('mailbox-id');
Domain Management
The SDK provides comprehensive domain management including automatic DNS setup via Domain Connect.
Adding a Domain
const domain = await client.domains.create({
domain: 'example.com',
hostname: 'mail.example.com',
mailboxes: 100,
mailboxQuota: 5242880, // 5GB per mailbox
quota: 524288000, // 500GB total
rateLimit: 12
});
Domain Connect (Automatic DNS Setup)
Check if a domain supports Domain Connect for automatic DNS configuration:
const support = await client.domains.checkDomainConnectSupport('example.com');
if (support.supported) {
// Generate setup URL
const { apply_url } = await client.domains.generateDomainConnectURL('example.com');
// Redirect user to DNS provider
window.location.href = apply_url;
}
Verify DNS Configuration
const dns = await client.domains.verifyDNS('example.com');
const isReady = dns.SPF.valid &&
dns.DKIM.valid &&
dns.DMARC.valid &&
dns.MX.valid &&
dns.A.valid;
console.log('Domain ready:', isReady);
List Domains
const result = await client.domains.list({
page: 1,
page_size: 20,
keyword: 'example' // Optional filter
});
console.log(`Found ${result.total} domains`);
Complete Integration Example
See examples/domain-management.ts for a complete Huly integration flow including:
- Adding domains
- Automatic setup via Domain Connect
- DNS verification polling
- Fallback to manual setup
Complete Huly Integration Example
// Add domain for a customer
await client.domains.create({
domain: customerDomain,
email: customerEmail
});
// Check Domain Connect and redirect
const support = await client.domains.checkDomainConnectSupport(customerDomain);
if (support.supported) {
const { apply_url } = await client.domains.generateDomainConnectURL(customerDomain);
// Store in Huly task and send to customer
await huly.tasks.create({
title: `Configure DNS for ${customerDomain}`,
metadata: { setupURL: apply_url }
});
}
// Poll for verification
const dns = await client.domains.verifyDNS(customerDomain);
if (dns.SPF.valid && dns.DKIM.valid) {
await huly.notifications.send('Domain is ready!');
}
Email Operations
Send Email
const email = await client.emails.send('mailbox-id', {
to: ['recipient@example.com'],
cc: ['cc@example.com'],
bcc: ['bcc@example.com'],
subject: 'Important Update',
body: '<h1>Hello!</h1><p>This is an HTML email.</p>',
attachments: [
{
filename: 'document.pdf',
content: base64Content,
contentType: 'application/pdf'
}
]
});
List Emails
const result = await client.emails.list('mailbox-id', {
page: 1,
page_size: 50,
folder: 'INBOX',
unread: true
});
console.log(`Total emails: ${result.total}`);
result.items.forEach(email => {
console.log(`${email.subject} from ${email.from}`);
});
Search Emails
const emails = await client.emails.search('mailbox-id', 'important project', {
from: 'boss@company.com',
after: '2025-01-01',
has_attachment: true
});
Get Email Details
const email = await client.emails.get('email-id');
console.log(email.subject);
console.log(email.body);
console.log(email.attachments);
Delete Email
await client.emails.delete('email-id');
Calendar Management
The SDK provides full CalDAV support for calendar operations.
List Calendars
const calendars = await client.calendar.listCalendars('mailbox-id');
calendars.forEach(cal => {
console.log(`${cal.name}: ${cal.description}`);
});
Create Calendar
const calendar = await client.calendar.createCalendar('mailbox-id', {
name: 'Work Calendar',
description: 'My work events',
color: '#FF5733'
});
List Events
const events = await client.calendar.listEvents('calendar-id', {
start_date: '2025-08-01',
end_date: '2025-08-31'
});
events.forEach(event => {
console.log(`${event.summary} at ${event.start}`);
});
Create Event
const event = await client.calendar.createEvent('calendar-id', {
summary: 'Team Meeting',
description: 'Weekly sync-up',
start: '2025-08-19T10:00:00Z',
end: '2025-08-19T11:00:00Z',
location: 'Conference Room A',
attendees: ['alice@example.com', 'bob@example.com']
});
Update Event
const updated = await client.calendar.updateEvent('event-id', {
summary: 'Updated Meeting Title',
start: '2025-08-19T11:00:00Z',
end: '2025-08-19T12:00:00Z'
});
Delete Event
await client.calendar.deleteEvent('event-id');
Configure Webhook
const webhook = await client.calendar.configureWebhook('calendar-id', {
url: 'https://yourapp.com/webhooks/calendar',
events: ['created', 'updated', 'deleted'],
secret: 'webhook-secret'
});
Contacts Management
The SDK provides full CardDAV support for contact operations.
List Contacts
const result = await client.contacts.list('mailbox-id', {
page: 1,
page_size: 100,
search: 'john'
});
result.items.forEach(contact => {
console.log(`${contact.name} - ${contact.email}`);
});
Create Contact
const contact = await client.contacts.create('mailbox-id', {
name: 'John Doe',
email: 'john@example.com',
phone: '+1234567890',
company: 'Acme Corp',
title: 'Senior Engineer',
address: {
street: '123 Main St',
city: 'San Francisco',
state: 'CA',
zip: '94102',
country: 'USA'
},
notes: 'Met at conference 2025'
});
Update Contact
const updated = await client.contacts.update('contact-id', {
phone: '+1987654321',
title: 'Lead Engineer'
});
Delete Contact
await client.contacts.delete('contact-id');
Import Contacts
const file = new File([vCardContent], 'contacts.vcf', { type: 'text/vcard' });
const result = await client.contacts.import('mailbox-id', file);
console.log(`Imported ${result.imported} contacts`);
console.log(`Skipped ${result.skipped} duplicates`);
Export Contacts
// Export as vCard
const vcard = await client.contacts.export('mailbox-id', 'vcard');
// Export as CSV
const csv = await client.contacts.export('mailbox-id', 'csv');
// Download the file
const url = URL.createObjectURL(csv);
const a = document.createElement('a');
a.href = url;
a.download = 'contacts.csv';
a.click();
Delta Sync
Microsoft Graph-compatible delta sync for efficient synchronization.
Start Delta Sync
const cursor = await client.sync.startDelta('mailbox-id', [
'emails',
'contacts',
'events'
]);
console.log(`Initial cursor: ${cursor.token}`);
Get Changes
const delta = await client.sync.getChanges(cursor.token, 100);
delta.changes.forEach(change => {
console.log(`${change.type}: ${change.entity_type} ${change.entity_id}`);
// change.type: 'created' | 'updated' | 'deleted'
// change.data: Full entity data (for created/updated)
});
// Save next cursor for subsequent syncs
const nextCursor = delta.nextCursor;
Get Changes Since Date
const delta = await client.sync.getChangesSince(
'mailbox-id',
['emails'],
new Date('2025-08-01'),
50
);
Stream Changes (Continuous Sync)
const cursor = await client.sync.startDelta('mailbox-id', ['emails']);
// Continuously poll for changes
for await (const changes of client.sync.streamChanges(cursor.token, 5000)) {
console.log(`Received ${changes.length} changes`);
changes.forEach(change => {
if (change.type === 'created') {
console.log('New email:', change.data);
} else if (change.type === 'updated') {
console.log('Updated email:', change.data);
} else if (change.type === 'deleted') {
console.log('Deleted email:', change.entity_id);
}
});
}
Service Accounts
Manage API keys and service accounts for programmatic access.
List Service Accounts
const accounts = await client.serviceAccounts.list();
Create Service Account
const account = await client.serviceAccounts.create({
name: 'Integration Service',
description: 'API access for integration',
scopes: ['email:read', 'email:write', 'contacts:read']
});
Generate API Key
const apiKey = await client.serviceAccounts.generateAPIKey(account.id, {
name: 'Production Key',
expires_at: '2026-12-31',
scopes: ['email:read', 'email:write']
});
// IMPORTANT: Save the key securely - it won't be shown again
console.log(`API Key: ${apiKey.key}`);
List API Keys
const keys = await client.serviceAccounts.listAPIKeys(account.id);
keys.forEach(key => {
console.log(`${key.name} - Created: ${key.created_at}, Expires: ${key.expires_at}`);
});
Revoke API Key
await client.serviceAccounts.revokeAPIKey(account.id, key.id);
Shared Mailboxes
Create and manage shared mailboxes for team collaboration.
List Shared Mailboxes
const mailboxes = await client.sharedMailboxes.list();
Create Shared Mailbox
const mailbox = await client.sharedMailboxes.create({
email: 'support@example.com',
name: 'Customer Support',
description: 'Team inbox for customer support'
});
Grant Access
await client.sharedMailboxes.grantAccess(
mailbox.id,
'service-account-id',
'write' // 'read' | 'write' | 'admin'
);
Revoke Access
await client.sharedMailboxes.revokeAccess(mailbox.id, 'service-account-id');
Error Handling
The SDK provides typed error classes for proper error handling.
import {
BlackLeafMailClient,
AuthenticationError,
ValidationError,
RateLimitError,
NotFoundError,
ServerError
} from '@blackleafmail/api';
try {
await client.emails.send('mailbox-id', emailData);
} catch (error) {
if (error instanceof AuthenticationError) {
console.error('Authentication failed:', error.message);
// Refresh token or re-authenticate
} else if (error instanceof ValidationError) {
console.error('Invalid data:', error.errors);
// Fix validation errors
} else if (error instanceof RateLimitError) {
console.error('Rate limit exceeded, retry after:', error.retryAfter);
// Wait and retry
} else if (error instanceof NotFoundError) {
console.error('Resource not found:', error.message);
} else if (error instanceof ServerError) {
console.error('Server error:', error.message);
// Retry with backoff
} else {
console.error('Unexpected error:', error);
}
}
Huly Integration
Complete integration pattern for managing customer domains through Huly.
Add Domain with Automated Setup
async function addCustomerDomain(customerDomain: string, customerEmail: string) {
// 1. Create domain in BlackLeaf Mail
const domain = await client.domains.create({
domain: customerDomain,
hostname: `mail.${customerDomain}`,
email: customerEmail,
mailboxes: 10
});
// 2. Check if Domain Connect is supported
const support = await client.domains.checkDomainConnectSupport(customerDomain);
if (support.supported) {
// 3. Generate automatic setup URL
const { apply_url } = await client.domains.generateDomainConnectURL(customerDomain);
// 4. Create Huly task with setup link
await huly.tasks.create({
title: `Configure DNS for ${customerDomain}`,
description: `Automatic DNS setup available`,
assignee: customerEmail,
metadata: {
domain: customerDomain,
setupURL: apply_url,
provider: support.provider
},
actions: [
{
label: 'Configure DNS',
url: apply_url
}
]
});
return { method: 'automatic', setupURL: apply_url };
} else {
// 5. Fallback to manual setup
const dns = await client.domains.verifyDNS(customerDomain);
// 6. Create Huly task with manual instructions
await huly.tasks.create({
title: `Configure DNS for ${customerDomain}`,
description: 'Manual DNS configuration required',
assignee: customerEmail,
metadata: {
domain: customerDomain,
records: dns
},
instructions: generateManualInstructions(dns)
});
return { method: 'manual', records: dns };
}
}
Poll for DNS Verification
async function waitForDNSVerification(domain: string, maxAttempts = 30) {
for (let i = 0; i < maxAttempts; i++) {
const dns = await client.domains.verifyDNS(domain);
const isReady = dns.SPF.valid &&
dns.DKIM.valid &&
dns.DMARC.valid &&
dns.MX.valid &&
dns.A.valid;
if (isReady) {
// Notify via Huly
await huly.notifications.send({
title: 'Domain Ready!',
message: `${domain} is fully configured and ready to send emails`,
priority: 'high'
});
return { verified: true, dns };
}
// Update Huly task with progress
await huly.tasks.update(taskId, {
status: 'in_progress',
metadata: {
verification_progress: {
SPF: dns.SPF.valid,
DKIM: dns.DKIM.valid,
DMARC: dns.DMARC.valid,
MX: dns.MX.valid,
A: dns.A.valid
}
}
});
// Wait 30 seconds before next check
await new Promise(resolve => setTimeout(resolve, 30000));
}
return { verified: false };
}
Development
Setup
# Clone the repository
git clone https://github.com/aaPanel/BillionMail.git
cd BillionMail/sdk/typescript
# Install dependencies
npm install
# or
pnpm install
Build
# Build the SDK
npm run build
# Build in watch mode
npm run build:watch
Code Quality
# Run linter
npm run lint
# Fix linting issues
npm run lint:fix
# Format code
npm run format
Testing
Running Tests
# Run all tests
npm test
# Run tests in watch mode
npm run test:watch
# Run tests with coverage
npm run test:coverage
Integration Tests
# Set up test environment
cp .env.example .env.test
# Edit .env.test with test credentials
# Run integration tests
npm run test:integration
Example Test
import { BlackLeafMailClient } from '../src';
describe('BlackLeaf Mail Client', () => {
let client: BlackLeafMailClient;
beforeAll(() => {
client = new BlackLeafMailClient({
baseURL: process.env.TEST_BASE_URL!,
apiKey: process.env.TEST_API_KEY!
});
});
it('should send an email', async () => {
const email = await client.emails.send('test-mailbox-id', {
to: ['test@example.com'],
subject: 'Test Email',
body: 'This is a test'
});
expect(email.id).toBeDefined();
expect(email.subject).toBe('Test Email');
});
it('should list domains', async () => {
const result = await client.domains.list({ page: 1, page_size: 10 });
expect(result.items).toBeInstanceOf(Array);
expect(result.total).toBeGreaterThanOrEqual(0);
});
});
API Reference
Client Configuration
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
baseURL |
string |
Yes | - | Base URL of the BlackLeaf Mail API |
apiKey |
string |
No* | - | API key for authentication |
accessToken |
string |
No* | - | OAuth access token for authentication |
timeout |
number |
No | 30000 |
Request timeout in milliseconds |
maxRetries |
number |
No | 3 |
Maximum retry attempts for failed requests |
retryDelay |
number |
No | 1000 |
Delay between retries in milliseconds |
*Either apiKey or accessToken is required
Resource Methods
Domains
list(params?)- List domainscreate(domain)- Create a domainget(domainId)- Get domain detailsupdate(domainId, updates)- Update domaindelete(domainId)- Delete domaincheckDomainConnectSupport(domain)- Check Domain Connect supportgenerateDomainConnectURL(domain)- Generate Domain Connect setup URLverifyDNS(domain)- Verify DNS configuration
Emails
list(mailboxId, params?)- List emailsget(emailId)- Get email detailssend(mailboxId, email)- Send an emaildelete(emailId)- Delete emailsearch(mailboxId, query, params?)- Search emails
Calendar
listCalendars(mailboxId)- List calendarscreateCalendar(mailboxId, calendar)- Create calendarlistEvents(calendarId, params?)- List eventscreateEvent(calendarId, event)- Create eventupdateEvent(eventId, event)- Update eventdeleteEvent(eventId)- Delete eventconfigureWebhook(calendarId, webhook)- Configure webhook
Contacts
list(mailboxId, params?)- List contactsget(contactId)- Get contact detailscreate(mailboxId, contact)- Create contactupdate(contactId, contact)- Update contactdelete(contactId)- Delete contactimport(mailboxId, file)- Import contactsexport(mailboxId, format)- Export contacts
Sync
startDelta(mailboxId, entityTypes)- Start delta syncgetChanges(cursor, limit?)- Get changesgetChangesSince(mailboxId, entityTypes, since, limit?)- Get changes since datestreamChanges(cursor, pollInterval?)- Stream changes continuously
Service Accounts
list()- List service accountscreate(account)- Create service accountgenerateAPIKey(accountId, keyConfig)- Generate API keylistAPIKeys(accountId)- List API keysrevokeAPIKey(accountId, keyId)- Revoke API key
Shared Mailboxes
list()- List shared mailboxescreate(mailbox)- Create shared mailboxgrantAccess(mailboxId, serviceAccountId, permission)- Grant accessrevokeAccess(mailboxId, serviceAccountId)- Revoke access
Contributing
We welcome contributions! Please see our Contributing Guide for details.
Pull Request Process
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Add tests for new functionality
- Ensure tests pass (
npm 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
MIT License - see the LICENSE file for details.
Support
- Documentation: https://docs.blackleafmail.com
- Issues: GitHub Issues
- Discord: Join our Discord
- Email: support@blackleafmail.com
Changelog
See CHANGELOG.md for version history and release notes.
Dependencies
Dependencies
| ID | Version |
|---|---|
| axios | ^1.6.0 |
Development dependencies
| ID | Version |
|---|---|
| @types/jest | ^29.0.0 |
| @types/node | ^20.0.0 |
| jest | ^29.0.0 |
| ts-jest | ^29.0.0 |
| typescript | ^5.0.0 |