9.9 Notification Campaigns
Controller: NotificationCampaignController · Route: api/v1/NotificationCampaign ·
GroupName: notifications.campaigns · 16 endpoints — the largest single controller in the framework.
A "campaign" is a scheduled, recurring, or multi-step ("workflow") bulk-notification entity, targeting a
resolved audience. See Chapter 6 for how an individual dispatch works underneath a
campaign step.
| Verb + route | Permission | Request | Response (data) |
|---|---|---|---|
POST GetDataTable | NotificationCampaigns.ViewAll | DtParameters | DtResult<NotificationCampaignDto> (unwrapped) |
GET GetDetails/{id} | NotificationCampaigns.View | — | NotificationCampaignDetailsDto |
GET GetEdit/{id} | NotificationCampaigns.Edit | — | NotificationCampaignEditDto |
GET GetDropdownList/{findBy} | NotificationCampaigns.View | route | NotificationCampaignViewListDto[] |
GET PreviewRecipients/{id} | NotificationCampaigns.Preview | — | NotificationCampaignAudienceDto[] |
GET DryRun/{id} | NotificationCampaigns.DryRun | — | NotificationCampaignEditDto |
POST Create | NotificationCampaigns.Create | NotificationCampaignEditDto | Guid |
PUT Update/{id} | NotificationCampaigns.Edit | NotificationCampaignEditDto | Guid |
DELETE Delete | NotificationCampaigns.Delete | DeleteDto[] (restore hardcoded false) | Guid |
PUT Restore | NotificationCampaigns.Delete | DeleteDto[] (restore hardcoded true) | Guid |
PUT Duplicate/{id} | NotificationCampaigns.Create | — | Guid (new campaign ID) |
PUT Active | NotificationCampaigns.Manage | DeleteDto[] | Guid |
PUT Deactive | NotificationCampaigns.Manage | DeleteDto[] | Guid |
PUT Pause/{id} | NotificationCampaigns.Schedule | — | Guid |
PUT Resume/{id} | NotificationCampaigns.Schedule | — | Guid |
PUT Cancel/{id} | NotificationCampaigns.Cancel | — | Guid |
Note: unlike most controllers, Delete and Restore are two separate routes here (not one route with a
?restore= query flag) — each hardcodes its own restore boolean internally.
PermissionConstants.NotificationCampaigns also defines Execute and Approve, which are not referenced
by any action in this controller — likely reserved for a future manual-trigger/approval-gate feature.
Field-level detail — NotificationCampaignEditDto
{
"id": "00000000-0000-0000-0000-000000000000",
"isActive": true,
"displayOrder": 0,
"name": "تذكير انتهاء الاشتراك - يوليو",
"fName": "Subscription expiry reminder - July",
"description": "Reminds tenants whose subscription expires within 7 days.",
"campaignType": 6,
"status": 0,
"priority": 5,
"startDate": "2026-07-10T00:00:00Z",
"endDate": "2026-07-31T23:59:59Z",
"timeZone": "Asia/Riyadh",
"recurrenceType": 2,
"cronExpression": null,
"customIntervalSeconds": null,
"steps": [
{
"stepNumber": 1,
"delay": 0,
"delayUnit": 0,
"eventKey": "SubscriptionExpiryReminder",
"templateId": "9a1b2c3d-0000-0000-0000-000000000010",
"channel": 1,
"trigger": 0,
"continueOnFailure": true,
"stopWorkflow": false,
"priority": 5
},
{
"stepNumber": 2,
"delay": 24,
"delayUnit": 2,
"eventKey": "SubscriptionExpiryReminder",
"templateId": "9a1b2c3d-0000-0000-0000-000000000011",
"channel": 6,
"trigger": 1,
"continueOnFailure": true,
"stopWorkflow": false,
"priority": 5
}
],
"audience": {
"sourceType": 3,
"userIds": null,
"roleNames": null,
"queryExpression": null
}
}
Enum reference:
campaignType(CampaignType):OneTime=1, Scheduled=2, Recurring=3, Workflow=4, Drip=5, Reminder=6status(CampaignStatus):Draft=0, Scheduled=1, Running=2, Paused=3, Completed=4, Cancelled=5, Failed=6recurrenceType(RecurrenceType):OneTime=0, Hourly=1, Daily=2, Weekly=3, Monthly=4, Yearly=5, Cron=6, CustomInterval=7delayUnit(DelayUnit):Seconds=0, Minutes=1, Hours=2, Days=3, Weeks=4, Months=5trigger(WorkflowStepTrigger):Always=0, OnDeliverySucceeded=1, OnDeliveryFailed=2, OnRetryExhausted=3, OnCustomEvent=4audience.sourceType(AudienceSourceType):SpecificUsers=0, Roles=1, Subscribers=2, TenantUsers=3, QueryResult=4, DynamicQuery=5
No FluentValidation validator exists for NotificationCampaignEditDto today — step ordering, delay
values, and audience configuration are not server-validated beyond what EF Core column constraints enforce.
GET PreviewRecipients/{id} — resolve the audience before running
{
"succeeded": true,
"message": null,
"data": [
{ "userId": "5f1a3b2c-0000-0000-0000-000000000001", "displayName": "Ahmed Al-Otaibi", "email": "ahmed@example.com", "phone": "966549000191" }
]
}
Business notes: this resolves NotificationCampaignAudience.SourceType (e.g. TenantUsers, Roles,
QueryResult) into a concrete recipient list via IAudienceResolver, without creating a
NotificationCampaignExecution — safe to call repeatedly while authoring a campaign.
GET DryRun/{id} — render without sending
Returns the campaign's steps with templates rendered against a sample data context (no real dispatch, no
NotificationDeliveryAttempt rows created) — lets an author verify Scriban template output before scheduling
a real run.
PUT Pause/{id} / Resume/{id} / Cancel/{id}
All three act on Status: Pause → Paused, Resume → back to Scheduled/Running, Cancel → Cancelled
(terminal — a cancelled campaign cannot be resumed, only duplicated via Duplicate/{id}).
Angular: a campaign builder wizard — steps + audience configured together, PreviewRecipients/DryRun
called before Create/Update is submitted, Pause/Resume/Cancel as toolbar actions on the campaign
detail page once it's running.
Backend: campaign-scheduler-pipeline (every 1 min) picks up due campaigns;
campaign-workflow-executor-pipeline (every 2 min) advances multi-step workflows;
campaign-cleanup-pipeline (daily 03:00 UTC) retires old completed/cancelled campaigns. All three run
through the platform's Background Jobs Framework — see Chapter 16 — Deployment.
Related APIs: Campaign Execution (run history/analytics per
campaign), Notification Templates (referenced by steps[].templateId).