Skip to main content

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:

  1. Admin creates or chooses a customer audience/segment.
  2. Admin previews recipient counts and channel reachability.
  3. Admin chooses SMS, Email, or Push for MVP.
  4. Admin chooses a matching message template.
  5. Admin sends now or schedules sending.
  6. 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/campaigns is now a create wizard with campaign info, audience builder, channel/template selection, preview, send-now, and schedule controls.
  • Admin /lotus/audiences exists for saved customer audiences backed by Vendure customer groups.
  • Admin /lotus/message-templates supports 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-backend develop through commit ae85d20.
  • 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.md
    • goldenlotus-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.txt
  • mobileApp/docs/PROJECT_TRACKING.md
  • FULL_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.ts
  • goldenlotus-backend/docker/vendure_backend/apps/server/src/plugins/golden-lotus/entities/lotus-campaign-recipient.entity.ts
  • goldenlotus-backend/docker/vendure_backend/apps/server/src/plugins/golden-lotus/entities/lotus-message-template.entity.ts
  • goldenlotus-backend/docker/vendure_backend/apps/server/src/plugins/golden-lotus/entities/lotus-campaign-channel.ts
  • goldenlotus-backend/docker/vendure_backend/apps/server/src/plugins/golden-lotus/entities/lotus-campaign-status.ts
  • goldenlotus-backend/docker/vendure_backend/apps/server/src/plugins/golden-lotus/entities/lotus-campaign-recipient-status.ts
  • goldenlotus-backend/docker/vendure_backend/apps/server/src/plugins/golden-lotus/services/lotus-campaign.service.ts
  • goldenlotus-backend/docker/vendure_backend/apps/server/src/plugins/golden-lotus/api/lotus-campaign-admin.resolver.ts
  • goldenlotus-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.ts
  • goldenlotus-backend/docker/vendure_backend/apps/server/src/plugins/golden-lotus/services/notifications/lotus-expo-push.service.ts
  • goldenlotus-backend/docker/vendure_backend/apps/server/src/plugins/golden-lotus/services/lotus-notification.service.ts
  • goldenlotus-backend/docker/vendure_backend/apps/server/src/plugins/golden-lotus/services/lotus-provider-config.service.ts
  • goldenlotus-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.tsx
  • goldenlotus-backend/packages/dashboard/src/app/golden-lotus/routes/lotus-message-templates.tsx
  • goldenlotus-backend/packages/dashboard/src/app/golden-lotus/routes/lotus-provider-settings.tsx
  • goldenlotus-backend/packages/dashboard/src/app/golden-lotus/routes/lotus-notifications.tsx
  • goldenlotus-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:
    • lotusMessageTemplates
    • createLotusMessageTemplate
    • updateLotusMessageTemplate
    • lotusCampaigns
    • lotusCampaign
    • lotusCampaignPreviewRecipients
    • createLotusCampaign
    • updateLotusCampaign
    • sendLotusCampaignNow
    • scheduleLotusCampaign
    • cancelLotusCampaign
  • 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, or PUSH.
  • 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 segmentDefinition is invalid for send.
  • At least one of customerIds, customerGroupIds, or meaningful filters must 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) before sendNow() and schedule().
  • 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:

  • sendLotusCampaignNow fails with message like Campaign 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() and updateCampaign(), if templateId is 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.tsx
  • lotus-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:
    • customerIds
    • customerGroupIds
    • hasPhone
    • hasEmail
    • hasPushToken
    • birthdayMonth
    • minPoints
    • maxPoints
    • membershipTierIds
    • provinceCode
    • wardCode
    • createdFrom
    • createdTo
    • pointExpiringBefore
  • Use structured query builder/repository methods, not string concatenation.
  • Keep result limits explicit.

Acceptance:

  • Segment preview returns customers matching filters.
  • A segment with hasPhone: true excludes customers without phone.
  • A segment with hasEmail: true excludes customers without email.

P1-02: Add Audience Preview Counts

Owner: Backend

Files:

  • lotus-campaign.service.ts
  • lotus-campaign-admin.resolver.ts
  • api-extensions.ts

Work:

  • Preview should return:
    • total customers
    • sample customers
    • smsReachable
    • emailReachable
    • pushReachable
    • missingPhone
    • missingEmail
    • missingPushToken
  • Push reachability uses LotusCustomerDevice with 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 audience button.
  • 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.tsx
  • lotus-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.ts
  • lotus-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.ts or similar.
  • Reuse SMTP/Brevo patterns from OTP email service if possible.
  • Wire into LotusCampaignService.

Work:

  • Send email campaign for EMAIL channel.
  • 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, or SKIPPED.

P3-03: Push Campaign Delivery

Owner: Backend

Files:

  • lotus-campaign.service.ts
  • lotus-notification.service.ts
  • lotus-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:

  1. Campaign info: code, name.
  2. Audience: select/build segment.
  3. Channel/template.
  4. 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/$id or 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
    • email
    • status
    • providerMessageId
    • errorMessage
    • sentAt

Acceptance:

  • Admin can filter by SENT, FAILED, SKIPPED, PENDING.

P5-01: Scheduled Campaign Worker

Owner: Backend

Files:

  • lotus-automation.service.ts
  • services/tasks/*
  • lotus-campaign.service.ts

Work:

  • Add scheduled task that scans due campaigns:
    • status SCHEDULED
    • scheduledAt <= now
  • Lock campaign by setting status SENDING before 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 FAILED recipients by default.
  • Optionally include SKIPPED after 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:

  1. fix: block empty lotus campaign audience
  2. feat: add lotus campaign audience preview
  3. feat: add campaign segment builder
  4. feat: support lotus email campaigns
  5. feat: support lotus push campaigns
  6. feat: 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.