Work that happens while you sleep.

The nightly digest, the reminder before the booking, the slow job nobody should have to watch a spinner for. Declared in your project, run by the platform, with no queue service and no cron server of your own.

— what your AI actually does

You ask once. It runs forever.

A schedule is two lines of config pointed at one handler. Your agent writes both, deploys, and the job is live. Nothing is clicked into a dashboard and nothing lives outside your project.

you, to your AIEmail my subscribers a digest every morning
hatchable.toml
# two keys, and only these two.
# the parser ignores anything else.
[[cron]]
path     = "/api/jobs/daily-digest"
schedule = "0 13 * * *"  # 13:00 UTC
api/jobs/daily-digest.js
import { db, email } from 'hatchable';

// 404 to the public internet. only the
// scheduler can reach this route.
export const access = 'scheduler';

export default async function (req, res) {
  const isCron =
    req.headers['x-hatchable-trigger'] === 'cron';

  const { rows } = await db.query(
    `SELECT email FROM subscribers
       WHERE digest_enabled = true
         AND (last_digest_at IS NULL
              OR last_digest_at < now() - interval '23 hours')
       ORDER BY id
       LIMIT 8`
  );

  for (const sub of rows) {
    await email.send({
      to: sub.email,
      subject: 'Your daily digest',
      html: renderDigest(sub),
    });
    await db.query(
      'UPDATE subscribers SET last_digest_at = now() WHERE email = $1',
      [sub.email]
    );
  }

  res.json({ sent: rows.length, cron: isCron });
}
the batch is small on purpose. each fire gets about 65 seconds, and the 23-hour clause means a repeat run quietly selects nobody.
— what you get

The background half of a real app.

Most apps are only half request and response. The other half runs on a clock, or after the visitor has already been given their answer. Both halves are here, in every project.

Cron

Schedules that live in your project

A recurring job is a [[cron]] block in your config file: one route, one schedule, standard five-field cron. It ships with the deploy, and the schedule is right there in the file list where anyone can read it.

Change the hour, redeploy, done. No console to hunt through.

Deferred

Answer now, work later

A handler can hand a slow job off and return immediately, so the person on the screen gets a response in milliseconds instead of watching a progress bar. The work runs out of band, seconds later, with a much longer budget.

Upload finishes instantly. The transcoding happens behind it.

Access

Nobody can call it but the platform

A job handler declares itself scheduler-only and then returns 404 to the entire internet, signed in or not. There is no secret URL to leak and no token to rotate, because there is no door.

The nightly billing job is not a URL anyone can find.

UTC

One clock, for everybody

Every schedule is UTC. No project time zone to set, no daylight saving to reason about, no argument about whose morning it is. Repeat firings are spread within the hour so that thousands of jobs on the same schedule do not all land on the same second.

Think of a schedule as the hour it runs in, not the second.

Batches

Big work, one bite at a time

Each fire has about 65 seconds, which is short on purpose: it keeps a stuck job from pinning anything. Long work is written to take a fixed batch, save its progress, and queue itself again for the next run.

Summarising forty thousand old posts finishes overnight, fifty at a time.

Digests

The digest that sends itself

Query, then send to each person who is due. It is the shape behind every weekly summary and monthly report. Sending is rate limited per minute for deliverability, so a large list is meant to drain across several runs rather than one burst.

See how email works for the sending side.

From your AI

Ask what is scheduled

The connection that built your app can list every job in the project, with the schedule, the next firing time, and whether the last run failed. You get the answer in the chat, without going to look for it.

"Is the Monday report still running?" comes back as a real answer.

Console

A tab that shows the truth

Projects with schedules get a Schedule tab: every job, when it fires next, when it last fired, how many times it has run, and the last error if there was one. Apps with no background work never see it.

One glance tells you whether last night's job actually ran.

— the numbers

The spec sheet, in plain terms.

Declaring a jobA [[cron]] block in hatchable.toml with two keys, path and schedule. A route file can also carry its own export const schedule.
Schedule formatStandard five-field cron. 0 9 * * * is every day at 09:00, 0 13 * * 1 is Monday at 13:00.
Fastest cadenceOnce an hour, on every plan including the paid ones. Anything faster is rejected at deploy with an explanation.
TimingThe platform checks for due jobs once a minute. Repeat firings carry a fixed offset within the hour so identical schedules do not stampede.
Time zoneUTC, always. There is no per-project time zone setting.
ReachabilityA handler declared access = 'scheduler' returns 404 to every request from the public internet. The scheduler invokes it from inside the platform.
Scheduled or manualScheduled fires arrive with an x-hatchable-trigger: cron header, so one handler can serve both the clock and a "run it now" button.
Time budgetAbout 65 seconds per scheduled fire. Work deferred from a live request gets several minutes. Anything longer is meant to be chunked across fires.
From codescheduler.at for a moment or a repeating expression, scheduler.now to run something immediately off the request, scheduler.cancel to call one off.
IncludedEvery plan, free included, with no separate quota on how many jobs you declare. Each fire counts as one function invocation, and free includes 100,000 a month. See the plans →

Numbers current as of August 2026 and enforced by the same deploy validator your project is checked against.

— things people ask for

Say it like this.

You never write a cron expression. These are ordinary sentences that turn into a schedule, a handler, and a deploy.

digest"Email me a summary of new signups every morning"
reminder"Send a reminder the day before each booking"
deferred"Don't make people wait for the PDF, build it in the background"
cleanup"Archive anything nobody has touched in 90 days"
monitoring"Check these prices hourly and tell me when one drops"
backfill"Write summaries for all the old posts, however long it takes"
— common questions

Asked and answered.

How often can a scheduled job run?

Once an hour is the fastest recurring schedule, and that is true on every plan, paid ones included. A schedule like */15 * * * * is rejected when you deploy, with an error that says exactly why. If you need something tighter, do it from your own code: a handler can hand work off to run in seconds, and can re-arm itself as often as it likes.

What time zone do schedules use?

UTC, always. A job set for 13:00 fires at 13:00 UTC no matter where you or your visitors are. There is no per-project time zone setting. If a local morning matters, pick the UTC hour that lands closest, or fan out by time zone inside the handler.

My cron block is in the config but nothing fires.

Check the key name. The parser reads path and schedule, and nothing else. A block written with route = instead of path = parses without complaint, produces no error, and never fires. It is the first thing to check, and the easiest to miss.

Can someone on the internet trigger my scheduled job?

No. A handler marked scheduler-only returns 404 to every request that arrives over the public internet, signed in or not. The scheduler reaches it from inside the platform, on a path that never touches the front door. That 404 is the security model, not a bug.

What if the job takes longer than a minute?

A scheduled fire gets roughly 65 seconds. That is deliberate. The answer is to process a fixed batch each time and let the handler queue the remainder for the next run, which is a pattern your AI already knows. Work that genuinely needs one long stretch can be deferred from a live request instead, where the budget is several minutes.

Can I see what is scheduled?

Yes, two ways. A project with schedules gets a Schedule tab in the console listing every job, when it fires next, when it last fired, how many times it has run, and the last error if there was one. Your AI can pull the same list over the project connection and read it back to you in the chat.

More detail lives in the config reference and the SDK reference.

— free to start, no card

Nothing to run, nothing to babysit.

Connect the AI you already use, describe the thing that should happen every morning, and it happens every morning.