onvibe.run

← All docs

Cron Jobs

Run code on a schedule. The platform calls a path of your app on a cron schedule; your app handles it in its normal handler. Typical uses: send reminder emails, clean up stale rows, refresh a cache, send a daily digest.

There is no separate "cron handler" — a cron run is just an HTTP request to your app that carries an auth token and a marker header. Verify it with isCronRequest(req).

Defining jobs: onvibe.json

Jobs are declared, never created imperatively. Add an onvibe.json file at the project root; it is reconciled on every deploy: jobs are created/updated, and jobs removed from the file are deleted.

{
  "crons": [
    {
      "name": "reminders",
      "schedule": "0 9 * * *",
      "path": "/cron/reminders",
      "timezone": "Europe/Madrid"
    }
  ]
}

Changing jobs: download → edit → deploy

onvibe.json holds the complete set of jobs, so never write it from memory:

get_config({ project_id })        // the live onvibe.json + its `version` lock
                                  // edit the `crons` array, KEEP `version` as-is
deploy({ project_id, files: [{ path: "onvibe.json", content: edited }] })

The deploy is rejected (CONFIG_OUT_OF_DATE) if the config moved since you downloaded it — call get_config again, re-apply your edit and redeploy. See onvibe://docs/config.

Two runtime tools complete the picture (they don't define jobs):

run_cron({ project_id, name })    // trigger once now, for testing
get_project({ project_id })       // schedule, next_run and last_run of each job

Handling the trigger in your app

Gate the cron logic with isCronRequest(req) so public traffic can't trigger it.

import { isCronRequest } from "./.onvibe/helpers.ts";

export default async function handler(req: Request): Promise<Response> {
  const url = new URL(req.url);
  if (url.pathname === "/cron/reminders" && isCronRequest(req)) {
    await sendReminders();           // scan DB, send emails, etc.
    return new Response("ok");
  }
  // ...rest of the app
}

isCronRequest(req) returns true only when the request carries the X-Onvibe-Cron header AND the correct APP_TOKEN bearer (set by the platform). cronName(req) returns the job name if you serve several jobs from one path.

Limits & semantics

Example: daily expiry reminders

{ "crons": [{ "name": "reminders", "schedule": "0 9 * * *", "path": "/cron/reminders", "timezone": "Europe/Madrid" }] }
import { isCronRequest } from "./.onvibe/helpers.ts";

async function handler(req: Request): Promise<Response> {
  const url = new URL(req.url);
  if (url.pathname === "/cron/reminders" && isCronRequest(req)) {
    const { rows } = await pool.query(
      "SELECT email, name FROM items WHERE expires_on = CURRENT_DATE + 3",
    );
    for (const r of rows) {
      // send an email to r.email (see onvibe://docs/email when available)
    }
    return new Response(`sent ${rows.length}`);
  }
  return new Response("not found", { status: 404 });
}
export default handler;

Notes

Read this page as Markdown (best for LLMs) · plain text
onvibe.run · home · all docs