Inspect durable graph runs for quote, invoice, task, agenda, and scheduled system automations.
Product automations are published system graphs in supabase/seeds/063_system_workflows.sql. Domain mutations, the Stripe webhook, and the workflow scheduler call dispatchWorkflowEvent or start a scheduled run. The Workflow SDK executes each matched graph via runWorkflowGraph.
| Graph | Start | Notes |
|---|---|---|
quote-lifecycle | quote.sent | 7-day and 1-day customer reminders, owner nudge, expire, 30-day auto-reject |
quote-accepted | quote.accepted | Emails, then draft invoice + follow-up task. Cancels the lifecycle sleep for that quote |
quote-rejected | quote.rejected | Owner notify. Cancels the lifecycle sleep |
invoice-lifecycle | invoice.sent | Template reminder cadence (negative daysAfterDue allowed), overdue, owner nudge |
invoice-paid / invoice-cancelled | matching events | Notify; cancel the lifecycle sleep for that invoice |
invoice-payment-failed / invoice-disputed | Stripe webhook | Notify only; lifecycle reminders keep running |
Sibling cancellation uses cancelRunsForDomainRecord so a run asleep in delay does not wait for the next status check. On a terminal quote or invoice event it cancels every active run on that record, whichever workflow key started it, so organization copies of the lifecycle graphs stop too.
| Graph | Start | Happy path |
|---|---|---|
order-lifecycle | order.confirmed | Confirmed, paid, picking, shipped, delivered, invoiced, completed |
purchase-order-lifecycle | purchase-order.sent | Sent, confirmed (manual), in transit, received, billed (manual) |
shipment-lifecycle | shipment.shipped | In transit, out for delivery, delivered |
The dispatcher routes an event to one subject by its prefix, so an order lifecycle only hears order.* events. reactToCommerceEvent (packages/core-workflow-runtime/src/workflow-runtime/order-events.ts) mirrors related events onto the order and purchase order:
invoice.sent / invoice.paid become order.invoiced / order.paid.order.shipped, order.ready-for-pickup, order.delivered, or order.picked-up. A delivered return becomes order.returned.purchase-order.in-transit.The same reactor creates an order when a quote is accepted and its template has create order on accept set. It also sends the built-in staff notifications for confirmed and backordered orders, shipment exceptions, received and overdue purchase orders, and low stock.
GET /api/v1/internal/commerce/daily (17 6 * * *) publishes purchase-order.overdue once per promised date, promotion.expiring for active promotions ending within 3 days, and promotion.limit-reached when committed redemptions reach the total limit.
Templates offer one-click starters from packages/automation/src/workflows/starter-automations.ts. Most are attached to the template. The starters triggered by stock events are created organization-wide instead, because inventory.* events carry no template.
| Starter | Trigger | Does |
|---|---|---|
| Low stock reorder | inventory.low-stock (organization-wide) | Drafts a purchase order from the reorder point and sends it. The purchase-order.send authorize floor can require approval |
| Purchase order overdue | purchase-order.overdue | Reminds the supplier and notifies the purchaser |
| Back in stock | inventory.back-in-stock (organization-wide) | Fills backorders for the variant and tells customers whose order is complete again |
| Prepaid order to picking | order.paid when the order is paid up front | Creates a draft shipment as the pick list |
| Ready for pickup | order.ready-for-pickup | Messages the customer (WhatsApp when connected, otherwise email) and reminds them after 7 days |
| Delivered, then invoice | order.delivered for on-fulfillment orders | Invoices the delivered lines and sends the invoice |
| Graph | Cron (Europe/Amsterdam) | Action |
|---|---|---|
task-due-scan | 0 7 * * * | Publish task.due-soon / task.overdue with idempotency kind:taskId:dueAt |
invoice-overdue-digest | 0 8 * * 1 | Email overdue invoices |
draft-stale-digest | 0 8 * * 1 | Email draft quotes/invoices older than 14 days |
billing-usage-threshold | 0 * * * * | Publish usage-threshold events; billing-usage-threshold-notify emails owners |
System schedules run once per enabled organization that has not opted out of the key and has no published workflow of its own on it. Each run carries the organization, and the scans and digests only read that organization's records.
There is no root workflow:* script. The Workflow SDK CLI (workflow, also installed as wf) is a dependency of apps/saas and apps/api, so run it through pnpm from the app whose runs you want to see. Start the apps (pnpm dev:portless), then open the local run viewer:
pnpm --filter api exec workflow webFor non-interactive output, list runs, steps or events with workflow inspect:
pnpm --filter api exec workflow inspect runs
pnpm --filter api exec workflow inspect steps --runId=<runId>Use --filter saas instead for runs started by the SaaS app. Add --backend vercel --env preview (or --env production) to inspect a deployed environment with your vc login session.
Open the Vercel dashboard and go to Workflows under Observability. Runs keep the domain id (quoteId, invoiceId, taskId, agendaItemId) on the trigger payload.
Organization workflows expose POST …/workflows/definitions/{definitionId}/publish, POST …/workflows/runs/{runId}/cancel, and POST …/workflows/runs/{runId}/approval (approve or deny a waiting approval step; the caller also needs the permission the approval node requires). GET …/workflows/runs?definitionKey= lists runs across every version of a key.
workflow-failed-alert emails organization owners when another published graph fails. It does not rematch itself. Check the failed run's step error, then replay or fix the domain record and republish if the definition changed.
Last updated on