Docs / Schedules

Schedules

Use schedules for recurring task runs. A scheduled task receives schedule metadata, not a user-defined payload. Keep dynamic inputs in code or secrets.

Define a schedule in task source

Use schedules.task() when the schedule should travel with the deployed task definition:

import { cache, image, logger, sandbox, schedules, source } from "@helmr/sdk"

const runtime = image("daily-report")
  .from("node:24-bookworm-slim")
  .workdir("/workspace")
  .run(["npm", "install", "-g", "bun@1.3.10"])
  .copy("/workspace/package.json", source.file("package.json"))
  .run(["bun", "install"], {
    cache: [{ mountPath: "/root/.bun/install/cache", cache: cache("daily-report-bun") }],
  })

const sb = sandbox("daily-report")
  .image(runtime)
  .workspace("/workspace")
  .resources({ cpu: 1, memory: "2Gi" })

export const dailyReport = schedules.task({
  id: "daily-report",
  sandbox: sb,
  secrets: [{ name: "REPORT_API_KEY", env: "REPORT_API_KEY" }],
  cron: { pattern: "0 9 * * *", timezone: "America/New_York" },
  run: async (payload) => {
    logger.info("running scheduled report", {
      scheduledAt: payload.timestamp.toISOString(),
      previous: payload.lastTimestamp?.toISOString(),
      upcoming: payload.upcoming.map((date) => date.toISOString()),
    })
  },
})

Deploy the task project:

helmr deploy

When the deployment is promoted, Helmr reconciles declarative schedules for the selected project environment. Code defines the project-level logical schedule once. Promotion creates or updates that environment’s schedule instance with its run options, active state, and next fire time, then enqueues that next fire into Redis/Valkey after the promotion commits. Removing the schedule from source and deploying again removes the selected environment instance; the logical schedule is removed after no environment instances remain.

Create a schedule from TypeScript

Use schedules.* with the default client, or client.schedules.* when a trusted service needs explicit credentials or multiple control-plane targets:

import { schedules } from "@helmr/sdk"

const schedule = await schedules.create({
  deduplicationKey: "daily-report-main",
  task: "daily-report",
  externalId: "main",
  cron: "0 9 * * *",
  timezone: "America/New_York",
  options: {
    maxDurationSeconds: 600,
  },
})

The task must already exist in the selected deployment. Any task secrets must already be stored in the selected project environment under the declared task secret names. deduplicationKey is required and is the stable public key that creates or replaces the project-level logical schedule and the selected environment instance, so repeated create calls update the same logical schedule instead of creating duplicates.

Manage imperative schedules through the same client:

await schedules.update(schedule, {
  task: "daily-report",
  externalId: "main",
  cron: "30 9 * * *",
  timezone: "America/New_York",
  options: {
    maxDurationSeconds: 600,
  },
})

await schedules.deactivate(schedule)
await schedules.activate(schedule)
await schedules.delete(schedule)

schedules.update() / client.schedules.update() replaces the imperative schedule definition and selected environment instance settings, and does not accept deduplicationKey. Send the task, cron, and run options that should remain on future scheduled runs.

Scheduled payload

The scheduled task payload is generated by Helmr:

FieldMeaning
timestampThe scheduled slot being fired.
lastTimestampPrevious fired slot for this schedule instance, when available.
timezoneThe normalized schedule timezone.
scheduleIdThe schedule record ID.
externalIdOptional external identifier from the schedule.
upcomingFuture scheduled slots from dispatch time.

Do not pass arbitrary schedule payloads. For fixed inputs, keep values in source code. For sensitive inputs, declare task secrets in task source.