Automating the notice
Tutorial 8 of 12 writes the Workflow that sends each renewal notice at 60 days, then moves the test clock to watch it run.
A Workflow is one file that holds its description, its code and its test. Leaseline declares a Lease Noun, and its renewal notice goes out every morning without anyone asking.
What you build
You write the Workflow behind the renewal notice: each day, every active lease that ends in 60 days gets Lease.notify. Then you advance the test clock and read the Events it wrote.
Steps
To write the Workflow, create its document under the renewals Process:
workflows/renewals/notice-at-60-days.mdx --- stakes: low --- ## Send the notice at 60 days {#notice-at-60-days} Each morning,every active Lease that ends in 60 days gets its renewal notice. ```ts every.day(async ($) => { const endsOn = new Date(Date.now() + 60 * 86_400_000).toISOString().slice(0, 10) for await (const lease of $.Lease.list({ where: `status = active and endsOn = ${endsOn}` }).all()) await $.step(lease.$id, () => $.Lease.notify({ lease: lease.$id })) }) ``` ```ts test import { expect, run } from 'startup-builder/ test' const { calls } = await run({}, { Lease: { list: () => [{ $id: 'lease_4loEed1JG4', status: 'active' }], notify: () => ({}) } }) expect(calls).toEqual(['Lease.list', 'Lease.notify']) ``` Each
$.stepis durable: a retry resumes after the last finished lease.To run a day in test, move the clock:
npx startup-builder clock advance --by 1dTo read what ran, list the latest Events:
npx startup-builder show /events --fields 'seq,name, at'
Result
The Workflow runs every morning, notifies each lease at 60 days and writes Lease.notified for each. From 2026-10-01 to 2026-10-22, Leaseline's ran 22 times and sent 14 notices. Checkpoint leaseline@ch08 (status planned) adds workflows/.
Next steps
- Tutorial 9 gives the residents' replies to the Success Manager agent.
- Workflows: triggers, schedules and steps.