Skip to main content

Overview

Workflow scheduling in Serverless Workflow allows developers to specify when and how their workflows should be executed, ensuring timely response to events and efficient resource utilization. The DSL offers multiple scheduling mechanisms to accommodate different execution patterns.
Scheduling determines when new workflow instances are created, not what happens within an already running instance.

Scheduling Properties

The workflow scheduling configuration offers four key properties:
PropertyDescription
everyDefines the interval for periodic workflow execution
cronUses CRON expressions to schedule execution at specific times
afterSpecifies a delay duration before restarting the workflow after completion
onEnables event-driven scheduling based on specified events

CRON-Based Scheduling

CRON expressions allow you to schedule workflow execution at specific times or intervals using the familiar CRON syntax.

Basic CRON Scheduling

document:
  dsl: '1.0.3'
  namespace: scheduled
  name: daily-report
  version: '1.0.0'

schedule:
  cron: "0 0 * * *"  # Every day at midnight

do:
  - generateReport:
      call: reportGenerator
  - sendReport:
      call: emailService
      with:
        report: ${ .generateReport.output }
schedule.cron
string
CRON expression defining when the workflow should execute

CRON Expression Format

CRON expressions use five or six fields:
┌───────────── minute (0 - 59)
│ ┌───────────── hour (0 - 23)
│ │ ┌───────────── day of month (1 - 31)
│ │ │ ┌───────────── month (1 - 12)
│ │ │ │ ┌───────────── day of week (0 - 6) (Sunday to Saturday)
│ │ │ │ │
│ │ │ │ │
* * * * *

CRON Examples

Every Minute

schedule:
  cron: "* * * * *"

Every Hour

schedule:
  cron: "0 * * * *"

Every Day at Noon

schedule:
  cron: "0 12 * * *"

Every Monday at 9 AM

schedule:
  cron: "0 9 * * 1"

Every Weekday at 6 PM

schedule:
  cron: "0 18 * * 1-5"

First Day of Every Month at Midnight

schedule:
  cron: "0 0 1 * *"

Every 15 Minutes

schedule:
  cron: "*/15 * * * *"

Twice Daily (8 AM and 8 PM)

schedule:
  cron: "0 8,20 * * *"

CRON Scheduling Use Cases

Daily Backup

document:
  dsl: '1.0.3'
  namespace: maintenance
  name: daily-backup
  version: '1.0.0'

schedule:
  cron: "0 2 * * *"  # Every day at 2 AM

do:
  - backupDatabase:
      call: backupService
      with:
        target: production-db
  
  - uploadToStorage:
      call: storageService
      with:
        backup: ${ .backupDatabase.output }
        location: s3://backups/

Weekly Report

document:
  dsl: '1.0.3'
  namespace: reports
  name: weekly-sales-report
  version: '1.0.0'

schedule:
  cron: "0 9 * * 1"  # Every Monday at 9 AM

do:
  - gatherSalesData:
      call: analyticsService
      with:
        period: last-week
  
  - generateReport:
      call: reportGenerator
      with:
        data: ${ .gatherSalesData.output }
  
  - distributeReport:
      call: emailService
      with:
        recipients: ["[email protected]", "[email protected]"]
        report: ${ .generateReport.output }

Hourly Data Sync

document:
  dsl: '1.0.3'
  namespace: integration
  name: hourly-sync
  version: '1.0.0'

schedule:
  cron: "0 * * * *"  # Every hour on the hour

do:
  - fetchUpdates:
      call: http
      with:
        method: get
        endpoint:
          uri: https://api.example.com/updates
  
  - syncToDatabase:
      call: databaseService
      with:
        updates: ${ .fetchUpdates.output }

Interval-Based Scheduling

The every property defines periodic execution intervals:

Basic Interval

schedule:
  every:
    hours: 2
    minutes: 30
schedule.every
object
Duration object defining the interval between workflow executions
The every property ensures periodic runs regardless of the previous run’s status. A new instance starts at each interval, even if previous instances are still running.

Interval Examples

Every 5 Minutes

schedule:
  every:
    minutes: 5

Every 30 Seconds

schedule:
  every:
    seconds: 30

Every 6 Hours

schedule:
  every:
    hours: 6

Every 2 Days

schedule:
  every:
    days: 2

Complex Interval

schedule:
  every:
    hours: 1
    minutes: 30
    seconds: 45

Interval Scheduling Use Cases

Health Check Monitor

document:
  dsl: '1.0.3'
  namespace: monitoring
  name: health-check
  version: '1.0.0'

schedule:
  every:
    minutes: 5

do:
  - checkServices:
      fork:
        branches:
          - checkAPI:
              call: http
              with:
                method: get
                endpoint:
                  uri: https://api.example.com/health
          
          - checkDatabase:
              call: databaseService
              with:
                action: ping
          
          - checkCache:
              call: cacheService
              with:
                action: ping
  
  - reportStatus:
      call: monitoringService
      with:
        results: ${ .checkServices.output }

Frequent Data Polling

document:
  dsl: '1.0.3'
  namespace: polling
  name: status-poller
  version: '1.0.0'

schedule:
  every:
    seconds: 30

do:
  - pollStatus:
      call: http
      with:
        method: get
        endpoint:
          uri: https://api.example.com/status
  
  - processChanges:
      if: ${ .pollStatus.output.hasChanges }
      call: changeProcessor
      with:
        changes: ${ .pollStatus.output.changes }

Delayed Restart Scheduling

The after property specifies a delay before restarting the workflow after completion:

Basic Delayed Restart

schedule:
  after:
    minutes: 5
schedule.after
object
Duration to wait after workflow completion before starting a new instance
Unlike every, the after property waits for the workflow to complete before starting the delay timer. This prevents overlap and ensures sequential execution.

Delayed Restart Examples

# Wait 10 minutes after completion
schedule:
  after:
    minutes: 10

# Wait 1 hour after completion
schedule:
  after:
    hours: 1

# Wait 30 seconds after completion
schedule:
  after:
    seconds: 30

Delayed Restart Use Case

document:
  dsl: '1.0.3'
  namespace: processing
  name: batch-processor
  version: '1.0.0'

schedule:
  after:
    minutes: 5

do:
  - fetchBatch:
      call: http
      with:
        method: get
        endpoint:
          uri: https://api.example.com/batch/next
  
  - processBatch:
      if: ${ .fetchBatch.output.items | length > 0 }
      for:
        each: item
        in: ${ .fetchBatch.output.items }
      do:
        - processItem:
            call: processor
            with:
              item: ${ .item }

Event-Driven Scheduling

The on property enables event-driven scheduling, triggering workflow execution based on specified events:

Basic Event-Driven Scheduling

schedule:
  on:
    events:
      - with:
          type: order.created
          source: https://api.example.com/orders
schedule.on
object
Event-driven scheduling configuration
schedule.on.events
array
Array of CloudEvents filters that trigger workflow execution

Event Correlation

You can specify multiple events that must be received:
schedule:
  on:
    events:
      all:
        - with:
            type: payment.received
            source: https://payments.example.com
        - with:
            type: inventory.reserved
            source: https://inventory.example.com
schedule.on.events.all
array
All specified events must be received to trigger the workflow
schedule.on.events.any
array
Any one of the specified events will trigger the workflow

Event-Driven Examples

Single Event Trigger

document:
  dsl: '1.0.3'
  namespace: ecommerce
  name: order-processor
  version: '1.0.0'

schedule:
  on:
    events:
      - with:
          type: order.placed
          source: https://api.example.com/orders

do:
  - validateOrder:
      call: validator
      with:
        order: ${ $workflow.input[0].data }
  
  - processPayment:
      call: paymentService
      with:
        orderId: ${ $workflow.input[0].data.orderId }

Multiple Event Correlation (ALL)

document:
  dsl: '1.0.3'
  namespace: fulfillment
  name: order-fulfillment
  version: '1.0.0'

schedule:
  on:
    events:
      all:
        - with:
            type: payment.confirmed
            source: https://payments.example.com
        - with:
            type: inventory.allocated
            source: https://inventory.example.com
        - with:
            type: shipping.ready
            source: https://shipping.example.com

do:
  - fulfillOrder:
      call: fulfillmentService
      with:
        paymentEvent: ${ $workflow.input[0] }
        inventoryEvent: ${ $workflow.input[1] }
        shippingEvent: ${ $workflow.input[2] }

Multiple Event Options (ANY)

document:
  dsl: '1.0.3'
  namespace: notifications
  name: alert-handler
  version: '1.0.0'

schedule:
  on:
    events:
      any:
        - with:
            type: system.error
            source: https://api.example.com
        - with:
            type: system.warning
            source: https://api.example.com
        - with:
            type: system.critical
            source: https://api.example.com

do:
  - determineseverity:
      call: severityAnalyzer
      with:
        event: ${ $workflow.input[0] }
  
  - sendAlert:
      call: alertingService
      with:
        severity: ${ .determineS severity.output }
        message: ${ $workflow.input[0].data.message }

Event-Driven Workflow Input

In event-driven scheduled workflows, the input is structured as an array containing the events that trigger the execution:
do:
  - processFirstEvent:
      call: processor
      with:
        event: ${ $workflow.input[0] }  # First event
  
  - processAllEvents:
      for:
        each: event
        in: ${ $workflow.input }
      do:
        - handleEvent:
            call: eventHandler
            with:
              event: ${ .event }
Authors can reference individual events using $workflow.input[index], where index indicates the event’s position starting from 0.

Event-Driven vs Start Listen Task

While both schedule.on and a start listener task enable event-driven execution, they serve distinct purposes:

schedule.on

Defines when a new workflow instance should be created based on external events:
schedule:
  on:
    events:
      - with:
          type: user.registered
          source: https://api.example.com

do:
  - welcomeUser:
      call: welcomeService
schedule.on manages the creation of new workflow instances. Faults or timeouts in the scheduling process are typically invisible to the workflow instance.

Start Listen Task

Defines what should happen after a workflow instance is created:
do:
  - waitForActivation:
      listen:
        to:
          any:
            - with:
                type: account.activated
                source: https://api.example.com
  
  - processActivation:
      call: activationProcessor
A start listener task operates within an already instantiated workflow. If it experiences a timeout or fault, it can cause the entire workflow instance to fail.

Key Differences

Aspectschedule.onStart listen Task
PurposeCreates new instanceWaits within instance
ScopeExternal to instanceInternal to instance
Failure ImpactDoes not affect instanceCan fault the instance
InputEvents in $workflow.inputEvents accessible in task
Use CaseTriggering workflowsWaiting for events mid-workflow

Combined Scheduling Strategies

You can combine different scheduling approaches, though typically only one is used:

CRON with Event Fallback

# This workflow runs on schedule OR can be triggered by events
schedule:
  cron: "0 */6 * * *"  # Every 6 hours
  on:
    events:
      - with:
          type: data.urgentUpdate
          source: https://api.example.com
When combining scheduling methods, the workflow instance is created whenever any scheduling condition is met.

Scheduling Best Practices

1

Choose the right scheduling type

Use CRON for time-based schedules, every for regular intervals, after for sequential processing, and on for event-driven execution.
2

Consider timezone implications

Be aware that CRON expressions typically use UTC. Document the timezone for clarity.
3

Avoid overlapping executions

Use after instead of every when you need to ensure previous workflow instances complete before starting new ones.
4

Set workflow timeouts

Always configure timeouts for scheduled workflows to prevent runaway executions.
5

Handle missing events

For event-driven workflows, implement logic to handle cases where expected events don’t arrive.
6

Monitor scheduled workflows

Implement logging and monitoring to track scheduled executions and identify failures.

Common Patterns

Pattern: Scheduled with Manual Override

schedule:
  cron: "0 2 * * *"  # Automatic: Daily at 2 AM
  on:
    events:
      - with:
          type: manual.trigger
          source: https://admin.example.com

do:
  - checkTriggerSource:
      set:
        isManual: ${ $workflow.input | length > 0 }
  
  - performOperation:
      call: operationService
      with:
        priority: ${ if .isManual then "high" else "normal" end }

Pattern: Rate-Limited Event Processing

schedule:
  every:
    minutes: 5

do:
  - fetchPendingEvents:
      call: eventStore
      with:
        limit: 100
  
  - processEvents:
      for:
        each: event
        in: ${ .fetchPendingEvents.output }
      do:
        - handleEvent:
            call: eventProcessor
            with:
              event: ${ .event }

Pattern: Conditional Scheduling

schedule:
  cron: "0 * * * *"  # Every hour

do:
  - checkBusinessHours:
      call: timeService
  
  - processIfBusinessHours:
      if: ${ .checkBusinessHours.output.isBusinessHours }
      call: businessProcessor
  
  - skipIfAfterHours:
      if: ${ .checkBusinessHours.output.isBusinessHours == false }
      call: logger
      with:
        message: Skipping execution - outside business hours
  • Workflows - Learn about workflow structure and lifecycle
  • Events - Understand event-driven architecture
  • Timeouts - Configure timeouts for scheduled workflows
  • Task Flow - Control execution within scheduled workflows

Build docs developers (and LLMs) love