Skip to content

Email Campaigns

Email campaigns let you send one-time broadcast messages to a targeted segment of contacts. Miranda handles the full lifecycle from draft composition through batched delivery.

FieldTypeDescription
idUUIDPrimary key
foundation_idUUIDOwning foundation
namestringInternal campaign name
subjectstring?Email subject line (supports merge fields)
bodystringHTML body (supports merge fields)
channelCampaignChannelemail for email campaigns
statusCampaignStatusLifecycle state (see below)
segment_criteriaJSON?Targeting criteria for audience resolution
statsJSON?Delivery statistics
scheduled_atdatetime?Scheduled send time
sent_atdatetime?When the campaign was actually sent
StatusDescription
draftBeing composed, not yet sent
scheduledQueued for future send
sendingCurrently being dispatched in batches
sentAll batches dispatched

The CampaignService::send() method orchestrates delivery:

  1. Status update — Campaign moves to sending, stats initialized to zeros
  2. Criteria enhancementemail_opt_in: true is automatically added to segment criteria
  3. Audience resolutionSegmentationService::resolve() builds the contact list
  4. Batch dispatch — Contact IDs are chunked into batches of 50, each dispatched as a SendCampaignBatch job
// CampaignService::send()
$criteria = $campaign->segment_criteria ?? [];
$criteria['email_opt_in'] = $criteria['email_opt_in'] ?? true;
$contactIds = $this->segmentationService
->resolve($campaign->foundation_id, $criteria)
->pluck('id')
->toArray();
$chunks = array_chunk($contactIds, 50);
foreach ($chunks as $chunk) {
SendCampaignBatch::dispatch($campaign, $chunk);
}

If the resolved segment is empty, the campaign is marked as sent immediately.

Each contact in a batch is sent an email via the EmailService:

  1. Merge fields are replaced in subject and body
  2. Email dispatched via Resend API (https://api.resend.com/emails)
  3. CommunicationLog record created for audit trail
  4. Activity recorded on the contact’s timeline (ActivityType::EmailSent)

The sender address defaults to the foundation’s email, falling back to the system default in config/mail.php.

Available merge placeholders (used in subject and body):

  • first_name — Contact’s first name
  • last_name — Contact’s last name
  • foundation_name — Foundation’s name

Wrap each placeholder in double curly braces in your content (e.g., the first_name placeholder becomes the contact’s actual first name at send time).

The stats JSON field tracks delivery metrics:

{
"sent": 150,
"delivered": 145,
"opened": 72,
"clicked": 23,
"bounced": 5
}

Retrieve stats via GET /api/campaigns/{campaign}/stats.

PlanEmails per month
Starter1,000
Growth5,000
ProUnlimited
MethodPathDescription
GET/api/campaignsList campaigns
POST/api/campaignsCreate a campaign
GET/api/campaigns/{id}Show a campaign
PUT/api/campaigns/{id}Update a campaign
DELETE/api/campaigns/{id}Delete a campaign
POST/api/campaigns/{id}/sendSend a campaign
GET/api/campaigns/{id}/statsGet delivery statistics

Every email sent creates a CommunicationLog record:

FieldValue
channelemail
directionoutbound
subjectMerged subject line
body_previewFirst 500 characters of plain text body
statussent or failed
campaign_idLink to the campaign