NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Campaigns for Laravel

Progress & lookup

Look a campaign up by UUID and read its progress:

use RoundlyConsulting\Campaigns\Facades\Campaigns;

$campaign = Campaigns::find($uuid);         // ?Campaign — null when unknown
$campaign = Campaigns::findOrFail($uuid);   // throws CampaignNotFound when unknown

$progress = $campaign->progress;            // CampaignProgress
$progress->status;                          // CampaignStatus, e.g. CampaignStatus::Processing
$progress->sent;                            // recipients delivered
$progress->failed;                          // deliveries that failed — never counted as sent
$progress->pending;                         // recipients with no outcome yet
$progress->total;                           // recipients in the campaign
$progress->percentage();                    // sent / total * 100, rounded to 2 decimals
$progress->remaining();                     // total - sent - failed, never below 0
$progress->isRunning();                     // status is Processing
$progress->isComplete();                    // status is Completed, Failed or Canceled

$campaign->startedAt;                       // ?Carbon — first switch to Processing
$campaign->endedAt;                         // ?Carbon — first terminal status

Lookups read the configured store. The default in-memory store lives only as long as the request, console command or queued job that created the campaign. Once that request ends, find() returns null and findOrFail() and campaign() throw CampaignNotFound — from a later request, a dashboard poll or a queue worker alike. To track progress across processes, switch to the database store (see Database persistence).

Progress counts each recipient once: sent were delivered, failed failed — recorded as failed by the delivery job, or the job itself failed (threw, timed out, ran out of attempts) — and the rest are still to go. A failed delivery is never counted as sent, and it never ends the campaign early. percentage() is the share delivered, failures excluded, and returns 0.0 while total is 0.

When the counters update

The counters are saved at every status change — prepare, start, completion, failure and cancel. While a campaign is Pending or Processing, find(), all() and the handle’s progress() read the live numbers from its recipients and its job batch instead, so a dashboard sees progress as recipients are delivered. The batch itself is one call away:

use RoundlyConsulting\Campaigns\Facades\Campaigns;

$batch = Campaigns::campaign($campaign)->batch();   // ?Illuminate\Bus\Batch — null before prepare()

$batch?->progress();       // Laravel's own percentage
$batch?->pendingJobs;      // jobs still waiting
$batch?->failedJobs;       // jobs that failed outright

Individual recipients carry their own state — list them with Campaigns::campaign($uuid)->recipients(), or read one with ->recipient($recipientUuid).

Listing and cancelling

use RoundlyConsulting\Campaigns\Campaign;
use RoundlyConsulting\Campaigns\Facades\Campaigns;

// A page of campaigns in creation order: offset, limit (defaults 0 and 10).
Campaigns::all(0, 25)->each(function (Campaign $campaign): void {
    logger()->info($campaign->subject, $campaign->progress->toArray());
});

// Cancel a campaign and its batch — the status becomes Canceled.
$campaign = Campaigns::cancel($uuid);   // a campaign that already ended comes back unchanged

cancel() cancels the batch, moves the campaign to Canceled and returns it; a campaign that has already ended comes back unchanged. Jobs still waiting in the queue see the cancelled batch and exit without sending. find() returns null for an unknown UUID; findOrFail(), campaign(), start() and cancel() throw CampaignNotFound.

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.