Marketing Campaign Module - AI Handoff Plan
Date: 2026-05-19
This file is written for another developer/AI agent to continue the Marketing Campaign module without re-auditing from zero.
0. Goal
Build the Marketing Campaign flow required by the product docs:
- Admin creates or chooses a customer audience/segment.
- Admin previews recipient counts and channel reachability.
- Admin chooses SMS, Email, or Push for MVP.
- Admin chooses a matching message template.
- Admin sends now or schedules sending.
- Admin can inspect per-recipient delivery logs.
The current implementation has a basic skeleton, but it is not production/UAT-ready.
Status Update - 2026-05-22
Implemented in the current working tree:
- Backend safety guard blocks empty campaign audiences before send/schedule.
- Template validation now checks enabled state and channel match.
- Audience preview returns total, sample customers, and SMS/Email/Push reachability counts.
- Admin
/lotus/campaignsis now a create wizard with campaign info, audience builder, channel/template selection, preview, send-now, and schedule controls. - Admin
/lotus/audiencesexists for saved customer audiences backed by Vendure customer groups. - Admin
/lotus/message-templatessupports SMS, Email, and Push templates, with future channels disabled. - Campaign detail route shows metadata, recipient status stats, and per-recipient delivery logs.
- SMS, Email, and Expo Push campaign delivery paths are wired through
LotusCampaignService. - Scheduled campaign automation is registered through the Vendure scheduler and
LotusAutomationService.
Still important before production:
- Run real provider UAT with configured ESMS, Brevo, and Expo Push credentials.
- Add explicit SMS/email marketing consent fields when the customer schema supports them.
- Provider Settings still do not drive all campaign providers; campaign sending reads environment variables.
- Large campaign sending is still synchronous and capped; move to a queue/batch job before high-volume use.
- Deploy/build the dashboard bundle when releasing; generated
html/output is no longer tracked in git.
Status Update - 2026-05-24
Docs/code audit update:
- Backend/admin source has been pushed to
goldenlotus-backenddevelopthrough commitae85d20. - Dashboard build output
html/is now untracked/ignored; do not commit generated static dashboard files. Deploy/build should regenerate them in Docker or CI. - Push delivery is no longer Expo-only. Backend uses unified
LotusPushService: Firebase for native FCM tokens and Expo for Expo push tokens. - Mobile device registration now exists and can send Expo token or Android native
FCM token depending on
EXPO_PUBLIC_PUSH_TOKEN_PROVIDER. - New current-state docs:
mobileApp/docs/PROJECT_STATUS_AUDIT_2026-05-24.mdgoldenlotus-backend/docs/PROJECT_STATUS_AUDIT_2026-05-24.md
Still important before production:
- Real provider UAT for SMS, Email and Push credentials.
- Consent policy finalization for SMS/email/push.
- Queue/batch design before high-volume campaigns.
- Retry operation for failed recipients remains a hardening item.
1. Product Source Of Truth
Relevant requirement docs:
mobileApp/docs/Phu luc 01. PSA 06.04.26 App membership.txtmobileApp/docs/PROJECT_TRACKING.mdFULL_PROJECT_AUDIT.md
Key product requirements from the docs:
- Create saved message forms/templates: push notification and SMS.
- Create campaign: send immediately or schedule.
- Query customer audience by customer filters, or create an audience directly in campaign.
- Sending channels: SMS, Email, Push notification.
- Future channels: Zalo, WhatsApp, Viber, Kakao.
- For multi-channel campaigns, preview how many customers have phone/email/push token/etc.
- Customer filters should support point expiry, birthday, and custom groups.
2. Current Implementation Inventory
Backend campaign files:
goldenlotus-backend/docker/vendure_backend/apps/server/src/plugins/golden-lotus/entities/lotus-campaign.entity.tsgoldenlotus-backend/docker/vendure_backend/apps/server/src/plugins/golden-lotus/entities/lotus-campaign-recipient.entity.tsgoldenlotus-backend/docker/vendure_backend/apps/server/src/plugins/golden-lotus/entities/lotus-message-template.entity.tsgoldenlotus-backend/docker/vendure_backend/apps/server/src/plugins/golden-lotus/entities/lotus-campaign-channel.tsgoldenlotus-backend/docker/vendure_backend/apps/server/src/plugins/golden-lotus/entities/lotus-campaign-status.tsgoldenlotus-backend/docker/vendure_backend/apps/server/src/plugins/golden-lotus/entities/lotus-campaign-recipient-status.tsgoldenlotus-backend/docker/vendure_backend/apps/server/src/plugins/golden-lotus/services/lotus-campaign.service.tsgoldenlotus-backend/docker/vendure_backend/apps/server/src/plugins/golden-lotus/api/lotus-campaign-admin.resolver.tsgoldenlotus-backend/docker/vendure_backend/apps/server/src/plugins/golden-lotus/api/api-extensions.ts
Backend provider/notification files:
goldenlotus-backend/docker/vendure_backend/apps/server/src/plugins/golden-lotus/services/notifications/lotus-esms.service.tsgoldenlotus-backend/docker/vendure_backend/apps/server/src/plugins/golden-lotus/services/notifications/lotus-expo-push.service.tsgoldenlotus-backend/docker/vendure_backend/apps/server/src/plugins/golden-lotus/services/lotus-notification.service.tsgoldenlotus-backend/docker/vendure_backend/apps/server/src/plugins/golden-lotus/services/lotus-provider-config.service.tsgoldenlotus-backend/docker/vendure_backend/apps/server/src/plugins/golden-lotus/services/lotus-automation.service.ts
Admin dashboard files:
goldenlotus-backend/packages/dashboard/src/app/golden-lotus/routes/lotus-campaigns.tsxgoldenlotus-backend/packages/dashboard/src/app/golden-lotus/routes/lotus-message-templates.tsxgoldenlotus-backend/packages/dashboard/src/app/golden-lotus/routes/lotus-provider-settings.tsxgoldenlotus-backend/packages/dashboard/src/app/golden-lotus/routes/lotus-notifications.tsxgoldenlotus-backend/packages/dashboard/src/app/golden-lotus/index.tsx
3. Current State And Gaps
What already exists
- Campaign entity, recipient entity, message template entity.
- Admin API for:
lotusMessageTemplatescreateLotusMessageTemplateupdateLotusMessageTemplatelotusCampaignslotusCampaignlotusCampaignPreviewRecipientscreateLotusCampaignupdateLotusCampaignsendLotusCampaignNowscheduleLotusCampaigncancelLotusCampaign
- Admin screen
/lotus/message-templates. - Admin screen
/lotus/campaigns. - SMS send-now path using eSMS if
ESMS_*env vars are configured. - Expo push service exists for notification sending, but is not wired into campaign sending.
- Email OTP/SMTP logic exists elsewhere, but campaign email delivery is not wired yet.
Critical gaps
P0 gap: empty segment currently defaults to latest customers.
In lotus-campaign.service.ts, resolveCustomers() falls back to:
return repo.find({ take: 500, order: { createdAt: 'DESC' } as any });
Impact: a campaign with empty segmentDefinition can send to up to 500 recent customers. This must be blocked before UAT.
Other gaps:
- Admin campaign create form sends
segmentDefinition: {}. - No audience/segment builder UI.
- No saved customer segments.
- No customer group selection in campaign UI.
- No preview counts by channel reachability.
- Admin lets user choose
EMAIL,PUSH,ZALO,WHATSAPP,VIBER,KAKAO, but backend only sends SMS. - Scheduled campaigns are stored as
SCHEDULED, but no worker/job sends them when due. - Provider Settings stores config JSON, but eSMS campaign sending reads env vars, not DB provider config.
- No campaign detail page.
- No per-recipient log table in admin.
- No consent/opt-in checks for SMS/email marketing.
- No batching/job queue for large campaigns.
- No idempotency guard against duplicate send.
4. Target MVP Scope
MVP should support:
- Single-channel campaign:
SMS,EMAIL, orPUSH. - Saved or inline segment definition.
- Preview before send.
- Send now.
- Schedule send.
- Per-recipient log.
- Strict safety guards.
Do not implement Zalo/WhatsApp/Viber/Kakao yet unless explicitly requested. Keep those channels hidden or disabled in admin until providers are implemented.
5. Proposed Data Contract
Segment definition JSON
Use this shape for MVP:
{
"customerIds": [],
"customerGroupIds": [],
"filters": {
"hasPhone": false,
"hasEmail": false,
"hasPushToken": false,
"birthdayMonth": null,
"minPoints": null,
"maxPoints": null,
"membershipTierIds": [],
"provinceCode": null,
"wardCode": null,
"createdFrom": null,
"createdTo": null,
"pointExpiringBefore": null
}
}
Rules:
- Empty
segmentDefinitionis invalid for send. - At least one of
customerIds,customerGroupIds, or meaningfulfiltersmust be present. - Preview API can allow empty input only if UI is intentionally showing
0, but send must not.
Preview response
Add or extend preview response to include:
type LotusCampaignRecipientPreview {
totalItems: Int!
items: [Customer!]!
smsReachable: Int!
emailReachable: Int!
pushReachable: Int!
missingPhone: Int!
missingEmail: Int!
missingPushToken: Int!
}
If GraphQL type change is too large, create a new type:
type LotusCampaignAudiencePreview {
totalItems: Int!
sampleCustomers: [Customer!]!
smsReachable: Int!
emailReachable: Int!
pushReachable: Int!
missingPhone: Int!
missingEmail: Int!
missingPushToken: Int!
}
6. Implementation Tickets
P0-01: Block Empty Segment Send
Owner: Backend
Files:
lotus-campaign.service.ts
Work:
- Add
assertValidSegmentDefinition(segmentDefinition)beforesendNow()andschedule(). - Empty
{}should throw a clear error. - Remove fallback latest 500 customers from send path.
- Preview may still return a sample only if explicitly requested, but send must not.
Acceptance:
sendLotusCampaignNowfails with message likeCampaign must have an audience segment before sending.- No campaign can send to default 500 customers.
P0-02: Validate Template Channel Matches Campaign Channel
Owner: Backend
Files:
lotus-campaign.service.ts
Work:
- In
createCampaign()andupdateCampaign(), iftemplateIdis present, load template and verify:- template exists in same channel
- template enabled
- template.channel equals campaign.channel
Acceptance:
- SMS campaign cannot use EMAIL template.
- Disabled template cannot be used for sending.
P0-03: Disable Unsupported Channels In Admin
Owner: Admin FE
Files:
lotus-campaigns.tsxlotus-message-templates.tsx
Work:
- MVP channels visible/enabled:
SMS,EMAIL,PUSH. - Hide or show disabled states for
ZALO,WHATSAPP,VIBER,KAKAO. - If backend does not yet support Email/Push, keep those disabled until tickets P3-02/P3-03 are complete.
Acceptance:
- Admin cannot create a campaign for unsupported provider by accident.
P1-01: Build Segment Resolver
Owner: Backend
Files:
lotus-campaign.service.ts- possibly
api-extensions.ts
Work:
- Extend
resolveCustomers()to support:customerIdscustomerGroupIdshasPhonehasEmailhasPushTokenbirthdayMonthminPointsmaxPointsmembershipTierIdsprovinceCodewardCodecreatedFromcreatedTopointExpiringBefore
- Use structured query builder/repository methods, not string concatenation.
- Keep result limits explicit.
Acceptance:
- Segment preview returns customers matching filters.
- A segment with
hasPhone: trueexcludes customers without phone. - A segment with
hasEmail: trueexcludes customers without email.
P1-02: Add Audience Preview Counts
Owner: Backend
Files:
lotus-campaign.service.tslotus-campaign-admin.resolver.tsapi-extensions.ts
Work:
- Preview should return:
- total customers
- sample customers
- smsReachable
- emailReachable
- pushReachable
- missingPhone
- missingEmail
- missingPushToken
- Push reachability uses
LotusCustomerDevicewith enabled push token.
Acceptance:
- Admin can show how many recipients can receive each channel before sending.
P1-03: Segment Builder UI
Owner: Admin FE
Files:
lotus-campaigns.tsx- optionally new component
components/campaign-segment-builder.tsx
Work:
- Replace
segmentDefinition: {}with real form state. - Add controls:
- customer group selector
- phone/email/push checkboxes
- birthday month select
- point min/max
- membership tier select
- province/ward select if data is available
- created date range
- Add
Preview audiencebutton. - Show preview counts.
Acceptance:
- Create campaign saves selected audience definition.
- The campaign list/detail shows a readable summary of the audience.
P2-01: Template Validation And UX
Owner: Backend + Admin FE
Files:
lotus-message-templates.tsxlotus-campaign.service.ts
Work:
- SMS template: body required.
- Email template: subject and body required.
- Push template: subject can map to push title, body maps to push body.
- Add helper text for supported variables:
{{firstName}}{{lastName}}{{fullName}}{{emailAddress}}{{phoneNumber}}{{customerId}}{{campaignCode}}{{campaignName}}
Acceptance:
- Admin cannot create invalid template for its channel.
- Admin knows which variables are supported.
P2-02: Template Render Preview
Owner: Backend + Admin FE
Work:
- Add API to render template using a sample customer.
- Admin can preview rendered SMS/email/push content before save/send.
Acceptance:
- Given body
Hello {{fullName}}, preview shows actual customer name.
P3-01: Harden SMS Campaign
Owner: Backend
Files:
lotus-campaign.service.tslotus-esms.service.ts
Work:
- Keep eSMS sending.
- Skip recipient without phone.
- Record per-recipient status.
- Add idempotency: if a recipient is already
SENT, do not send again unless explicit retry. - Add batch size or job queue for large sends.
Acceptance:
- SMS campaign sends only reachable customers.
- Re-clicking send does not duplicate already sent messages.
P3-02: Email Campaign Delivery
Owner: Backend
Files:
- Create
services/notifications/lotus-campaign-email.service.tsor similar. - Reuse SMTP/Brevo patterns from OTP email service if possible.
- Wire into
LotusCampaignService.
Work:
- Send email campaign for
EMAILchannel. - Subject from template subject.
- Body from template body.
- Skip recipient without email.
- Store provider message ID/error where available.
Acceptance:
- EMAIL campaign sends real email in configured environment.
- Recipients show
SENT,FAILED, orSKIPPED.
P3-03: Push Campaign Delivery
Owner: Backend
Files:
lotus-campaign.service.tslotus-notification.service.tslotus-expo-push.service.ts
Work:
- For
PUSH, create app inbox notification and send Expo push. - Respect
pushNotificationsEnabled. - Skip customers without device push token.
- Link type can default to
NONE, later support article/event/voucher.
Acceptance:
- PUSH campaign creates notifications visible in app inbox.
- Push token customers receive Expo push.
P4-01: Campaign Create Wizard
Owner: Admin FE
Files:
lotus-campaigns.tsx- Prefer extracting components.
Steps:
- Campaign info: code, name.
- Audience: select/build segment.
- Channel/template.
- Preview and confirm.
Acceptance:
- Admin cannot reach final send step without valid audience and matching template.
P4-02: Campaign Detail Page
Owner: Admin FE
Files:
- New route
lotus-campaigns/$idor equivalent dashboard route. - Update
golden-lotus/index.tsx.
Content:
- Campaign metadata.
- Segment summary.
- Status/timing.
- Template.
- Recipient stats.
- Recipient table.
Acceptance:
- Clicking a campaign opens detail page.
- User can inspect recipient errors.
P4-03: Recipient Log Table
Owner: Backend + Admin FE
Work:
- Add paginated query for recipients if current relation loading is too heavy.
- Table columns:
- customer
- phone
- status
- providerMessageId
- errorMessage
- sentAt
Acceptance:
- Admin can filter by
SENT,FAILED,SKIPPED,PENDING.
P5-01: Scheduled Campaign Worker
Owner: Backend
Files:
lotus-automation.service.tsservices/tasks/*lotus-campaign.service.ts
Work:
- Add scheduled task that scans due campaigns:
- status
SCHEDULED scheduledAt <= now
- status
- Lock campaign by setting status
SENDINGbefore sending. - Then call delivery logic.
Acceptance:
- A campaign scheduled for a past/near-future time sends automatically.
- Two worker processes do not send the same campaign twice.
P5-02: Retry Failed Recipients
Owner: Backend + Admin FE
Work:
- Add mutation
retryLotusCampaignFailedRecipients(id: ID!). - Only retry
FAILEDrecipients by default. - Optionally include
SKIPPEDafter data is fixed.
Acceptance:
- Retry sends only failed recipients, not all recipients.
7. Suggested Branches And Commits
Recommended branches:
- Backend:
feature/lotus-campaign-audience-delivery - Dashboard/admin: same branch if monorepo, or commit separately if repo policy requires.
Suggested commits:
fix: block empty lotus campaign audiencefeat: add lotus campaign audience previewfeat: add campaign segment builderfeat: support lotus email campaignsfeat: support lotus push campaignsfeat: run scheduled lotus campaigns
8. QA Test Matrix
Safety
- Create campaign without audience, click send: must fail.
- Create campaign with disabled template, click send: must fail.
- Create SMS campaign with EMAIL template: must fail.
Segment
- Segment by explicit customer IDs.
- Segment by customer group.
- Segment hasPhone=true.
- Segment hasEmail=true.
- Segment hasPushToken=true.
- Birthday month segment.
- Point range segment.
- Membership tier segment.
Delivery
- SMS customer with phone: SENT.
- SMS customer without phone: SKIPPED.
- Email customer with email: SENT.
- Email customer without email: SKIPPED.
- Push customer with push token and enabled preference: SENT.
- Push customer without token: SKIPPED.
Schedule
- Schedule campaign in future: status SCHEDULED.
- Worker reaches scheduledAt: status SENDING then SENT/FAILED.
- Cancel before scheduledAt: worker does not send.
Idempotency
- Click Send twice: already SENT recipients are not sent again.
- Retry failed: only FAILED recipients are retried.
9. Verification Commands
Run backend build:
cd /home/root1/Document/Lotus-Spa-backend/goldenlotus-backend/docker/vendure_backend
corepack pnpm build
Run dashboard build if available from repo root:
cd /home/root1/Document/Lotus-Spa-backend/goldenlotus-backend
corepack pnpm --filter dashboard build
Rebuild local backend Docker after backend API changes:
cd /home/root1/Document/Lotus-Spa-backend/goldenlotus-backend/docker
docker compose -f docker-compose.yml build server worker
docker compose -f docker-compose.yml up -d server worker
Check local health:
curl -s http://127.0.0.1:38102/health
10. AI Agent Prompt To Use
Use this prompt for the AI agent doing implementation:
You are continuing the Golden Lotus Marketing Campaign module.
Read MARKETING_CAMPAIGN_AI_HANDOFF.md first.
Do not rebuild from scratch.
Work in small commits by phase.
Start with P0 safety:
1. Block empty campaign segment from send/schedule.
2. Validate campaign template channel matches campaign channel.
3. Disable unsupported admin campaign channels until delivery is implemented.
Use existing patterns in:
- goldenlotus-backend/docker/vendure_backend/apps/server/src/plugins/golden-lotus/services/lotus-campaign.service.ts
- goldenlotus-backend/packages/dashboard/src/app/golden-lotus/routes/lotus-campaigns.tsx
- goldenlotus-backend/packages/dashboard/src/app/golden-lotus/routes/lotus-message-templates.tsx
After each phase, run backend build and list changed files.
Do not change unrelated modules.
Do not remove existing entity fields unless a migration/compatibility plan is included.
11. Final Definition Of Done
The module is acceptable for UAT only when:
- Empty audience cannot send.
- Audience builder exists.
- Preview counts are shown before send.
- SMS, Email, Push have real send paths or are explicitly disabled.
- Schedule sends automatically.
- Recipient status and errors are visible in admin.
- Provider status is visible before sending.
- QA test matrix above passes.