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

Sending a campaign

The Campaigns facade is the fastest way to create, address and send a campaign. create() takes the subject and the content and returns a fluent PendingCampaign builder; the campaign UUID is generated for you:

use RoundlyConsulting\Campaigns\Facades\Campaigns;

$campaign = Campaigns::create('Our spring collection is here', '<p>Hello, the spring collection just landed.</p>')
    ->from('[email protected]', 'Example Shop')
    ->to(['[email protected]', '[email protected]'])
    ->dispatch();                    // prepare + start in one call

// Follow it through its handle.
$progress = Campaigns::campaign($campaign->uuid)->progress();
$progress->percentage();   // share delivered so far, e.g. 42.5 — live while sending
$progress->failed;         // deliveries that failed
$progress->remaining();    // recipients with no outcome yet
$progress->isRunning();    // bool
$progress->isComplete();   // bool

// Cancel it and its batch; the cancelled Campaign comes back.
$campaign = Campaigns::cancel($campaign->uuid);

dispatch() prepares the campaign — opens its job batch, stores it with its recipients and marks it Pending — then starts it and returns the fresh Campaign.

Builder methods

  • from($address, ?$name) — the sender; the name defaults to the address. Without it, the stored send defaults apply — and with no from-address default either, the mailer’s global mail.from.
  • to($recipients) — add recipients; additive. See Recipients.
  • uuid($uuid) — use your own id instead of the generated one. A uuid another campaign already holds throws CampaignAlreadyExists: preparing never overwrites, or re-sends, an existing campaign.
  • subject($subject) / content($content) — replace what you passed to create().
  • onlyVerified(bool $onlyVerified = true) / viaContactType($type) — resolution rules for contacts and owners.
  • prepare() — open the batch, store the campaign with its recipients and leave it Pending, without sending.
  • dispatch() — prepare and start in one call.

Prepare now, send later

use RoundlyConsulting\Campaigns\Facades\Campaigns;

$campaign = Campaigns::create('Draft subject', 'Draft body')
    ->uuid('0b6f4c1e-8d2a-4f0e-9c3b-5a7d2e1f4b6c')          // your own id instead of a generated UUID
    ->subject('Our spring collection is here')               // replace the subject
    ->content('<p>Hello, the spring collection just landed.</p>')
    ->from('[email protected]')                               // the name defaults to the address
    ->to('[email protected]')
    ->prepare();                                             // batch opened, status Pending, nothing sent

// Later — from a scheduled job or an admin action:
Campaigns::start($campaign->uuid);   // only a Pending campaign starts

prepare() stores the campaign with its recipients and returns it Pending. Start it with Campaigns::start() whenever you’re ready. Only a Pending campaign starts — starting one that is already sending, or has ended, throws InvalidCampaignTransition instead of queueing every recipient twice. That holds when two processes start it at the same moment too — a double-clicked button, two scheduler workers: the move to Processing is one atomic compare-and-set, and only its winner queues the recipients. A campaign with nobody to send to — no recipients, or every owner filtered out — completes as soon as it starts.

Starting in a later request needs the database store: the in-memory store only lives for the current request, console command or queued job.

Every other facade method — lookups, listing, the per-campaign handle, settings() — is covered in The Campaigns facade.

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.