Tags & Activities
Tags provide freeform labeling for contacts, while activities create an automatic timeline of every significant interaction. Both systems are foundation-scoped and integrate with segmentation.
Tag model
Section titled “Tag model”Tags are foundation-scoped labels applied to contacts via a many-to-many pivot table (contact_tag).
| Field | Type | Description |
|---|---|---|
id | UUID | Primary key |
foundation_id | UUID | Owning foundation |
name | string | Tag label (e.g., “major-donor”, “board-member”, “2026-gala”) |
color | string? | Optional hex color for UI display |
Applying tags
Section titled “Applying tags”Tags are attached to contacts through the contact_tag pivot table, which also stores foundation_id for scoping. The relationship supports syncWithoutDetaching for idempotent tag application:
// Apply tags to a contact$contact->tags()->syncWithoutDetaching([ $tag->id => ['foundation_id' => $foundationId],]);Tags during import
Section titled “Tags during import”The import workflow supports tags in two ways:
- CSV column — A
tagscolumn with comma-separated values (e.g.,"donor,2026-gala") - Global tags — Applied to all imported contacts via the
options.tagsarray
Tags are auto-created if they don’t already exist in the foundation.
Using tags in segmentation
Section titled “Using tags in segmentation”Tags are a first-class segmentation criterion. The tags criteria key filters contacts who have any of the specified tag names:
{ "tags": ["major-donor", "annual-gala"]}API endpoints
Section titled “API endpoints”| Method | Path | Description |
|---|---|---|
| GET | /api/tags | List all tags for the foundation |
| POST | /api/tags | Create a new tag |
| DELETE | /api/tags/{tag} | Delete a tag |
Activities
Section titled “Activities”Activity model
Section titled “Activity model”Activities are timeline events recorded against contacts for audit and segmentation. They are created automatically by Miranda’s services — you do not create them manually.
| Field | Type | Description |
|---|---|---|
id | UUID | Primary key |
foundation_id | UUID | Owning foundation |
contact_id | UUID | The contact this event belongs to |
type | ActivityType | Event category (see table below) |
description | string? | Human-readable description |
metadata | JSON? | Arbitrary context (IDs, amounts, etc.) |
Activity types
Section titled “Activity types”| Value | Description | Triggered by |
|---|---|---|
donated | Contact made a donation | DonationService::recordDonation() |
email_sent | Email was sent to contact | EmailService::send() |
email_opened | Contact opened an email | Webhook tracking |
sms_sent | SMS was sent to contact | SmsService::send() |
application_submitted | Scholarship application submitted | ScholarshipService::createApplication() |
scholarship_awarded | Scholarship award granted | ScholarshipService::awardScholarship() |
tag_added | Tag was applied to contact | Tag management |
note_added | Note was added to contact | Note creation |
imported | Contact was imported | ImportService::importBatch() |
ActivityService
Section titled “ActivityService”The ActivityService provides a single method for recording activities:
class ActivityService{ public function log( Contact $contact, ActivityType $type, ?string $description = null, ?array $metadata = null, ): Activity;}Example activity metadata
Section titled “Example activity metadata”// Donation activity{ "donation_id": "uuid-here", "amount": 5000, "donation_page": "Annual Fund"}
// Import activity{ "source": "csv"}
// Campaign email activity{ "campaign_id": "uuid-here", "sequence_step_id": null}Activity timeline
Section titled “Activity timeline”Every contact’s activities() relationship provides a chronological timeline of all interactions. This powers the contact detail view in the admin dashboard, giving foundation staff a complete picture of each person’s engagement history.