# What Types of Background Jobs Does Kaneo Run? Complete Scheduler Architecture Guide

> Discover Kaneo background jobs like trial reminders and health checks. Explore its scheduler architecture with croner and PostgreSQL leader-lock for reliable execution.

- Repository: [kaneo.app/kaneo](https://github.com/usekaneo/kaneo)
- Tags: architecture
- Published: 2026-08-30

---

**Kaneo runs periodic background jobs—including trial reminders and health checks—using the croner library with a PostgreSQL-based leader-lock mechanism to ensure single-execution in clustered environments.**

Kaneo is an open-source project management platform built with TypeScript and Node.js. The API layer manages scheduled maintenance tasks through a dedicated scheduler package located in `apps/api/src/scheduler`. All jobs follow a consistent pattern that prevents duplicate execution when running multiple server instances.

## How Kaneo's Scheduler Works

The scheduler implementation lives in [`apps/api/src/scheduler/index.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/scheduler/index.ts) and uses the **croner** library to manage cron-based execution. Rather than running jobs directly, Kaneo wraps each handler with a **leader-lock** utility that coordinates across instances using a PostgreSQL table.

All job registrations follow this pattern in the scheduler index:

```typescript
// apps/api/src/scheduler/index.ts
import { Cron } from "croner";
import { withCheckIn } from "./utils";
import { checkTrialReminders } from "./trial-reminders";

const jobs: Cron[] = [];

jobs.push(
  new Cron(
    "23 * * * *",
    withCheckIn("trial-reminders", checkTrialReminders)
  )
);

```

The `withCheckIn` wrapper automatically handles lease acquisition and release, ensuring that only one instance processes each job during any given time slot.

## Types of Background Jobs in Kaneo

Kaneo currently implements two primary categories of background work: user-facing notification jobs and infrastructure maintenance tasks.

### Trial Reminders

The **trial-reminders** job notifies users when their free trial subscription approaches expiration. According to the source code in [`apps/api/src/scheduler/index.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/scheduler/index.ts), this job runs hourly at minute 23 (`23 * * * *`) via the `checkTrialReminders` handler.

This job queries the database for trials nearing expiration and dispatches email or SMS notifications through Kaneo's notification service. The implementation resides in [`apps/api/src/scheduler/trial-reminders.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/scheduler/trial-reminders.ts) and integrates with the `withCheckIn` utility for safe execution.

### Leader Lock Infrastructure

While technically a scheduling mechanism rather than a business job, the **leader-lock** system functions as a background coordination service that runs continuously. Implemented in [`apps/api/src/scheduler/leader-lock.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/scheduler/leader-lock.ts), this infrastructure manages the `job_lease` table defined in [`apps/api/src/database/schema.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/database/schema.ts).

The schema defines the lock storage as:

```typescript
// apps/api/src/database/schema.ts
export const jobLeaseTable = pgTable("job_lease", {
  name: text("name").notNull(),
  owner: text("owner").notNull(),
  expires_at: timestamp("expires_at").notNull(),
});

```

When any cron job triggers, the system attempts to insert or update a row in this table. If another instance currently holds an unexpired lease for that job name, the current instance skips execution.

## The Leader Lock Implementation Details

The leader-lock mechanism relies on two primary functions exported from [`apps/api/src/scheduler/leader-lock.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/scheduler/leader-lock.ts): `acquireJobLease` and `releaseJobLease`.

When `withCheckIn` wraps a job handler, it performs the following sequence:

1. Calls `acquireJobLease` with the job name and current instance identifier
2. Checks if the returned lease is valid for this instance
3. Executes the job logic only if the lease acquisition succeeds
4. Calls `releaseJobLease` in a finally block to free the slot for the next execution window

This pattern guarantees that even with horizontal scaling across multiple containers or servers, resource-intensive tasks like trial reminder batch processing run exactly once per scheduled interval.

## Creating Custom Background Jobs

Developers extending Kaneo can add new background jobs by following the established pattern in [`apps/api/src/scheduler/index.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/scheduler/index.ts).

First, create a handler function in a new file:

```typescript
// apps/api/src/scheduler/custom-cleanup.ts
export async function cleanupOldActivityLogs() {
  // Delete logs older than 90 days
  // Implementation details here
}

```

Then register the job with an appropriate cron schedule:

```typescript
// apps/api/src/scheduler/index.ts
import { cleanupOldActivityLogs } from "./custom-cleanup";

jobs.push(
  new Cron(
    "0 2 * * *",  // Daily at 2:00 AM
    withCheckIn("activity-cleanup", cleanupOldActivityLogs)
  )
);

```

For direct lease management in complex scenarios, import the leader-lock functions directly:

```typescript
import { acquireJobLease, releaseJobLease } from "./leader-lock";

async function specializedJob() {
  const lease = await acquireJobLease("special-job");
  if (!lease) return;  // Another instance is processing
  
  try {
    // Perform specialized work here
  } finally {
    await releaseJobLease("special-job");
  }
}

```

## Summary

- Kaneo uses the **croner** library in [`apps/api/src/scheduler/index.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/scheduler/index.ts) to schedule recurring tasks with cron expressions.
- The **trial-reminders** job runs hourly to notify users of impending trial expirations.
- A **leader-lock** mechanism in [`apps/api/src/scheduler/leader-lock.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/scheduler/leader-lock.ts) prevents duplicate execution using the `job_lease` PostgreSQL table.
- The `withCheckIn` utility automatically handles lease acquisition and release for standard jobs.
- Developers can extend the scheduler by wrapping new handlers with `withCheckIn` and registering them in the scheduler index file.

## Frequently Asked Questions

### How does Kaneo prevent the same background job from running on multiple servers simultaneously?

Kaneo implements a distributed locking mechanism using the `job_lease` table in PostgreSQL. Before executing any job, the scheduler attempts to acquire a lease for that specific job name. If another instance already holds an unexpired lease, the current instance skips execution. The lease automatically expires based on the `expires_at` timestamp defined in [`apps/api/src/database/schema.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/database/schema.ts).

### What scheduling library does Kaneo use for background jobs?

Kaneo uses **croner**, a lightweight JavaScript cron library. The implementation in [`apps/api/src/scheduler/index.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/scheduler/index.ts) creates new `Cron` instances with schedule expressions and handler functions wrapped by the `withCheckIn` utility.

### How can I add a new periodic task to Kaneo's scheduler?

Create a handler function in `apps/api/src/scheduler/`, then import it into [`apps/api/src/scheduler/index.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/scheduler/index.ts). Register the job using `new Cron(scheduleExpression, withCheckIn("job-name", handler))`. The `withCheckIn` wrapper automatically handles the leader-lock logic, ensuring safe execution in multi-instance deployments.

### Where is the trial reminder logic implemented in the Kaneo codebase?

The trial reminder functionality is implemented in [`apps/api/src/scheduler/trial-reminders.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/scheduler/trial-reminders.ts) and registered in [`apps/api/src/scheduler/index.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/scheduler/index.ts) with the cron schedule `23 * * * *`. This configuration runs the check once per hour at minute 23 to identify and notify users with trials nearing expiration.