The official Node.js / TypeScript SDK for the CycleTask Mail Forwarding API.
- Zero runtime dependencies (native
fetch, Node 18+) - Full TypeScript type coverage
- Automatic retries with exponential back-off (429 & 5xx)
- ESM and CommonJS dual-module support
npm install @cycletask/nodeyarn add @cycletask/nodepnpm add @cycletask/nodeimport CycleTask from '@cycletask/node';
const cycletask = new CycleTask('ct_live_your_api_key');
// List your domains
const domains = await cycletask.domains.list();
console.log(domains);
// Create an alias
const alias = await cycletask.aliases.create({
alias: 'hello',
domainId: domains[0]._id,
destinations: ['me@gmail.com'],
});
console.log(alias.fullAddress); // hello@yourdomain.comconst cycletask = new CycleTask('ct_live_your_api_key', {
baseUrl: 'https://api.task-cycle.com/api', // default
timeout: 30_000, // 30 seconds (default)
maxRetries: 3, // retry on 429 / 5xx (default)
});| Option | Type | Default | Description |
|---|---|---|---|
baseUrl |
string |
https://api.task-cycle.com/api |
API base URL |
timeout |
number |
30000 |
Request timeout in milliseconds |
maxRetries |
number |
3 |
Max retries on 429 / 5xx responses |
// List all domains
const domains = await cycletask.domains.list();
// Create a domain
const domain = await cycletask.domains.create({ domain: 'example.com' });
// Get a domain by ID
const domain = await cycletask.domains.get('domain_id');
// Delete a domain
await cycletask.domains.delete('domain_id');
// Trigger DNS verification
const result = await cycletask.domains.verify('domain_id');
// result.allVerified → boolean
// result.results → { mx, spf, dkim, dmarc }
// Get required DNS records
const dns = await cycletask.domains.getDnsRecords('domain_id');
// dns.records → Array<{ type, host, value, priority?, verified }>// List aliases (with pagination & filtering)
const { data, pagination } = await cycletask.aliases.list({
page: 1,
limit: 20,
search: 'hello',
domain: 'domain_id',
status: 'active',
sort: '-createdAt',
});
// Create an alias
const alias = await cycletask.aliases.create({
alias: 'hello',
domainId: 'domain_id',
destinations: ['me@gmail.com', 'backup@gmail.com'],
description: 'Main contact alias',
privacyMode: false,
});
// Update an alias
const updated = await cycletask.aliases.update('alias_id', {
destinations: ['new@gmail.com'],
isActive: false,
description: 'Updated description',
});
// Delete an alias
await cycletask.aliases.delete('alias_id');
// Get forwarding logs for an alias
const logs = await cycletask.aliases.getLogs('alias_id');// Dashboard overview
const overview = await cycletask.stats.overview();
// overview.domains → { total, verified }
// overview.aliases → { total, active }
// overview.emails → { totalForwarded, delivered, bounced, deferred, rejected }
// Daily chart data
const chart = await cycletask.stats.chart({ days: 30 });
// chart → Array<{ date, forwarded, bounced, deferred, rejected }>
// Global email logs (paginated)
const { data, pagination } = await cycletask.stats.logs({
page: 1,
limit: 50,
status: 'delivered',
search: 'sender@example.com',
});// Service health
const status = await cycletask.status.get();
// 90-day uptime data
const uptime = await cycletask.status.uptime();
// Recent incidents
const incidents = await cycletask.status.incidents();The SDK throws typed errors that you can catch and handle:
import CycleTask, {
CycleTaskError,
AuthenticationError,
NotFoundError,
ValidationError,
RateLimitError,
} from '@cycletask/node';
const cycletask = new CycleTask('ct_live_your_api_key');
try {
await cycletask.domains.create({ domain: 'example.com' });
} catch (err) {
if (err instanceof AuthenticationError) {
// 401 — invalid API key
console.error('Bad API key:', err.message);
} else if (err instanceof ValidationError) {
// 422 — invalid request body
console.error('Validation failed:', err.message);
console.error('Field errors:', err.errors);
} else if (err instanceof NotFoundError) {
// 404
console.error('Not found:', err.message);
} else if (err instanceof RateLimitError) {
// 429 — all retries exhausted
console.error('Rate limited. Retry after:', err.retryAfter, 'seconds');
} else if (err instanceof CycleTaskError) {
// Any other API error
console.error(`API error ${err.statusCode}:`, err.message);
} else {
throw err;
}
}| Class | HTTP Status | Description |
|---|---|---|
CycleTaskError |
any | Base class for all API errors |
AuthenticationError |
401 | Invalid or missing API key |
ForbiddenError |
403 | Insufficient permissions |
NotFoundError |
404 | Resource not found |
ValidationError |
422 | Request body failed validation |
RateLimitError |
429 | Rate limit exceeded after retries |
All error classes expose:
message— human-readable descriptionstatusCode— HTTP status code (or0for network / timeout errors)code— machine-readable error code
ValidationError additionally exposes errors: Record<string, string[]> with per-field messages.
RateLimitError additionally exposes retryAfter: number (seconds until reset).
Requests that receive a 429 Too Many Requests or 5xx response are automatically retried with exponential back-off:
| Attempt | Delay |
|---|---|
| 1st | ~500 ms |
| 2nd | ~1 000 ms |
| 3rd | ~2 000 ms |
Each delay includes up to 25 % random jitter. After maxRetries attempts the SDK throws the underlying error.
Network errors and timeouts are also retried.
Every request parameter and API response is fully typed. Import types directly:
import type {
Domain,
Alias,
EmailLog,
StatsOverview,
PaginatedResponse,
CreateAliasParams,
} from '@cycletask/node';- Node.js 18+ (uses native
fetch) - No runtime dependencies