Database persistence
InMemoryCampaignStore keeps campaigns in memory for the current request, console command or queued job — ideal for tests and create-and-send flows. For campaigns you look up, start or cancel later — delayed starts, dashboards, retries, audit — switch to the shipped database store with one line:
CAMPAIGNS_STORE='RoundlyConsulting\Campaigns\Stores\DatabaseCampaignStore'Then publish and run the migrations (see Installation) — they are never auto-loaded. The database store behaves like the in-memory one — the same contract test suite runs against both — including keying recipients by campaign, so the same recipient added to two campaigns is kept in both.
Schema
| Table | Columns | Model |
|---|---|---|
campaigns | id, uuid (unique), subject, content (longText), from_name, from_address, status (indexed, default Created), sent, failed, pending, total, batch, started_at, ended_at, timestamps, deleted_at | CampaignRecord |
campaign_recipients | id, uuid, campaign_uuid (indexed), name, reachable_at, has_been_processed, error_occured, error_message, timestamps, deleted_at — unique (campaign_uuid, uuid) | CampaignRecipientRecord |
Models
The records are ordinary Eloquent models with soft deletes, so you can query them for dashboards and reports:
use RoundlyConsulting\Campaigns\Enums\CampaignStatus;
use RoundlyConsulting\Campaigns\Models\CampaignRecord;
$record = CampaignRecord::query()->where('uuid', $uuid)->firstOrFail();
$record->status; // CampaignStatus (cast)
$record->recipients()->count(); // HasMany CampaignRecipientRecord
$record->recipients()->where('error_occured', true)->get(); // failed recipients
CampaignRecord::query()->where('status', CampaignStatus::Processing)->get();Both models ship factories for your tests:
use RoundlyConsulting\Campaigns\Models\CampaignRecipientRecord;
use RoundlyConsulting\Campaigns\Models\CampaignRecord;
$campaign = CampaignRecord::factory()->create();
CampaignRecipientRecord::factory()->count(3)->create([
'campaign_uuid' => $campaign->uuid,
]);Use real UUIDs
With the database store, campaign and recipient ids must be valid UUIDs. find() and findRecipient() return null for anything else without running a query, so a custom ->uuid() or CampaignRecipient id that isn’t a UUID can’t be found again. Generated ids are always valid.
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.