Skip to content

Segmentation & Targeting

Miranda’s segmentation engine lets you define filter criteria to build contact lists dynamically. Segments can be saved for reuse in campaigns, exports, and reporting.

The SegmentationService applies a criteria array to an Eloquent query builder, progressively narrowing the contact set. Criteria are combined with AND logic — a contact must match all specified filters.

class SegmentationService
{
public function apply(Builder $query, array $criteria): Builder;
public function resolve(string $foundationId, array $criteria): Builder;
}

resolve() starts with all contacts in a foundation and applies the criteria. apply() can be used on an existing query for composition.

KeyTypeDescription
relationship_typesstring[]Filter to contacts with any of these types (donor, applicant, subscriber, general)
tagsstring[]Filter to contacts tagged with any of these tag names
giving_minintegerMinimum lifetime giving (in cents) from donor relationship metadata
giving_maxintegerMaximum lifetime giving (in cents) from donor relationship metadata
first_contact_afterdate stringOnly contacts with first_contact_at on or after this date
email_opt_inbooleanFilter by email opt-in preference
sms_opt_inbooleanFilter by SMS opt-in preference
{
"relationship_types": ["donor"],
"tags": ["major-donor", "annual-gala"],
"giving_min": 50000,
"email_opt_in": true
}

This resolves to: donors who are tagged “major-donor” OR “annual-gala”, have given at least $500 lifetime, and are opted in to email.

The SavedSegment model persists criteria for reuse:

FieldTypeDescription
idUUIDPrimary key
foundation_idUUIDOwning foundation
namestringDisplay name (e.g., “Lapsed donors”)
criteriaJSONThe criteria object applied at query time

Saved segments are dynamic — they re-evaluate against current contact data every time they are used. There is no cached member list.

When a campaign is created, its segment_criteria field stores the targeting criteria. At send time, the CampaignService resolves the segment and dispatches batched jobs:

// CampaignService::send()
$criteria = $campaign->segment_criteria ?? [];
// Auto-filter for opt-in based on channel
if ($campaign->channel === CampaignChannel::Email) {
$criteria['email_opt_in'] = true;
}
$contactIds = $this->segmentationService
->resolve($campaign->foundation_id, $criteria)
->pluck('id')
->toArray();

The campaign service automatically adds opt-in filtering based on the campaign channel — email_opt_in for email campaigns, sms_opt_in for SMS campaigns.

MethodPathDescription
GET/api/segmentsList saved segments
POST/api/segmentsCreate a saved segment
POST/api/segments/previewPreview contact count for given criteria (without saving)
DELETE/api/segments/{segment}Delete a saved segment

The preview endpoint lets you test criteria before saving or sending:

Terminal window
POST /api/segments/preview
Content-Type: application/json
{
"criteria": {
"relationship_types": ["donor"],
"giving_min": 10000
}
}

Returns the count of matching contacts without creating a saved segment.