diff --git a/app/Jobs/Campaigns/Export.php b/app/Jobs/Campaigns/Export.php
index 749335b6bc..e70f1bb256 100644
--- a/app/Jobs/Campaigns/Export.php
+++ b/app/Jobs/Campaigns/Export.php
@@ -4,6 +4,7 @@
 
 use App\Jobs\FileCleanup;
 use App\Models\Campaign;
+use App\Models\CampaignExport;
 use App\Services\Campaign\ExportService;
 use App\User;
 use Exception;
@@ -31,16 +32,19 @@ class Export implements ShouldQueue
 
     protected int $userId;
 
+    protected int $campaignExportId;
+
     protected bool $assets;
 
     /**
      * CampaignExport constructor.
      */
-    public function __construct(Campaign $campaign, User $user, bool $assets = false)
+    public function __construct(Campaign $campaign, User $user, CampaignExport $campaignExport, bool $assets = false)
     {
         $this->campaignId = $campaign->id;
         $this->userId = $user->id;
         $this->assets = $assets;
+        $this->campaignExportId = $campaignExport->id;
     }
 
     /**
@@ -49,6 +53,12 @@ public function __construct(Campaign $campaign, User $user, bool $assets = false
      */
     public function handle()
     {
+        $campaignExport = CampaignExport::find($this->campaignExportId);
+        if (!$campaignExport) {
+            return 0;
+        }
+        $campaignExport->update(['status' => CampaignExport::STATUS_RUNNING]);
+
         /** @var Campaign|null $campaign */
         $campaign = Campaign::find($this->campaignId);
         if (!$campaign) {
@@ -69,12 +79,13 @@ public function handle()
             ->assets($this->assets)
             ->export();
 
+        $campaignExport->update(['status' => CampaignExport::STATUS_FINISHED, 'size' => $service->filesize(), 'path' => $service->exportPath()]);
+
         // Don't delete in "sync" mode as there is no delay.
         $queue = config('queue.default');
         if ($queue !== 'sync') {
             FileCleanup::dispatch($service->exportPath())->delay(now()->addMinutes(60));
         }
-
         return 1;
     }
 
@@ -83,6 +94,12 @@ public function handle()
      */
     public function failed(Exception $exception)
     {
+        $campaignExport = CampaignExport::find($this->campaignExportId);
+        if (!$campaignExport) {
+            return;
+        }
+        $campaignExport->update(['status' => CampaignExport::STATUS_FAILED]);
+
         // Set the campaign export date to null so that the user can try again.
         // If it failed once, trying again won't help, but this might motivate
         // them to report the error.
diff --git a/app/Models/CampaignExport.php b/app/Models/CampaignExport.php
new file mode 100644
index 0000000000..4928517a8d
--- /dev/null
+++ b/app/Models/CampaignExport.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Database\Eloquent\MassPrunable;
+use Illuminate\Database\Eloquent\Model;
+
+/**
+ * Class CampaignExport
+ * @package App\Models
+ *
+ * @property int $id
+ * @property int $campaign_id
+ * @property int $created_by
+ * @property int $status
+ * @property string $path
+ *
+ */
+class CampaignExport extends Model
+{
+    use MassPrunable;
+
+    public const TYPE_ENTITIES = 1;
+    public const TYPE_ASSETS = 2;
+
+    public const STATUS_SCHEDULED = 1;
+    public const STATUS_RUNNING = 2;
+    public const STATUS_FINISHED = 3;
+    public const STATUS_FAILED = 4;
+
+    public $fillable = [
+        'size',
+        'type',
+        'status',
+        'campaign_id',
+        'created_by',
+        'path',
+    ];
+
+    /**
+     * Automatically prune old elements from the db
+     */
+    public function prunable(): Builder
+    {
+        return static::where('updated_at', '<=', now()->subDays(90));
+    }
+
+}
diff --git a/app/Services/Campaign/ExportService.php b/app/Services/Campaign/ExportService.php
index d6f304c3c7..cb6a1d2bce 100644
--- a/app/Services/Campaign/ExportService.php
+++ b/app/Services/Campaign/ExportService.php
@@ -5,6 +5,7 @@
 use App\Facades\CampaignCache;
 use App\Jobs\Campaigns\Export;
 use App\Models\Image;
+use App\Models\CampaignExport;
 use App\Notifications\Header;
 use App\Traits\CampaignAware;
 use App\Traits\UserAware;
@@ -29,6 +30,7 @@ class ExportService
     protected bool $assets = false;
 
     protected int $files = 0;
+    protected int $filesize = 0;
 
     public function exportPath(): string
     {
@@ -46,8 +48,23 @@ public function queue(): self
         $this->campaign->export_date = date('Y-m-d');
         $this->campaign->saveQuietly();
 
-        Export::dispatch($this->campaign, $this->user, false);
-        Export::dispatch($this->campaign, $this->user, true);
+        $entitiesExport = CampaignExport::create([
+            'campaign_id' => $this->campaign->id,
+            'created_by' => $this->user->id,
+            'type' => CampaignExport::TYPE_ENTITIES,
+            'status' => CampaignExport::STATUS_SCHEDULED,
+        ]);
+
+        Export::dispatch($this->campaign, $this->user, $entitiesExport, false);
+
+        $assetExport = CampaignExport::create([
+            'campaign_id' => $this->campaign->id,
+            'created_by' => $this->user->id,
+            'type' => CampaignExport::TYPE_ASSETS,
+            'status' => CampaignExport::STATUS_SCHEDULED,
+        ]);
+
+        Export::dispatch($this->campaign, $this->user, $assetExport, true);
 
         return $this;
     }
@@ -66,6 +83,11 @@ public function export(): self
         return $this;
     }
 
+    public function filesize(): int
+    {
+        return $this->filesize;
+    }
+
     protected function prepare(): self
     {
         $saveFolder = storage_path() . '/exports/campaigns/';
@@ -212,6 +234,8 @@ protected function finish(): self
             $saveFolder = storage_path() . '/exports/campaigns/';
 
             $this->archive->saveTo($saveFolder);
+            $this->filesize = (int) floor(filesize($this->path) / pow(1024, 2));
+
         } catch (Exception $e) {
             // The export might fail if the zip is too big.
             $this->files = 0;
diff --git a/database/migrations/2023_10_25_173253_create_campaign_exports_table.php b/database/migrations/2023_10_25_173253_create_campaign_exports_table.php
new file mode 100644
index 0000000000..ec839b30a1
--- /dev/null
+++ b/database/migrations/2023_10_25_173253_create_campaign_exports_table.php
@@ -0,0 +1,39 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class () extends Migration {
+    /**
+     * Run the migrations.
+     */
+    public function up(): void
+    {
+        Schema::create('campaign_exports', function (Blueprint $table) {
+
+            $table->increments('id');
+            $table->unsignedInteger('campaign_id');
+            $table->unsignedInteger('created_by')->nullable();
+            $table->integer('size');
+            $table->tinyInteger('type');
+            $table->tinyInteger('status');
+            $table->string('path')->nullable();
+            $table->timestamps();
+
+            $table->index(['status']);
+
+            $table->foreign('campaign_id')->references('id')->on('campaigns')->onDelete('cascade');
+            $table->foreign('created_by')->references('id')->on('users')->onDelete('set null');
+
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::dropIfExists('campaign_exports');
+    }
+};