Looking to hire Laravel developers? Try LaraJobs

laravel-taskbridge maintained by codetechnl

Description
Run Laravel jobs on a schedule via AWS EventBridge Scheduler and SQS — no server running around the clock required. Works alongside Laravel's built-in scheduler.
Author
CodeTechNL
Last update
2026/04/17 07:19 (dev-main)
License
Links
Downloads
94

Comments
comments powered by Disqus

laravel-taskbridge

A database-driven scheduler that runs your Laravel jobs through AWS EventBridge Scheduler and SQS — without needing a server running around the clock.

Laravel's built-in scheduler still works alongside this package. TaskBridge is an addition, not a replacement: use it for jobs that should be triggered by EventBridge so you are not dependent on a continuously running server or schedule:run cron for those tasks. Jobs are stored in your database, synced to EventBridge, and dispatched into SQS at the configured time. The queue worker picks them up and processes them as normal Laravel jobs.

How it works

Your job class (implements ScheduledJob)
        │
        ▼
  TaskBridge::sync()
        │  stores job in database + creates/updates schedule in AWS EventBridge
        ▼
  AWS EventBridge Scheduler
        │  fires at the configured cron time
        │  puts a raw SQS message on your queue
        ▼
  Your SQS queue worker (php artisan queue:work)
        │  picks up the message and executes the job
        │  TaskBridge middleware wraps execution
        ▼
  Run log (taskbridge_job_runs)
        │  status, duration, output, triggered_by
        ▼
  Domain events (JobExecutionSucceeded / JobExecutionFailed / …)

Your existing Laravel scheduler (schedule:run) keeps working unchanged. TaskBridge adds a separate path for jobs that should be triggered by EventBridge, removing the requirement for a continuously running server or cron daemon for those specific tasks.

Requirements

  • PHP 8.3+
  • Laravel 12 or 13
  • AWS account with EventBridge Scheduler and SQS access

Installation

composer require codetechnl/laravel-taskbridge

Publish and run the migrations:

php artisan vendor:publish --tag=taskbridge-migrations
php artisan migrate

Publish the config file:

php artisan vendor:publish --tag=taskbridge-config

AWS setup

1. IAM execution role

EventBridge Scheduler needs an IAM role to put messages on your SQS queue. This role is separate from your application's AWS credentials.

Create the role with an SQS trust policy and sqs:SendMessage permission:

# Trust policy
aws iam create-role \
  --role-name taskbridge-scheduler-role \
  --assume-role-policy-document '{
    "Version":"2012-10-17",
    "Statement":[{
      "Effect":"Allow",
      "Principal":{"Service":"scheduler.amazonaws.com"},
      "Action":"sts:AssumeRole"
    }]
  }'

# Permission policy
aws iam put-role-policy \
  --role-name taskbridge-scheduler-role \
  --policy-name taskbridge-sqs-send \
  --policy-document '{
    "Version":"2012-10-17",
    "Statement":[{
      "Effect":"Allow",
      "Action":"sqs:SendMessage",
      "Resource":"arn:aws:sqs:*:*:*"
    }]
  }'

2. EventBridge schedule group

Create a schedule group (or use the default default group):

aws scheduler create-schedule-group --name taskbridge

3. Environment variables

AWS_DEFAULT_REGION=eu-west-1

TASKBRIDGE_SCHEDULER_ROLE_ARN=arn:aws:iam::123456789012:role/taskbridge-scheduler-role
TASKBRIDGE_SCHEDULE_GROUP=taskbridge
TASKBRIDGE_SCHEDULE_PREFIX=taskbridge

Creating a scheduled job

A scheduled job is a standard Laravel ShouldQueue job that also implements the ScheduledJob contract:

use CodeTechNL\TaskBridge\Contracts\ScheduledJob;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class SendDailyReport implements ScheduledJob, ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function cronExpression(): string
    {
        return '0 8 * * *'; // every day at 08:00
    }

    public function handle(): void
    {
        // your logic here
    }
}

Optional interfaces

LabeledJob — custom display name

use CodeTechNL\TaskBridge\Contracts\LabeledJob;

class SendDailyReport implements ScheduledJob, LabeledJob, ShouldQueue
{
    public function taskLabel(): string
    {
        return 'Send Daily Report';
    }
    // ...
}

GroupedJob — group jobs in the UI

use CodeTechNL\TaskBridge\Contracts\GroupedJob;

class SendDailyReport implements ScheduledJob, GroupedJob, ShouldQueue
{
    public function group(): string
    {
        return 'Reporting';
    }
    // ...
}

ConditionalJob — runtime skip condition

use CodeTechNL\TaskBridge\Contracts\ConditionalJob;

class SendDailyReport implements ScheduledJob, ConditionalJob, ShouldQueue
{
    public function shouldRun(): bool
    {
        return ! app()->isMaintenanceMode();
    }
    // ...
}

ReportsOutput — log structured output

use CodeTechNL\TaskBridge\Concerns\HasJobOutput;
use CodeTechNL\TaskBridge\Contracts\ReportsOutput;

class ImportProducts implements ScheduledJob, ReportsOutput, ShouldQueue
{
    use HasJobOutput;

    public function handle(): void
    {
        $processed = 0;
        $skipped   = 0;

        // ... your import logic ...

        $this->reportOutput([
            'processed' => $processed,
            'skipped'   => $skipped,
        ]);
    }
}

The metadata is stored as a success JobOutput in the run log. On failure, TaskBridge automatically records an error output with the exception message — no action needed in the job.

Job discovery

By default, TaskBridge scans app/Jobs for classes implementing ScheduledJob:

// config/taskbridge.php
'discover' => [
    app_path('Jobs'),
],

To scan additional directories or register jobs from vendor packages manually:

'discover' => [
    app_path('Jobs'),
    app_path('Modules/Billing/Jobs'),
],

'jobs' => [
    \Vendor\Package\Jobs\SomeScheduledJob::class,
],

Syncing to EventBridge

After adding jobs to the database (via the Filament UI or manually), sync them to EventBridge:

use CodeTechNL\TaskBridge\Facades\TaskBridge;

$result = TaskBridge::sync();
// $result->created, $result->updated, $result->removed

Manual execution

// Run immediately (bypasses enabled/shouldRun check)
$run = TaskBridge::run(SendDailyReport::class, force: true);

// Run with enabled/shouldRun checks
$run = TaskBridge::run(SendDailyReport::class);

// Dry run — calls handle() but Bus::fake() prevents actual queue dispatches
$run = TaskBridge::run(SendDailyReport::class, dryRun: true);

Enable / disable jobs

TaskBridge::enable(SendDailyReport::class);  // enables + syncs to EventBridge
TaskBridge::disable(SendDailyReport::class); // disables + removes from EventBridge

Cron override

Temporarily override the cron expression without editing the job class:

TaskBridge::overrideCron(SendDailyReport::class, '*/15 * * * *');
TaskBridge::resetCron(SendDailyReport::class); // restore to cronExpression()

Configuration reference

// config/taskbridge.php

return [
    'models' => [
        // Override with your own model if needed (must extend the original)
        'scheduled_job'     => \CodeTechNL\TaskBridge\Models\ScheduledJob::class,
        'scheduled_job_run' => \CodeTechNL\TaskBridge\Models\ScheduledJobRun::class,
    ],

    'eventbridge' => [
        'region'         => env('AWS_DEFAULT_REGION', 'eu-west-1'),
        'prefix'         => env('TASKBRIDGE_SCHEDULE_PREFIX', 'taskbridge'),
        'role_arn'       => env('TASKBRIDGE_SCHEDULER_ROLE_ARN'),
        'schedule_group' => env('TASKBRIDGE_SCHEDULE_GROUP', 'default'),
        'retry_policy'   => [
            'maximum_event_age_seconds' => env('TASKBRIDGE_RETRY_MAX_AGE_SECONDS', 86400),
            'maximum_retry_attempts'    => env('TASKBRIDGE_RETRY_MAX_ATTEMPTS', 185),
        ],
    ],

    'discover' => [
        app_path('Jobs'),
    ],

    'jobs' => [],

    'logging' => [
        'enabled'        => env('TASKBRIDGE_LOGGING_ENABLED', true),
        'retention_days' => env('TASKBRIDGE_RUN_RETENTION_DAYS', 30),
    ],
];

Built-in maintenance jobs

PruneRunsJob

Deletes run log entries older than taskbridge.logging.retention_days (default: 30 days). Runs daily at 03:00.

Enable it by adding it to the jobs array in config:

'jobs' => [
    \CodeTechNL\TaskBridge\Jobs\PruneRunsJob::class,
],

Then sync to register it in EventBridge.

CheckMissedJobs

Monitors jobs that haven't run within twice their expected cron interval and dispatches a JobMissed event. Listen to this event to send alerts.

Events

Listen to these events to add custom behaviour:

use CodeTechNL\TaskBridge\Events\JobExecutionFailed;

Event::listen(JobExecutionFailed::class, function (JobExecutionFailed $event) {
    // $event->job   — ScheduledJob model
    // $event->run   — ScheduledJobRun model
    // $event->exception — Throwable
    Notification::send(...);
});
Event Payload
JobExecutionStarted $job, $run
JobExecutionSucceeded $job, $run
JobExecutionFailed $job, $run, $exception
JobExecutionSkipped $job, $run, $reason
JobSynced $job
JobRemoved identifier string
JobMissed $job

Custom models

Extend and override the default models for custom logic:

// app/Models/ScheduledJob.php
class ScheduledJob extends \CodeTechNL\TaskBridge\Models\ScheduledJob
{
    // custom scopes, relations, etc.
}
// config/taskbridge.php
'models' => [
    'scheduled_job'     => App\Models\ScheduledJob::class,
    'scheduled_job_run' => App\Models\ScheduledJobRun::class,
],

Translating status labels

Publish the language files:

php artisan vendor:publish --tag=taskbridge-lang

This creates lang/vendor/taskbridge/en/enums.php. Copy and translate for other locales.

Running the tests

./vendor/bin/pest