# How Kaneo Handles Scheduled Jobs: Distributed Cron Architecture Explained

> Discover how Kaneo manages scheduled jobs with a distributed cron architecture. Learn about its PostgreSQL-based distributed locks and Sentry cron monitoring for reliable, exactly-once execution.

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

---

**TLDR:** Kaneo runs all recurring background tasks through an embedded scheduler in the API service that leverages PostgreSQL-based distributed locks to guarantee exactly-once execution across multi-instance deployments, coupled with Sentry cron monitoring for observability.

Scheduled jobs in Kaneo operate without separate worker processes or external queue systems. The open-source project management platform implements a self-contained scheduling framework within `apps/api/src/scheduler` that handles everything from due-date reminders to seat reconciliation directly inside the API service. This design reduces infrastructure overhead while maintaining strict execution guarantees through database-backed leader election.

## Scheduler Architecture in Kaneo

The scheduling system consists of three core layers: a registration bootstrap, a distributed locking mechanism, and domain-specific job implementations.

### Core Components

All scheduling logic resides in the `apps/api/src/scheduler` package. The entry point at [`apps/api/src/scheduler/index.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/scheduler/index.ts) registers four distinct cron jobs and initializes the execution loop. Each job handler wraps its logic with `withJobLease`, a distributed lock utility defined in [`apps/api/src/scheduler/leader-lock.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/scheduler/leader-lock.ts) that prevents concurrent execution across scaled API pods. The lock implementation supports PostgreSQL as the default store with optional Redis backing for high-throughput scenarios.

### Background Job Types

Kaneo currently maintains four production scheduled jobs, each mapped to a specific module:

- **Due-date reminders** – [`apps/api/src/scheduler/reminder-timing.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/scheduler/reminder-timing.ts) scans for tasks approaching deadlines and dispatches notifications every five minutes.
- **Trial reminders** – [`apps/api/src/scheduler/trial-reminders.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/scheduler/trial-reminders.ts) monitors trial periods and sends expiration warnings on an hourly cadence.
- **Seat reconciliation** – [`apps/api/src/scheduler/seat-reconciliation.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/scheduler/seat-reconciliation.ts) performs daily workspace billing calculations to synchronize seat usage with subscription limits.
- **Project webhook reminders** – Handles webhook delivery retries and failure notifications (referenced in Sentry monitoring configuration).

## Distributed Locking for Exactly-Once Execution

Kaneo guarantees **exactly-once execution** through a lease-based concurrency control system. When the API service scales to multiple instances, the `withJobLease` function ensures only one instance processes a given job at a time.

The lock implementation writes a lease row containing `job_id` and `expires_at` timestamps into the `scheduler_leases` table. If an instance successfully acquires the lease, it executes the job callback; otherwise, the job skips for that cycle. This mechanism prevents duplicate work without requiring external coordination services like ZooKeeper or Consul.

```typescript
// apps/api/src/scheduler/seat-reconciliation.ts
import { withJobLease } from "./leader-lock";

export async function reconcileWorkspaceSeats() {
  await withJobLease("seat-reconciliation", async () => {
    // Fetches workspaces, calculates seat usage, updates database records
  });
}

```

The integration test at [`tests/api-integration/leader-lock.test.ts`](https://github.com/usekaneo/kaneo/blob/main/tests/api-integration/leader-lock.test.ts) validates this behavior under race conditions.

## Job Execution Flow

The scheduler follows a four-phase lifecycle from startup to completion:

1. **Registration** – During API server bootstrap, [`apps/api/src/scheduler/index.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/scheduler/index.ts) registers job handlers with their respective cron intervals (5 minutes for due-date checks, 1 hour for trial notifications, daily for seat reconciliation).
2. **Leader Election** – Each job invocation attempts to acquire a distributed lease via `withJobLease`. Failed acquisitions result in immediate termination for that instance.
3. **Domain Execution** – The job-specific callback executes business logic, such as querying overdue tasks or calculating billing metrics.
4. **Health Reporting** – Upon successful completion, the job reports status to Sentry using stable slugs (`due-date-reminders`, `trial-reminders`, `seat-reconciliation`, `project-webhook-reminders`).

## Sentry Monitoring Integration

Kaneo integrates **Sentry Cron Monitoring** to track job health and alert on missed executions. The scheduler calls `Sentry.captureCheckIn` with the job's unique slug after each successful run.

```typescript
// apps/api/src/scheduler/index.ts
import * as Sentry from "@sentry/node";

export function startScheduler() {
  setInterval(async () => {
    await sendDueDateReminders();
    Sentry.captureCheckIn("due-date-reminders", { status: "ok" });
    
    await sendTrialReminders();
    Sentry.captureCheckIn("trial-reminders", { status: "ok" });
    
    await reconcileWorkspaceSeats();
    Sentry.captureCheckIn("seat-reconciliation", { status: "ok" });
  }, 5 * 60 * 1000);
}

```

Sentry auto-provisions monitors on the first check-in and triggers "cron-missed" alerts if a job fails to report within its expected window. Configuration details and alert thresholds reside in [`sentry/README.md`](https://github.com/usekaneo/kaneo/blob/main/sentry/README.md) and [`sentry/alerts.json`](https://github.com/usekaneo/kaneo/blob/main/sentry/alerts.json).

## Testing Scheduled Job Logic

The repository includes comprehensive tests for timing-sensitive logic. Unit tests in [`tests/api/scheduler/reminder-timing.test.ts`](https://github.com/usekaneo/kaneo/blob/main/tests/api/scheduler/reminder-timing.test.ts) validate delivery windows without requiring full integration suites.

```typescript
import { isReminderDue } from "../../../apps/api/src/scheduler/reminder-timing";

it("accepts scheduler runs within the ten-minute delivery window", () => {
  const now = new Date("2024-01-01T10:05:00Z");
  const reminder = { dueDate: new Date("2024-01-01T10:00:00Z") };
  expect(isReminderDue(reminder, now)).toBe(true);
});

```

## Summary

- Kaneo implements scheduled jobs inside the API service at `apps/api/src/scheduler`, eliminating the need for separate worker processes.
- **Distributed locking** via `withJobLease` in [`leader-lock.ts`](https://github.com/usekaneo/kaneo/blob/main/leader-lock.ts) ensures exactly-once execution across multiple API instances using PostgreSQL or Redis.
- Four primary jobs handle due-date reminders, trial notifications, seat reconciliation, and webhook deliveries on varying intervals from five minutes to daily.
- **Sentry integration** provides automatic monitoring and alerting through `captureCheckIn` calls with stable job slugs.
- The architecture supports horizontal scaling without job duplication or external coordination services.

## Frequently Asked Questions

### How does Kaneo prevent duplicate job execution across multiple API instances?

Kaneo uses a distributed lock mechanism called `withJobLease` implemented in [`apps/api/src/scheduler/leader-lock.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/scheduler/leader-lock.ts). Before executing any scheduled task, the scheduler attempts to write a lease record to the `scheduler_leases` database table. Only the instance that successfully acquires the lease executes the job; others skip the cycle. This guarantees exactly-once execution even when scaling the API service to multiple pods or servers.

### What types of scheduled jobs does Kaneo run?

The platform runs four recurring background jobs: **due-date reminders** (every 5 minutes), **trial reminders** (hourly), **seat reconciliation** (daily), and **project webhook reminders**. Each job has a dedicated implementation file in `apps/api/src/scheduler/` and handles specific domain concerns like notifying users of impending deadlines or synchronizing billing state with subscription limits.

### How does Kaneo monitor scheduled job failures?

Kaneo integrates with Sentry Cron Monitoring through the `captureCheckIn` API. After each successful job run, the scheduler reports a check-in using stable slugs defined in [`apps/api/src/scheduler/index.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/scheduler/index.ts). Sentry automatically creates monitors on first execution and triggers alerts if check-ins are missed or if jobs report error statuses. Alert configurations are stored in [`sentry/alerts.json`](https://github.com/usekaneo/kaneo/blob/main/sentry/alerts.json) with documentation in [`sentry/README.md`](https://github.com/usekaneo/kaneo/blob/main/sentry/README.md).

### Where are the scheduled job intervals defined in Kaneo?

Job intervals are defined directly in [`apps/api/src/scheduler/index.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/scheduler/index.ts) where the `startScheduler` function registers `setInterval` calls. Due-date reminders execute every 5 minutes, trial reminders run hourly, and seat reconciliation triggers daily. These intervals are hardcoded in the scheduler bootstrap rather than external configuration files, ensuring version-controlled consistency across deployments.