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 statusLookups 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 outrightIndividual 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 unchangedcancel() 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 cryptoBy 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.