Learn-skills.dev convex-cron
Scheduled functions and cron jobs in Convex. Use when setting up recurring tasks, cleanup jobs, data syncing, scheduled notifications, or any background automation that runs on a schedule.
install
source · Clone the upstream repo
git clone https://github.com/NeverSight/learn-skills.dev
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/NeverSight/learn-skills.dev "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/skills-md/aaronvanston/skills-convex/convex-cron" ~/.claude/skills/neversight-learn-skills-dev-convex-cron && rm -rf "$T"
manifest:
data/skills-md/aaronvanston/skills-convex/convex-cron/SKILL.mdsource content
Convex Cron Jobs
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
// Every 5 minutes crons.interval("sync data", { minutes: 5 }, internal.sync.fetchData, {}); // Every 2 hours crons.interval("cleanup temp", { hours: 2 }, internal.files.cleanup, {}); // Every 30 seconds (minimum) crons.interval("health check", { seconds: 30 }, internal.monitoring.check, {});
Cron Expression Scheduling
┌───────────── minute (0-59) │ ┌───────────── hour (0-23) │ │ ┌───────────── day of month (1-31) │ │ │ ┌───────────── month (1-12) │ │ │ │ ┌───────────── day of week (0-6, Sunday=0) * * * * *
// Every day at 9 AM UTC crons.cron("morning digest", "0 9 * * *", internal.notifications.morning, {}); // Every Monday at 8 AM UTC crons.cron("weekly summary", "0 8 * * 1", internal.reports.weekly, {}); // First day of month at midnight crons.cron("monthly billing", "0 0 1 * *", internal.billing.process, {}); // Every 15 minutes crons.cron("frequent sync", "*/15 * * * *", internal.sync.run, {});
Internal Functions for Crons
Always use internal functions:
// convex/tasks.ts import { internalMutation } from "./_generated/server"; import { v } from "convex/values"; export const cleanupExpiredSessions = internalMutation({ args: {}, returns: v.number(), handler: async (ctx) => { const oneHourAgo = Date.now() - 60 * 60 * 1000; const expired = await ctx.db .query("sessions") .withIndex("by_lastActive") .filter((q) => q.lt(q.field("lastActive"), oneHourAgo)) .collect(); for (const session of expired) { await ctx.db.delete(session._id); } return expired.length; }, });
Crons with Arguments
crons.interval( "cleanup temp files", { hours: 1 }, internal.cleanup.byType, { fileType: "temp", maxAge: 3600000 } ); crons.interval( "cleanup cache files", { hours: 24 }, internal.cleanup.byType, { fileType: "cache", maxAge: 86400000 } );
Batching Large Datasets
Handle large datasets to avoid timeouts:
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 more items if (!result.isDone) { await ctx.scheduler.runAfter(0, internal.tasks.processBatch, { cursor: result.continueCursor, }); } return null; }, });
External API Calls
Use actions for external APIs:
// convex/sync.ts "use node"; import { internalAction, internalMutation } from "./_generated/server"; import { internal } from "./_generated/api"; import { v } from "convex/values"; export const syncExternalData = internalAction({ args: {}, returns: v.null(), handler: async (ctx) => { const response = await fetch("https://api.example.com/data", { headers: { Authorization: `Bearer ${process.env.API_KEY}` }, }); const data = await response.json(); await ctx.runMutation(internal.sync.storeData, { data, syncedAt: Date.now() }); return null; }, }); // In crons.ts crons.interval("sync external", { minutes: 15 }, internal.sync.syncExternalData, {});
Logging and Monitoring
export const cleanupWithLogging = internalMutation({ args: {}, returns: v.null(), handler: async (ctx) => { const startTime = Date.now(); let processedCount = 0; try { const items = await ctx.db.query("items") .filter((q) => q.lt(q.field("expiresAt"), Date.now())) .collect(); for (const item of items) { await ctx.db.delete(item._id); processedCount++; } await ctx.db.insert("cronLogs", { jobName: "cleanup", duration: Date.now() - startTime, processedCount, status: "success", }); } catch (error) { await ctx.db.insert("cronLogs", { jobName: "cleanup", duration: Date.now() - startTime, processedCount, status: "failed", error: String(error), }); throw error; } return null; }, });
Common Pitfalls
- Using public functions - Always use internal functions
- Forgetting timezone - All cron expressions use UTC
- Long-running mutations - Break into batches
- Missing error handling - Log failures for debugging
References
- Cron Jobs: https://docs.convex.dev/scheduling/cron-jobs
- Scheduling: https://docs.convex.dev/scheduling