Docs / Cron and scheduled jobs
Cron and scheduled jobs
Register UTC schedules, protect internal calls and verify repeated execution.
Deploy the job endpoint first
JoripSpace cron calls a GET or POST endpoint on the deployed Worker. Adding a scheduled() handler or a local timer does not register a project schedule. The sample deletes up to 100 expired app sessions per run.
import { Hono } from 'hono';
import type { AppEnv } from '../types';
export const jobs = new Hono<AppEnv>();
jobs.post('/cleanup-sessions', async (c) => {
// The scheduler sends this URL through a private Dispatch call.
// Never authorize a request using only a public header such as X-JoripSpace-Cron-ID.
if (new URL(c.req.url).hostname !== 'joripspace-cron.internal')
return c.json({ error: 'scheduler_required' }, 403);
const result = await c.env.DB.prepare(
'DELETE FROM app_sessions WHERE session_hash IN (' +
'SELECT session_hash FROM app_sessions WHERE expires_at<=? ORDER BY expires_at LIMIT 100)'
).bind(Date.now()).run();
// The job is idempotent; a later schedule processes any remaining items.
return c.json({ ok: true, deleted: result.meta.changes });
});import { jobs } from './routes/jobs';
app.route('/jobs', jobs);Use the app_sessions table and expiry index from the Hono example. Verify the private internal URL and require platform authentication; a public POST with forged cron headers must return 403.
Check for duplicates and register in UTC
joripspace crons --project PROJECT
joripspace cron create --project PROJECT --schedule "*/5 * * * *" --path /jobs/cleanup-sessions --method POST --name "Clean expired sessions"The five fields are minute, hour, day, month and weekday in UTC. Do not create another schedule when one already serves the same purpose.
| Intent | UTC expression |
|---|---|
| Every five minutes | */5 * * * * |
| Daily at 09:00 KST | 0 0 * * * |
| Daily at midnight KST | 0 15 * * * · 15:00 UTC on the previous day |
Account for daylight-saving changes in other regions.
Run immediately and verify status
joripspace cron run --project PROJECT --cron-id CRON_ID
joripspace crons --project PROJECT
joripspace events --project PROJECT --event-type errorUse the CRON_ID from the create response to run the job immediately. Check last_status for the result and next_run_at for the next scheduled run, then confirm that expired sessions were removed from the database.
joripspace cron delete --project PROJECT --cron-id CRON_ID --yesDesign for retries, delay and failure
Network failures and retries can cause a job to run more than once. Design it so repeating a request does not duplicate its effects. Use conditional DELETE for cleanup, unique job IDs and database state transitions for billing or delivery, and an outbox with a next_attempt_at index for external API work.
Process bounded batches with cursors instead of reading the entire dataset. Monitor the last successful run and delay of important jobs. Record job progress so delayed or failed work can be retried safely.