Skip to content

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 CampaignCallRequestCallService.createCampaignCall()
Email Merge contact + templateDataEmailService.send()
WhatsApp Bulk WhatsAppTemplateMessageWhatsappService.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% }