A scheduled digest with a long wait

This workflow runs every morning from its declarative schedules export, so nothing outside it has to start a run. It also shows sleep, which parks the run without holding a process open. The run stays parked until the timer fires, then resumes from where it slept.

The whole workflow is a single workflows/daily-digest.workflow.ts file. It needs no trigger of its own: the schedules export is the trigger. Stackbone reads every cron pattern in UTC, so 0 8 * * * fires at 08:00 UTC, not at 08:00 local time.

workflows/daily-digest.workflow.ts
import { z } from '@stackbone/sdk';
import { sleep } from '@stackbone/sdk/workflow';

export const schedules = [{ cron: '0 8 * * *', input: { scope: 'daily' } }];

export const inputSchema = z.object({ scope: z.enum(['daily', 'weekly']) });
export const outputSchema = z.object({ sent: z.number() });

export async function dailyDigestWorkflow(input: z.infer<typeof inputSchema>) {
  'use workflow';

  const recipients = await collectRecipients(input.scope);

  let sent = 0;
  for (const to of recipients) {
    await deliver(to, input.scope);
    sent += 1;
    await sleep('5s'); // gentle pacing between sends, durable across a restart
  }

  return { sent };
}

async function collectRecipients(scope: string) {
  'use step';
  return scope === 'weekly' ? ['team@acme.com'] : ['sam@acme.com', 'lee@acme.com'];
}

async function deliver(to: string, scope: string) {
  'use step'; // idempotent on (to, scope, day)
  return { to, scope, delivered: true };
}

Adding a schedule at run time

The runtime reconciles the schedules export on every boot. That makes it the right home for a cadence that ships with the workspace. To add or drop one while the box is running, call the namespaced accessor from inside a workflow:

import { stackbone } from '@stackbone/sdk';

await stackbone.workflows.schedule('daily-digest', { scope: 'weekly' }, '0 8 * * 1');
await stackbone.workflows.unschedule('daily-digest');
const active = await stackbone.workflows.listSchedules();

These three live only on stackbone.workflows. There is no top-level scheduleWorkflow import to reach for. The runtime keeps dynamic schedules apart from the declarative ones, so a deploy never prunes them.

What's next

BUILT WITH ❤️ FROM CANADA AND SPAIN