---
title: "Database persistence — Campaigns for Laravel | Roundly"
description: "Switch to the DatabaseCampaignStore — schema, Eloquent models, factories and the UUID rule for persisted campaigns."
url: https://roundly-consulting.com/open-source/docs/campaigns-for-laravel/persistence
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Campaigns for Laravel](https://roundly-consulting.com/open-source/docs/campaigns-for-laravel.md)

# 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:

```bash
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:

```php
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:

```php
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 crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## 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.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/campaigns-for-laravel.md)
