npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@crontify/sdk

v1.0.1

Published

Official Node.js SDK for Crontify — cron job and scheduled task monitoring

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/sdk

Quick 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