@crontify/sdk
v1.0.1
Published
Official Node.js SDK for Crontify — cron job and scheduled task monitoring
Maintainers
Readme
@crontify/sdk
Official Node.js/TypeScript SDK for Crontify — cron job and scheduled task monitoring.
Never miss a failed or silent cron job again. Crontify detects missed runs, hung jobs, slow jobs, and explicit failures — and alerts you immediately via Slack, Discord, email, or webhook.
Installation
npm install @crontify/sdkQuick Start
import { CrontifyMonitor } from '@crontify/sdk';
const monitor = new CrontifyMonitor({
apiKey: process.env.CRONTIFY_API_KEY!,
monitorId: 'your-monitor-id', // From your Crontify dashboard
});
// Wrap any async function — start/success/fail handled automatically
await monitor.wrap(async () => {
await sendDailyEmails();
});API
new CrontifyMonitor(options)
Creates a monitor instance for a single job.
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| apiKey | string | required | Your Crontify API key |
| monitorId | string | required | Monitor ID from dashboard |
| baseUrl | string | https://api.crontify.com | API base URL (for self-hosted) |
| timeout | number | 10000 | Request timeout in ms |
| silent | boolean | true | If true, ping failures won't crash your job |
monitor.start() → Promise<PingResponse | null>
Signal that the job has started. Call at the top of your handler.
monitor.success(options?) → Promise<PingResponse | null>
Signal successful completion. Call at the end of your handler.
| Option | Type | Description |
|--------|------|-------------|
| meta | Record<string, number> | Arbitrary numeric metadata stored on the run and evaluated against alert rules |
await monitor.success({
meta: {
rows_processed: 1523,
emails_sent: 48,
duration_ms: 3200,
},
});If any configured alert rule matches a field in meta, a silent_failure alert fires even though the job succeeded. For example, a rule of rows_processed eq 0 will alert if you pass meta: { rows_processed: 0 }.
monitor.fail(options?) → Promise<PingResponse | null>
Signal failure. Call in your catch block.
| Option | Type | Description |
|--------|------|-------------|
| message | string | Human-readable error message |
| meta | Record<string, number> | Arbitrary numeric metadata stored on the run |
| log | string | Full log output (stdout/stderr). Truncated to 10,000 characters. Stored separately and never fetched in list queries. |
await monitor.fail({
message: error.message,
log: error.stack,
meta: { exit_code: 1 },
});monitor.wrap(fn) → Promise<T>
Automatically wraps an async function with start(), success(), and fail(). The return value is preserved. Errors are re-thrown after fail() is called.
On error, wrap() automatically captures error.stack as the log field — so you get full stack traces in your run detail view with no extra code.
// Basic usage
await monitor.wrap(async () => {
await processOrders();
});
// With metadata on success
await monitor.wrap(
async () => {
const result = await processOrders();
return result;
},
{
meta: (result) => ({ orders_processed: result.count }),
},
);new CrontifyClient(options)
Manages multiple monitors from one API key. Use .monitor(id) to get a CrontifyMonitor instance per job. Instances are cached.
import { CrontifyClient } from '@crontify/sdk';
const crontify = new CrontifyClient({
apiKey: process.env.CRONTIFY_API_KEY!,
});
const emailMonitor = crontify.monitor('email-job-id');
const reportMonitor = crontify.monitor('report-job-id');Metadata and Alert Rules
You can attach numeric metadata to any ping. This metadata is stored on the run record and displayed in the dashboard.
More importantly, you can configure alert rules on each monitor in the dashboard. A rule defines a condition — if the condition is true for the metadata on a given run, a silent_failure alert fires even if the job completed without error.
Example rules:
| Field | Operator | Threshold | Fires when... |
|-------|----------|-----------|---------------|
| rows_processed | eq | 0 | Job processed zero rows |
| rows_processed | lt | 100 | Fewer than 100 rows processed |
| error_count | gt | 0 | Any errors occurred |
| exit_code | ne | 0 | Non-zero exit code |
Rules are evaluated per-field. If a field is absent from the ping payload, that rule is silently skipped — a missing field and a zero value are treated differently by design.
Log Attachment
Attach log output to any fail ping. This is useful for capturing stderr, stack traces, or job output for debugging directly in the Crontify dashboard.
try {
await runJob();
} catch (error) {
await monitor.fail({
message: error.message,
log: error.stack, // Stored separately — never bloats run list queries
});
}Logs are stored in a separate table and only fetched when you open a specific run in the dashboard. They are truncated to 10,000 characters server-side.
When using wrap(), stack traces are captured automatically — you don't need to pass log manually.
You can also retrieve the log programmatically:
GET /api/v1/monitors/:monitorId/runs/:runId/log
Authorization: Bearer <jwt>Response:
{
"hasLog": true,
"content": "Error: Connection refused\n at ..."
}Integration Examples
node-cron
import cron from 'node-cron';
import { CrontifyMonitor } from '@crontify/sdk';
const monitor = new CrontifyMonitor({
apiKey: process.env.CRONTIFY_API_KEY!,
monitorId: process.env.CRONTIFY_DAILY_EMAIL_MONITOR_ID!,
});
// Using wrap() — simplest approach
cron.schedule('0 9 * * *', async () => {
await monitor.wrap(async () => {
await sendDailyDigestEmails();
});
});
// Manual start/success/fail with metadata
cron.schedule('*/5 * * * *', async () => {
await monitor.start();
try {
const result = await syncInventory();
await monitor.success({
meta: { rows_synced: result.count },
});
} catch (error) {
await monitor.fail({
message: error instanceof Error ? error.message : 'Sync failed',
log: error instanceof Error ? error.stack : undefined,
});
}
});BullMQ
import { Worker } from 'bullmq';
import { CrontifyClient } from '@crontify/sdk';
const crontify = new CrontifyClient({
apiKey: process.env.CRONTIFY_API_KEY!,
});
const worker = new Worker(
'report-queue',
async (job) => {
const monitor = crontify.monitor(process.env.CRONTIFY_REPORT_MONITOR_ID!);
await monitor.wrap(async () => {
await generateWeeklyReport(job.data);
});
},
{ connection: { host: 'localhost', port: 6379 } },
);
worker.on('failed', (job, error) => {
console.error(`Job ${job?.id} failed:`, error.message);
});Agenda
import Agenda from 'agenda';
import { CrontifyMonitor } from '@crontify/sdk';
const agenda = new Agenda({ db: { address: process.env.MONGODB_URL! } });
const invoiceMonitor = new CrontifyMonitor({
apiKey: process.env.CRONTIFY_API_KEY!,
monitorId: process.env.CRONTIFY_INVOICE_MONITOR_ID!,
});
agenda.define('send monthly invoices', async (job) => {
await invoiceMonitor.wrap(async () => {
const customers = await getCustomersWithActiveSubscriptions();
await Promise.all(customers.map((c) => sendInvoice(c)));
});
});
agenda.every('1 month', 'send monthly invoices');
await agenda.start();setInterval / Plain async
import { CrontifyMonitor } from '@crontify/sdk';
const monitor = new CrontifyMonitor({
apiKey: process.env.CRONTIFY_API_KEY!,
monitorId: process.env.CRONTIFY_HEALTH_CHECK_MONITOR_ID!,
});
// Run every 5 minutes
setInterval(async () => {
await monitor.wrap(async () => {
const status = await checkExternalApiHealth();
if (!status.ok) throw new Error(`API unhealthy: ${status.reason}`);
});
}, 5 * 60 * 1000);Error Handling
In silent: true mode (the default), ping failures are logged as warnings and never crash your job. This is the recommended production setting — a monitoring outage should never take down your scheduled tasks.
const monitor = new CrontifyMonitor({
apiKey: process.env.CRONTIFY_API_KEY!,
monitorId: 'your-monitor-id',
silent: true, // default — ping errors are console.warn'd, not thrown
});If you want ping failures to be thrown (e.g. in tests):
const monitor = new CrontifyMonitor({
apiKey: process.env.CRONTIFY_API_KEY!,
monitorId: 'your-monitor-id',
silent: false,
});Error types
import {
CrontifyApiError,
CrontifyTimeoutError,
CrontifyNetworkError,
} from '@crontify/sdk';
try {
await monitor.start();
} catch (error) {
if (error instanceof CrontifyApiError) {
// HTTP error from the API (4xx / 5xx)
console.error(error.statusCode, error.message);
} else if (error instanceof CrontifyTimeoutError) {
// Request timed out
} else if (error instanceof CrontifyNetworkError) {
// Connection refused, DNS failure, etc.
}
}TypeScript
This package ships with full TypeScript definitions. No @types package needed.
import type { CrontifyOptions, PingResponse, FailOptions } from '@crontify/sdk';Requirements
- Node.js >= 18 (uses native
fetch) - TypeScript >= 5.0 (if using TypeScript)
License
MIT
