Jobs
Jobs are deprecated. Use scheduled flows instead, which provide durable execution, automatic retries per step, and better error handling.
Jobs allow you to execute tasks in the background on a schedule. For new projects, use scheduled flows which offer the same scheduling capabilities with additional benefits:
- Durable execution — Steps persist and resume on failure
- Per-step retries — Failed steps retry independently
- No time limit — Flows can run longer than 15 minutes
- Better observability — Step-by-step traces in the Console
Migrating to scheduled flows
A scheduled job like this:
job EmailNewCustomerReport {
@schedule("every weekday at 9am")
}import { EmailNewCustomerReport, models } from "@teamkeel/sdk";
export default EmailNewCustomerReport(async (ctx) => {
const customers = await models.customer.findMany({
where: { createdAt: { greaterThan: yesterday() } }
});
await sendEmail({
to: "team@company.com",
subject: "New Customers",
body: formatReport(customers),
});
});Becomes a scheduled flow:
flow EmailNewCustomerReport {
@schedule("every weekday at 9am")
@permission(roles: [System])
}import { EmailNewCustomerReport, models } from "@teamkeel/sdk";
export default EmailNewCustomerReport(async (ctx) => {
const customers = await ctx.step("get customers", async () => {
return await models.customer.findMany({
where: { createdAt: { greaterThan: yesterday() } }
});
});
await ctx.step("send email", async () => {
await sendEmail({
to: "team@company.com",
subject: "New Customers",
body: formatReport(customers),
});
});
});The key differences:
- Declare as
flowinstead ofjob - Add
@permission(roles: [System])for automated runs - Wrap work in
ctx.step()calls for durability - Files go in
/flowsinstead of/jobs
See Scheduled Flows for full documentation.
Legacy documentation
The following documentation is for existing jobs. New projects should use scheduled flows.
Limitations
- Jobs have 512MB of disk space available
- Jobs can only run for 15 minutes before being killed and marked as failed
Scheduled jobs
A job can be scheduled using the @schedule attribute. Job schedules are defined the same way as flow schedules.
job EmailNewCustomerReport {
@schedule("every monday at 9am")
}Manual jobs
To manually run a job from the console, define a permission rule:
job DoImportantThing {
@permission(expression: true)
}You can add @permission to a job that also uses @schedule, allowing both scheduled and manual runs.
Role-based permissions
job BigDataImport {
@permission(roles: [Developer])
}
role Developer {
emails {
"sally@corp.com"
"barry@corp.com"
}
}The expression true in a job permission means anyone with access to your project can trigger the job. Jobs are never publicly accessible.
Inputs
Manual jobs can accept inputs displayed as a form in the console:
job RefundOrder {
inputs {
orderId ID
}
@permission(expression: true)
}Scheduled jobs cannot have inputs.
Testing
Jobs are available in tests via @teamkeel/testing:
import { jobs, actions, resetDatabase } from "@teamkeel/testing";
import { test, expect, beforeEach } from "vitest";
beforeEach(resetDatabase);
test("EmailNewCustomerReport updates new customer flag", async () => {
await jobs.emailNewCustomerReport();
const res = await actions.getCustomer();
expect(res.reportSent).toBeTruthy();
});