Convexskills convex-cron-jobs
Scheduled function patterns for background tasks including interval scheduling, cron expressions, job monitoring, retry strategies, and best practices for long-running tasks
install
source · Clone the upstream repo
git clone https://github.com/waynesutton/convexskills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/waynesutton/convexskills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/convex-cron-jobs" ~/.claude/skills/waynesutton-convexskills-convex-cron-jobs && rm -rf "$T"
manifest:
skills/convex-cron-jobs/SKILL.mdsource content
Convex Cron Jobs
Schedule recurring functions for background tasks, cleanup jobs, data syncing, and automated workflows in Convex applications.
Documentation Sources
Before implementing, do not assume; fetch the latest documentation:
- Primary: https://docs.convex.dev/scheduling/cron-jobs
- Scheduling Overview: https://docs.convex.dev/scheduling
- Scheduled Functions: https://docs.convex.dev/scheduling/scheduled-functions
- For broader context: https://docs.convex.dev/llms.txt
Instructions
Cron Jobs Overview
Convex cron jobs allow you to schedule functions to run at regular intervals or specific times. Key features:
- Run functions on a fixed schedule
- Support for interval-based and cron expression scheduling
- Automatic retries on failure
- Monitoring via the Convex dashboard
Basic Cron Setup
// convex/crons.ts import { cronJobs } from "convex/server"; import { internal } from "./_generated/api"; const crons = cronJobs(); // Run every hour crons.interval( "cleanup expired sessions", { hours: 1 }, internal.tasks.cleanupExpiredSessions, {} ); // Run every day at midnight UTC crons.cron( "daily report", "0 0 * * *", internal.reports.generateDailyReport, {} ); export default crons;
Interval-Based Scheduling
Use
crons.interval for simple recurring tasks:
// convex/crons.ts import { cronJobs } from "convex/server"; import { internal } from "./_generated/api"; const crons = cronJobs(); // Every 5 minutes crons.interval( "sync external data", { minutes: 5 }, internal.sync.fetchExternalData, {} ); // Every 2 hours crons.interval( "cleanup temp files", { hours: 2 }, internal.files.cleanupTempFiles, {} ); // Every 30 seconds (minimum interval) crons.interval( "health check", { seconds: 30 }, internal.monitoring.healthCheck, {} ); export default crons;
Cron Expression Scheduling
Use
crons.cron for precise scheduling with cron expressions:
// convex/crons.ts import { cronJobs } from "convex/server"; import { internal } from "./_generated/api"; const crons = cronJobs(); // Every day at 9 AM UTC crons.cron( "morning notifications", "0 9 * * *", internal.notifications.sendMorningDigest, {} ); // Every Monday at 8 AM UTC crons.cron( "weekly summary", "0 8 * * 1", internal.reports.generateWeeklySummary, {} ); // First day of every month at midnight crons.cron( "monthly billing", "0 0 1 * *", internal.billing.processMonthlyBilling, {} ); // Every 15 minutes crons.cron( "frequent sync", "*/15 * * * *", internal.sync.syncData, {} ); export default crons;
Cron Expression Reference
┌───────────── minute (0-59) │ ┌───────────── hour (0-23) │ │ ┌───────────── day of month (1-31) │ │ │ ┌───────────── month (1-12) │ │ │ │ ┌───────────── day of week (0-6, Sunday=0) │ │ │ │ │ * * * * *
Common patterns:
- Every minute* * * * *
- Every hour0 * * * *
- Every day at midnight0 0 * * *
- Every Sunday at midnight0 0 * * 0
- First day of every month0 0 1 * *
- Every 5 minutes*/5 * * * *
- Every hour from 9 AM to 5 PM, Monday through Friday0 9-17 * * 1-5
Internal Functions for Crons
Cron jobs should call internal functions for security:
// convex/tasks.ts import { internalMutation, internalQuery } from "./_generated/server"; import { v } from "convex/values"; // Cleanup expired sessions export const cleanupExpiredSessions = internalMutation({ args: {}, returns: v.number(), handler: async (ctx) => { const oneHourAgo = Date.now() - 60 * 60 * 1000; const expiredSessions = await ctx.db .query("sessions") .withIndex("by_lastActive") .filter((q) => q.lt(q.field("lastActive"), oneHourAgo)) .collect(); for (const session of expiredSessions) { await ctx.db.delete(session._id); } return expiredSessions.length; }, }); // Process pending tasks export const processPendingTasks = internalMutation({ args: {}, returns: v.null(), handler: async (ctx) => { const pendingTasks = await ctx.db .query("tasks") .withIndex("by_status", (q) => q.eq("status", "pending")) .take(100); for (const task of pendingTasks) { await ctx.db.patch(task._id, { status: "processing", startedAt: Date.now(), }); // Schedule the actual processing await ctx.scheduler.runAfter(0, internal.tasks.processTask, { taskId: task._id, }); } return null; }, });
Cron Jobs with Arguments
Pass static arguments to cron jobs:
// convex/crons.ts import { cronJobs } from "convex/server"; import { internal } from "./_generated/api"; const crons = cronJobs(); // Different cleanup intervals for different types crons.interval( "cleanup temp files", { hours: 1 }, internal.cleanup.cleanupByType, { fileType: "temp", maxAge: 3600000 } ); crons.interval( "cleanup cache files", { hours: 24 }, internal.cleanup.cleanupByType, { fileType: "cache", maxAge: 86400000 } ); export default crons;
// convex/cleanup.ts import { internalMutation } from "./_generated/server"; import { v } from "convex/values"; export const cleanupByType = internalMutation({ args: { fileType: v.string(), maxAge: v.number(), }, returns: v.number(), handler: async (ctx, args) => { const cutoff = Date.now() - args.maxAge; const oldFiles = await ctx.db .query("files") .withIndex("by_type_and_created", (q) => q.eq("type", args.fileType).lt("createdAt", cutoff) ) .collect(); for (const file of oldFiles) { await ctx.storage.delete(file.storageId); await ctx.db.delete(file._id); } return oldFiles.length; }, });
Monitoring and Logging
Add logging to track cron job execution:
// convex/tasks.ts import { internalMutation } from "./_generated/server"; import { v } from "convex/values"; export const cleanupWithLogging = internalMutation({ args: {}, returns: v.null(), handler: async (ctx) => { const startTime = Date.now(); let processedCount = 0; let errorCount = 0; try { const expiredItems = await ctx.db .query("items") .withIndex("by_expiresAt") .filter((q) => q.lt(q.field("expiresAt"), Date.now())) .collect(); for (const item of expiredItems) { try { await ctx.db.delete(item._id); processedCount++; } catch (error) { errorCount++; console.error(`Failed to delete item ${item._id}:`, error); } } // Log job completion await ctx.db.insert("cronLogs", { jobName: "cleanup", startTime, endTime: Date.now(), duration: Date.now() - startTime, processedCount, errorCount, status: errorCount === 0 ? "success" : "partial", }); } catch (error) { // Log job failure await ctx.db.insert("cronLogs", { jobName: "cleanup", startTime, endTime: Date.now(), duration: Date.now() - startTime, processedCount, errorCount, status: "failed", error: String(error), }); throw error; } return null; }, });
Batching for Large Datasets
Handle large datasets in batches to avoid timeouts:
// convex/tasks.ts import { internalMutation } from "./_generated/server"; import { internal } from "./_generated/api"; import { v } from "convex/values"; const BATCH_SIZE = 100; export const processBatch = internalMutation({ args: { cursor: v.optional(v.string()), }, returns: v.null(), handler: async (ctx, args) => { const result = await ctx.db .query("items") .withIndex("by_status", (q) => q.eq("status", "pending")) .paginate({ numItems: BATCH_SIZE, cursor: args.cursor ?? null }); for (const item of result.page) { await ctx.db.patch(item._id, { status: "processed", processedAt: Date.now(), }); } // Schedule next batch if there are more items if (!result.isDone) { await ctx.scheduler.runAfter(0, internal.tasks.processBatch, { cursor: result.continueCursor, }); } return null; }, });
External API Calls in Crons
Use actions for external API calls:
// convex/sync.ts "use node"; import { internalAction } from "./_generated/server"; import { internal } from "./_generated/api"; import { v } from "convex/values"; export const syncExternalData = internalAction({ args: {}, returns: v.null(), handler: async (ctx) => { // Fetch from external API const response = await fetch("https://api.example.com/data", { headers: { Authorization: `Bearer ${process.env.API_KEY}`, }, }); if (!response.ok) { throw new Error(`API request failed: ${response.status}`); } const data = await response.json(); // Store the data using a mutation await ctx.runMutation(internal.sync.storeExternalData, { data, syncedAt: Date.now(), }); return null; }, }); export const storeExternalData = internalMutation({ args: { data: v.any(), syncedAt: v.number(), }, returns: v.null(), handler: async (ctx, args) => { await ctx.db.insert("externalData", { data: args.data, syncedAt: args.syncedAt, }); return null; }, });
// convex/crons.ts import { cronJobs } from "convex/server"; import { internal } from "./_generated/api"; const crons = cronJobs(); crons.interval( "sync external data", { minutes: 15 }, internal.sync.syncExternalData, {} ); export default crons;
Examples
Schema for Cron Job Logging
// convex/schema.ts import { defineSchema, defineTable } from "convex/server"; import { v } from "convex/values"; export default defineSchema({ cronLogs: defineTable({ jobName: v.string(), startTime: v.number(), endTime: v.number(), duration: v.number(), processedCount: v.number(), errorCount: v.number(), status: v.union( v.literal("success"), v.literal("partial"), v.literal("failed") ), error: v.optional(v.string()), }) .index("by_job", ["jobName"]) .index("by_status", ["status"]) .index("by_startTime", ["startTime"]), sessions: defineTable({ userId: v.id("users"), token: v.string(), lastActive: v.number(), expiresAt: v.number(), }) .index("by_user", ["userId"]) .index("by_lastActive", ["lastActive"]) .index("by_expiresAt", ["expiresAt"]), tasks: defineTable({ type: v.string(), status: v.union( v.literal("pending"), v.literal("processing"), v.literal("completed"), v.literal("failed") ), data: v.any(), createdAt: v.number(), startedAt: v.optional(v.number()), completedAt: v.optional(v.number()), }) .index("by_status", ["status"]) .index("by_type_and_status", ["type", "status"]), });
Complete Cron Configuration Example
// convex/crons.ts import { cronJobs } from "convex/server"; import { internal } from "./_generated/api"; const crons = cronJobs(); // Cleanup jobs crons.interval( "cleanup expired sessions", { hours: 1 }, internal.cleanup.expiredSessions, {} ); crons.interval( "cleanup old logs", { hours: 24 }, internal.cleanup.oldLogs, { maxAgeDays: 30 } ); // Sync jobs crons.interval( "sync user data", { minutes: 15 }, internal.sync.userData, {} ); // Report jobs crons.cron( "daily analytics", "0 1 * * *", internal.reports.dailyAnalytics, {} ); crons.cron( "weekly summary", "0 9 * * 1", internal.reports.weeklySummary, {} ); // Health checks crons.interval( "service health check", { minutes: 5 }, internal.monitoring.healthCheck, {} ); export default crons;
Best Practices
- Never run
unless explicitly instructednpx convex deploy - Never run any git commands unless explicitly instructed
- Only use
orcrons.interval
methods, not deprecated helperscrons.cron - Always call internal functions from cron jobs for security
- Import
frominternal
even for functions in the same file_generated/api - Add logging and monitoring for production cron jobs
- Use batching for operations that process large datasets
- Handle errors gracefully to prevent job failures
- Use meaningful job names for dashboard visibility
- Consider timezone when using cron expressions (Convex uses UTC)
Common Pitfalls
- Using public functions - Cron jobs should call internal functions only
- Long-running mutations - Break large operations into batches
- Missing error handling - Unhandled errors will fail the entire job
- Forgetting timezone - All cron expressions use UTC
- Using deprecated helpers - Avoid
,crons.hourly
, etc.crons.daily - Not logging execution - Makes debugging production issues difficult
References
- Convex Documentation: https://docs.convex.dev/
- Convex LLMs.txt: https://docs.convex.dev/llms.txt
- Cron Jobs: https://docs.convex.dev/scheduling/cron-jobs
- Scheduling Overview: https://docs.convex.dev/scheduling
- Scheduled Functions: https://docs.convex.dev/scheduling/scheduled-functions