Campaign Service
Campaign Service is the multi-channel outbound campaign engine for the Nexivo platform. It supports call (outbound dialing), email (templates), WhatsApp (templates), and physical mail (QR code tracking). Quartz provides persistent, JDBC-backed job scheduling so campaigns survive restarts and scale across pods.
Deployment
campaign-services/campaign-service — Spring Boot 3.4.4 / Java 21 · 3 pods in nexivo namespace
Tech Stack
| Layer |
Technology |
| Framework |
Spring Boot 3.4.4 |
| Language |
Java 21 |
| Database |
PostgreSQL |
| Job scheduling |
Quartz (JDBC job store, table prefix QRTZ_) |
| Object storage |
S3-compatible (DigitalOcean Spaces) |
| QR code generation |
Google ZXing |
Core Entities
Campaign
| Field |
Type |
Notes |
id |
UUID |
Primary key |
title |
String |
|
description |
String |
|
campaignStatus |
Enum |
DRAFT / SCHEDULED / RUNNING / PAUSED / COMPLETED |
whatsapp |
boolean |
Channel flag |
email |
boolean |
Channel flag |
call |
boolean |
Channel flag |
mail |
boolean |
Channel flag |
schedule |
FK |
Schedule entity |
Channel Configs
| Entity |
PK |
Key Fields |
CallCampaign |
campaignId |
callHandlerConfig JSONB (type, lang, teamId, aiAgentId), script, source phone |
EmailCampaign |
campaignId |
templateId, sourceEmail, templateData JSONB, attachment FK |
WhatsappCampaign |
campaignId |
templateId, templateData JSONB, attachment FK |
MailCampaign |
campaignId |
subject, message, responseUrl, attachment FK |
Scheduling & Targeting
| Entity |
Key Fields |
Schedule |
startOn / endOn (LocalDate), operationalStartTime / EndTime, timeZone, operationalDays (DayOfWeek[]), operationFrequency (WEEKDAYS / DAILY / WEEKLY / MONTHLY / YEARLY) |
CampaignTarget |
minAge, maxAge, contactTags JSONB, genders JSONB, contactType JSONB (required) |
Response Tracking
| Field |
Type |
Notes |
trackingId |
UUID |
Per-response identifier |
campaignId |
FK |
Owning campaign |
channel |
Enum |
WHATSAPP / EMAIL / MAIL |
status |
Enum |
SEND / RESPONDED |
responseData |
JSONB |
Channel-specific payload |
contact |
JSONB |
Contact snapshot at time of response |
Campaign Status Machine
stateDiagram-v2
[*] --> DRAFT : create campaign
DRAFT --> SCHEDULED : schedule
DRAFT --> RUNNING : start immediately
SCHEDULED --> RUNNING : start time reached\n(CampaignStatusScheduleJob)
SCHEDULED --> PAUSED : manual pause
SCHEDULED --> DRAFT : edit
RUNNING --> PAUSED : manual pause\n(Quartz job paused)
RUNNING --> COMPLETED : all contacts reached\nor end time reached
PAUSED --> RUNNING : resume\n(Quartz job resumed)
PAUSED --> SCHEDULED : reschedule
COMPLETED --> [*]
note right of DRAFT : Editable
note right of SCHEDULED : Editable
note right of PAUSED : Editable
Status changes emit a Spring ApplicationEvent. CampaignStatusListener (async) translates the event into a Quartz pause / resume / unschedule operation.
REST API
| Endpoint |
Methods |
Description |
/campaigns |
GET, POST, PUT, DELETE |
Campaign CRUD |
/campaigns/{id}/status |
PUT |
Transition campaign status |
/campaigns/{id}/schedule |
POST |
Attach or update schedule |
/campaigns/{id}/responses |
GET, POST |
Record and retrieve responses |
/campaigns/{id}/calls |
GET, PUT |
Call campaign config |
/campaigns/{id}/emails |
GET, PUT (multipart) |
Email config + optional attachment |
/campaigns/{id}/whatsapp |
GET, PUT (multipart) |
WhatsApp config + optional attachment |
/campaigns/{id}/mails |
GET, PUT (multipart) |
Mail config + optional attachment |
/campaigns/{id}/mails/print |
GET |
Generate QR codes for batch |
/campaigns/{id}/mails/{trackId}/status |
POST |
Mark physical mail as sent |
/campaigns/{id}/mails/metrics |
GET |
Sent count, response count, response rate % |
/campaigns/{id}/targets |
GET, PUT |
Audience targeting config |
/campaign-schedule |
POST |
Manually trigger scheduled campaign check |
Channel Comparison
| Channel |
Delivery mechanism |
Template-based |
Attachment |
Tracking |
| Call |
Outbound dial via Call Service |
No (script config) |
No |
Call logs |
| Email |
Via Email Service |
Yes |
Yes (S3) |
Open / click (external) |
| WhatsApp |
Bulk via WhatsApp Service |
Yes (approved templates) |
Yes (S3) |
CampaignResponses |
| Mail |
Physical print + QR code |
No |
Yes (S3) |
QR scan → callback |
Execution Engine (Quartz)
Jobs
| Job |
Type |
Behaviour |
CampaignExecutionJob |
Repeating |
Fetches next batch of contacts, dispatches channel requests |
CampaignStatusScheduleJob |
One-shot (per campaign) |
Fires at startOn / endOn to transition status automatically |
Both jobs store tenantId in JobDataMap; TenantContextHolder is set at the start of each execution to ensure correct tenant isolation.
Execution Config
| Config Key |
Default |
Description |
campaign.execution.batch-size |
100 |
Contacts processed per job firing |
campaign.execution.interval |
10 |
Seconds between repeated job firings |
Channel Dispatch
| Channel |
Service call |
| Call |
CampaignCallRequest → CallService.createCampaignCall() |
| Email |
Merge contact + templateData → EmailService.send() |
| WhatsApp |
Bulk WhatsAppTemplateMessage → WhatsappService.send() |
| Mail |
Generate 300×300 PNG QR code (Base64) encoding responseUrl?trackId=X&campaignId=Y&contactType=Z → deliver for printing |
Physical Mail Tracking Flow
sequenceDiagram
participant Op as Operator
participant CS as Campaign Service
participant Recipient as Mail Recipient
Op->>CS: GET /campaigns/{id}/mails/print
CS-->>Op: QR codes (Base64 PNG, one per contact)
Op->>Recipient: Physical letter with QR code
Recipient->>CS: Scan QR → GET responseUrl?trackId=X&campaignId=Y
CS->>CS: POST /campaigns/{id}/responses\n(status=RESPONDED)
Op->>CS: POST /campaigns/{id}/mails/{trackId}/status\n(status=SEND)
Op->>CS: GET /campaigns/{id}/mails/metrics
CS-->>Op: { sent, responded, responseRate% }