Skip to main content
Timers provide a way to pause workflow execution for a specified duration. Unlike regular sleep() calls, workflow timers are durable and survive process restarts.

Overview

Workflow timers are deterministic and persisted. When a workflow is paused for a timer, the workflow execution state is saved, and the workflow resumes automatically after the specified duration.

Basic Usage

Use the timer() helper function to create a durable delay:
use Workflow\Workflow;
use function Workflow\timer;

class NotificationWorkflow extends Workflow
{
    public function execute($userId)
    {
        // Send immediate notification
        yield activity(SendEmailActivity::class, $userId, 'Welcome!');
        
        // Wait 24 hours
        yield timer(86400);
        
        // Send follow-up notification
        yield activity(SendEmailActivity::class, $userId, 'How are you enjoying our service?');
        
        return 'notifications_sent';
    }
}

Time Units

Timers accept time in multiple formats:

Seconds (Integer)

// Wait 60 seconds
yield timer(60);

String Format

// Wait 2 hours and 30 minutes
yield timer('2 hours 30 minutes');

// Wait 3 days
yield timer('3 days');

CarbonInterval

use Carbon\CarbonInterval;

// Wait 1 week
yield timer(CarbonInterval::weeks(1));

// Wait 2 hours
yield timer(CarbonInterval::hours(2));

Helper Functions

The package provides convenient time-based helper functions:
use function Workflow\{seconds, minutes, hours, days, weeks, months, years};

class ReminderWorkflow extends Workflow
{
    public function execute($taskId)
    {
        // Wait 30 seconds
        yield seconds(30);
        
        // Wait 5 minutes
        yield minutes(5);
        
        // Wait 2 hours
        yield hours(2);
        
        // Wait 1 day
        yield days(1);
        
        // Wait 1 week
        yield weeks(1);
        
        // Wait 3 months
        yield months(3);
        
        // Wait 1 year
        yield years(1);
        
        yield activity(SendReminderActivity::class, $taskId);
        
        return 'reminder_sent';
    }
}
All time helper functions return a PromiseInterface that resolves when the timer completes.

Multiple Timers

You can use multiple timers in sequence:
public function execute($orderId)
{
    yield activity(ConfirmOrderActivity::class, $orderId);
    
    // Wait 1 hour
    yield hours(1);
    yield activity(SendShippingNotificationActivity::class, $orderId);
    
    // Wait 24 hours
    yield days(1);
    yield activity(SendDeliveryConfirmationActivity::class, $orderId);
    
    // Wait 7 days
    yield weeks(1);
    yield activity(SendReviewRequestActivity::class, $orderId);
    
    return 'completed';
}

Zero and Negative Durations

Timers with zero or negative durations resolve immediately:
// Resolves immediately
yield timer(0);

// Also resolves immediately
yield timer(-10);

Timers with Queries

Combine timers with query methods to monitor workflow state during delays:
use Workflow\QueryMethod;

class WaitingWorkflow extends Workflow
{
    private string $status = 'initialized';
    
    #[QueryMethod]
    public function getStatus(): string
    {
        return $this->status;
    }
    
    public function execute($duration)
    {
        $this->status = 'waiting';
        
        yield timer($duration);
        
        $this->status = 'completed';
        
        return 'workflow';
    }
}
Query the workflow while it’s waiting:
$workflow = WorkflowStub::make(WaitingWorkflow::class);
$workflow->start(10);

sleep(1);

// Query while timer is active
$status = $workflow->getStatus(); // 'waiting'

while ($workflow->running());

$status = $workflow->getStatus(); // 'completed'

Timeout Pattern

Combine timers with await conditions to implement timeouts:
use function Workflow\{await, timer};

class ApprovalWorkflow extends Workflow
{
    private bool $approved = false;
    
    #[SignalMethod]
    public function approve(): void
    {
        $this->approved = true;
    }
    
    public function execute()
    {
        // Wait for approval or timeout after 48 hours
        $timeout = timer(hours(48));
        $approval = await(fn () => $this->approved);
        
        // Race between timeout and approval
        $result = yield race([$timeout, $approval]);
        
        if ($this->approved) {
            return 'approved';
        }
        
        // Timeout occurred
        yield activity(NotifyTimeoutActivity::class);
        return 'timeout';
    }
}
For a built-in timeout implementation, use the awaitWithTimeout() helper function.

Queue Driver Limitations

Some queue drivers have maximum delay limits:

SQS Driver

Amazon SQS has a maximum delay of 15 minutes (900 seconds). For longer durations, the workflow automatically breaks the delay into multiple timer dispatches:
// On SQS, this will be broken into multiple 15-minute timers
yield hours(2); // Automatically handled

Retry Delays

Timers are commonly used for retry logic with exponential backoff:
public function execute($apiEndpoint)
{
    $maxRetries = 5;
    $retryCount = 0;
    
    while ($retryCount < $maxRetries) {
        try {
            $result = yield activity(CallApiActivity::class, $apiEndpoint);
            return $result;
        } catch (\Exception $e) {
            $retryCount++;
            
            if ($retryCount >= $maxRetries) {
                throw $e;
            }
            
            // Exponential backoff: 1s, 2s, 4s, 8s, 16s
            $delay = pow(2, $retryCount);
            yield timer($delay);
        }
    }
}

Scheduled Workflows

Timers enable scheduled workflow patterns:
class DailyReportWorkflow extends Workflow
{
    public function execute()
    {
        while (true) {
            // Generate report
            yield activity(GenerateReportActivity::class);
            
            // Wait 24 hours before next report
            yield days(1);
        }
    }
}
For infinitely looping workflows, consider using Continue as New to prevent history from growing unbounded.

Use Cases

Delayed Actions

Execute activities after a specific delay (send follow-up email after 24 hours)

Retry Logic

Implement exponential backoff between retry attempts

Scheduled Tasks

Run periodic tasks at fixed intervals

Timeouts

Set maximum wait times for external events or approvals

Best Practices

Prefer hours(2) over timer(7200) for better readability.
Be aware of queue driver limitations when using very long delays.
Never use PHP’s sleep() in workflows - it’s not durable and will break replay.
Use awaitWithTimeout() when you need both a condition check and a timeout.

Build docs developers (and LLMs) love