Laravel queued job for an AI video API: delay, redispatch, poll

A Laravel ShouldQueue job reads a Sume video job once and redispatches itself with ->delay() from next_poll_after_seconds, so no worker sleeps during a render.

5 min readSume
All posts

In Laravel, wait for an AI video by dispatching a small queued job that reads Sume's status once and, if the job is not terminal, dispatches itself again with ->delay(...). Laravel documents delay() on dispatch as the way to keep a job from being available immediately (Laravel queues docs, read 2026-10-06), and Sume's status response tells you how long to wait with next_poll_after_seconds (Sume jobs guide, read 2026-10-06).

The job below uses Laravel's Http client and a services.sume.key config value. It stops after 120 reads, the same cap a 30 minute wall clock gives at 15 second spacing.

What is the job class?

Re-dispatching a fresh job, rather than release(), keeps the attempt counter for real errors separate from the number of polls. Laravel's note about Amazon SQS limiting delays to 15 minutes does not matter here because Sume caps a queued job's suggested wait at 30 seconds.

<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Support\Facades\Http;

class PollSumeVideo implements ShouldQueue
{
    use Dispatchable, Queueable;

    public $tries = 3;

    public function __construct(public string $jobId, public int $left = 120) {}

    public function handle(): void
    {
        $s = Http::withToken(config('services.sume.key'))
            ->get("https://api.sume.com/v1/jobs/{$this->jobId}/status")
            ->throw()->json();

        if ($s['terminal'] ?? false) {
            // update your videos row from $s['sume_status']
        } elseif ($this->left > 0) {
            self::dispatch($this->jobId, $this->left - 1)
                ->delay(now()->addSeconds($s['next_poll_after_seconds'] ?? 15));
        }
    }
}

Where is the paid request?

Not in this class. Submit from a controller or another job with Http::withToken(...)->withHeaders(['Idempotency-Key' => $video->id])->post('https://api.sume.com/v1/videos', [...]), store the returned id on the model, then PollSumeVideo::dispatch($id)->delay(now()->addSeconds(15)). Because the poll job never pays, $tries = 3 is safe: a Laravel retry re-reads and cannot create a second clip.

Terminal states to handle in the update step, Sume jobs guide (read 2026-10-06)
sume_statusterminalWhat to store
completedtrueFetch the result, copy the file, keep the job id
failedtrueShow the public error, no output exists; Sume refunds the hold
canceledtrueThe job stopped before generation started
queued or processingfalseRedispatch with the suggested delay

What if the job sits in queued for a long time?

queued is a normal accepted state. Sume applies the workspace's concurrency limit when workers move jobs to processing, so a burst of submits can wait in line without any error. The generation admission guide lists the limits: a Free workspace runs 1 job at a time with room for 5 more queued, Pro runs 4 with 20 queued. Keep polling at the suggested delay; do not resubmit.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume