Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/rjdellecese/confect/llms.txt

Use this file to discover all available pages before exploring further.

Cron jobs let you run functions on a recurring schedule - hourly, daily, weekly, or with custom intervals. Perfect for background tasks, cleanup jobs, and periodic processing.

Overview

Interval-Based

Run functions at fixed intervals

Cron Expressions

Use standard cron syntax for complex schedules

Reliable

Guaranteed execution with automatic retries

Convex API

Uses vanilla Convex cron API
Confect uses the vanilla Convex cron API. Define your crons in confect/crons.ts and the Confect CLI will re-export them into convex/crons.ts.

Defining Crons

Create cron jobs in confect/crons.ts:
confect/crons.ts
import { cronJobs } from "convex/server";
import { internal } from "../convex/_generated/api";

const crons = cronJobs();

// Run every hour
crons.interval(
  "clear stale sessions",
  { hours: 1 },
  internal.sessions.clearStale
);

// Run every day at midnight
crons.daily(
  "generate daily reports",
  { hourUTC: 0, minuteUTC: 0 },
  internal.reports.generateDaily
);

// Run every Monday at 9 AM
crons.weekly(
  "send weekly digest",
  { weekday: "monday", hourUTC: 9, minuteUTC: 0 },
  internal.emails.sendWeeklyDigest
);

// Run on the 1st of every month
crons.monthly(
  "process monthly billing",
  { day: 1, hourUTC: 0, minuteUTC: 0 },
  internal.billing.processMonthly
);

export default crons;
Cron jobs must reference internal functions. They cannot call public functions.

Interval-Based Crons

Run functions at fixed intervals:
// Every 5 minutes
crons.interval(
  "check health",
  { minutes: 5 },
  internal.monitoring.checkHealth
);

// Every 30 seconds
crons.interval(
  "process queue",
  { seconds: 30 },
  internal.queue.process
);

// Every 6 hours
crons.interval(
  "sync external data",
  { hours: 6 },
  internal.sync.syncData
);

// Every 2 days
crons.interval(
  "cleanup old data",
  { days: 2 },
  internal.cleanup.removeOld
);

Daily Crons

Run once per day at a specific time:
// 3 AM UTC
crons.daily(
  "backup database",
  { hourUTC: 3, minuteUTC: 0 },
  internal.backup.createBackup
);

// Noon UTC
crons.daily(
  "send daily summary",
  { hourUTC: 12, minuteUTC: 0 },
  internal.notifications.sendDailySummary
);

// 11:30 PM UTC
crons.daily(
  "process analytics",
  { hourUTC: 23, minuteUTC: 30 },
  internal.analytics.process
);
All cron times are in UTC. Convert from your local timezone when scheduling.

Weekly Crons

Run once per week on a specific day:
// Monday at 9 AM
crons.weekly(
  "start week",
  { weekday: "monday", hourUTC: 9, minuteUTC: 0 },
  internal.tasks.startWeek
);

// Friday at 5 PM
crons.weekly(
  "end week",
  { weekday: "friday", hourUTC: 17, minuteUTC: 0 },
  internal.tasks.endWeek
);

// Sunday at midnight
crons.weekly(
  "weekly cleanup",
  { weekday: "sunday", hourUTC: 0, minuteUTC: 0 },
  internal.cleanup.weekly
);
Available weekdays: "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"

Monthly Crons

Run once per month on a specific day:
// 1st of the month at midnight
crons.monthly(
  "monthly billing",
  { day: 1, hourUTC: 0, minuteUTC: 0 },
  internal.billing.processMonthly
);

// 15th of the month at noon
crons.monthly(
  "mid-month report",
  { day: 15, hourUTC: 12, minuteUTC: 0 },
  internal.reports.generateMidMonth
);

// Last day of the month
crons.monthly(
  "end of month",
  { day: 31, hourUTC: 23, minuteUTC: 59 },
  internal.tasks.endOfMonth
);
For day 31, the cron will run on the last day of months with fewer than 31 days.

Cron Expressions

Use standard cron syntax for complex schedules:
// Every 15 minutes
crons.cron(
  "frequent check",
  "*/15 * * * *",
  internal.monitoring.check
);

// Weekdays at 9 AM
crons.cron(
  "weekday morning",
  "0 9 * * 1-5",
  internal.tasks.weekdayMorning
);

// First Monday of every month at 10 AM
crons.cron(
  "first monday",
  "0 10 1-7 * 1",
  internal.reports.firstMonday
);
Cron expression format: minute hour day month weekday
  • * - any value
  • */n - every n units
  • n-m - range from n to m
  • n,m - values n and m

Implementing Cron Handlers

Implement the functions called by crons:
confect/sessions.ts
import { FunctionImpl, DatabaseReader, DatabaseWriter } from "@confect/server";
import { Effect } from "effect";

const clearStale = FunctionImpl.make(
  api,
  "sessions",
  "clearStale",
  () =>
    Effect.gen(function* () {
      const db = yield* DatabaseReader<typeof databaseSchema>();
      const dbWrite = yield* DatabaseWriter<typeof databaseSchema>();
      
      const cutoff = Date.now() - 24 * 60 * 60 * 1000; // 24 hours ago
      
      const staleSessions = yield* db
        .table("sessions")
        .filter((q) => q.lt(q.field("lastAccessedAt"), cutoff))
        .collect();
      
      // Delete stale sessions
      yield* Effect.forEach(
        staleSessions,
        (session) => dbWrite.table("sessions").delete(session._id),
        { concurrency: 10 }
      );
      
      return { deleted: staleSessions.length };
    }).pipe(Effect.orDie)
);

Common Use Cases

Daily Cleanup

// Remove old records every day at 2 AM
crons.daily(
  "cleanup old records",
  { hourUTC: 2, minuteUTC: 0 },
  internal.cleanup.removeOld
);

const removeOld = FunctionImpl.make(
  api,
  "cleanup",
  "removeOld",
  () =>
    Effect.gen(function* () {
      const db = yield* DatabaseReader<typeof databaseSchema>();
      const dbWrite = yield* DatabaseWriter<typeof databaseSchema>();
      
      const cutoff = Date.now() - 30 * 24 * 60 * 60 * 1000; // 30 days
      
      const old = yield* db
        .table("logs")
        .filter((q) => q.lt(q.field("createdAt"), cutoff))
        .collect();
      
      yield* Effect.forEach(
        old,
        (log) => dbWrite.table("logs").delete(log._id)
      );
    }).pipe(Effect.orDie)
);

Hourly Sync

// Sync with external API every hour
crons.interval(
  "sync external data",
  { hours: 1 },
  internal.sync.syncData
);

const syncData = FunctionImpl.make(
  api,
  "sync",
  "syncData",
  () =>
    Effect.gen(function* () {
      const db = yield* DatabaseWriter<typeof databaseSchema>();
      
      // Fetch from external API
      const response = yield* Effect.tryPromise(() =>
        fetch("https://api.example.com/data")
      );
      
      const data = yield* Effect.tryPromise(() => response.json());
      
      // Update local data
      for (const item of data) {
        yield* db.table("external_data").insert(item);
      }
    }).pipe(Effect.orDie)
);

Weekly Reports

// Send weekly report every Monday at 9 AM
crons.weekly(
  "weekly report",
  { weekday: "monday", hourUTC: 9, minuteUTC: 0 },
  internal.reports.sendWeekly
);

const sendWeekly = FunctionImpl.make(
  api,
  "reports",
  "sendWeekly",
  () =>
    Effect.gen(function* () {
      const db = yield* DatabaseReader<typeof databaseSchema>();
      
      // Get last week's data
      const weekAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
      
      const activity = yield* db
        .table("activities")
        .filter((q) => q.gt(q.field("createdAt"), weekAgo))
        .collect();
      
      // Generate and send report
      const report = generateReport(activity);
      yield* sendEmail("admin@example.com", report);
    }).pipe(Effect.orDie)
);

Monthly Billing

// Process billing on the 1st of each month
crons.monthly(
  "monthly billing",
  { day: 1, hourUTC: 0, minuteUTC: 0 },
  internal.billing.processMonthly
);

const processMonthly = FunctionImpl.make(
  api,
  "billing",
  "processMonthly",
  () =>
    Effect.gen(function* () {
      const db = yield* DatabaseReader<typeof databaseSchema>();
      const dbWrite = yield* DatabaseWriter<typeof databaseSchema>();
      
      // Get active subscriptions
      const subscriptions = yield* db
        .table("subscriptions")
        .filter((q) => q.eq(q.field("status"), "active"))
        .collect();
      
      // Process each subscription
      yield* Effect.forEach(
        subscriptions,
        (sub) =>
          Effect.gen(function* () {
            // Charge customer
            yield* chargeCustomer(sub.customerId, sub.amount);
            
            // Record payment
            yield* dbWrite.table("payments").insert({
              subscriptionId: sub._id,
              amount: sub.amount,
              date: Date.now(),
            });
          }),
        { concurrency: 5 }
      );
    }).pipe(Effect.orDie)
);

Error Handling

Handle errors in cron jobs:
const resilientJob = FunctionImpl.make(
  api,
  "jobs",
  "process",
  () =>
    Effect.gen(function* () {
      const db = yield* DatabaseWriter<typeof databaseSchema>();
      
      // Process with error handling
      const result = yield* processData().pipe(
        Effect.catchAll((error) =>
          Effect.gen(function* () {
            // Log error
            yield* db.table("errors").insert({
              job: "resilientJob",
              error: String(error),
              timestamp: Date.now(),
            });
            
            // Don't fail the entire job
            return null;
          })
        )
      );
    }).pipe(Effect.orDie)
);
If a cron job fails, Convex will automatically retry it. Implement proper error handling to avoid infinite retries.

Monitoring Cron Jobs

View cron execution history in the _scheduled_functions table:
const listCronHistory = Effect.gen(function* () {
  const db = yield* DatabaseReader<typeof databaseSchema>();
  
  const history = yield* db
    .table("_scheduled_functions")
    .filter((q) => q.neq(q.field("completedTime"), undefined))
    .order("desc")
    .take(100)
    .collect();
  
  return history.map((job) => ({
    name: job.name,
    state: job.state.kind,
    scheduledTime: new Date(job.scheduledTime),
    completedTime: job.completedTime ? new Date(job.completedTime) : null,
  }));
});

Best Practices

1

Keep Jobs Short

Cron jobs should complete within a few minutes. Break long tasks into smaller chunks.
2

Handle Failures

Implement error handling to prevent failed jobs from blocking subsequent runs.
3

Use Internal Functions

Always use internal functions for cron handlers - never public functions.
4

Monitor Execution

Check the _scheduled_functions table regularly to ensure crons are running successfully.
Cron jobs run in UTC time. Use a timezone converter when scheduling jobs for specific local times.

Next Steps

Scheduling

Schedule one-time functions

Functions

Learn about mutations and actions

Database

Access data in cron jobs

Convex Crons

Convex cron documentation

Build docs developers (and LLMs) love