Skip to content

Campaign to_send not recalculated when lists are added/removed in draft status #2733

@ermoi

Description

@ermoi

Bug Description

When modifying the lists associated with a draft campaign (adding or removing lists), the to_send field is not recalculated. The to_send value is set only when the campaign is first created and remains static even if the actual number of subscribers changes due to list modifications.

Steps to Reproduce

  1. Create a new draft campaign
  2. Associate it with List A (e.g., 100 subscribers)
    • to_send is set to 100
  3. Save the draft
  4. Edit the draft campaign and add List B (e.g., 50 more subscribers)
  5. Check the campaign details

Expected Behavior

After adding List B:

  • to_send should be recalculated to 150 (100 + 50)
  • The campaign should reflect the actual number of recipients

Similarly, when removing a list:

  • to_send should decrease accordingly

Actual Behavior

  • to_send remains at the original value (100)
  • The database shows the correct list associations in campaign_lists
  • When the campaign is sent, it may send to more (or fewer) subscribers than indicated by to_send

Database Evidence

listmonk=# SELECT
  c.id,
  c.name,
  c.to_send as "to_send_actual",
  COUNT(DISTINCT sl.subscriber_id) as "suscriptores_reales"
FROM campaigns c
INNER JOIN campaign_lists cl ON c.id = cl.campaign_id
INNER JOIN subscriber_lists sl ON cl.list_id = sl.list_id
WHERE c.id in (9,29)  -- Reemplaza con ID de tu campaña
AND sl.status = 'confirmed'
AND c.status = 'draft'
GROUP BY c.id, c.name, c.to_send;
 id |                 name                  | to_send_actual | suscriptores_reales 
----+---------------------------------------+----------------+---------------------
 29 | Convocatoria BUSSINES ÁREA FITUR 2026 |            320 |                 310

Impact

  • User confusion: The UI shows incorrect recipient counts
  • Campaign planning: Users cannot trust the to_send number for estimation
  • Reporting: Analytics show "Sent: 150 / To send: 100" which is misleading
  • Resource allocation: Cannot properly estimate send time/load

Environment

  • Listmonk version: v5.1.0 (30846f8 2025-09-09)
  • Database: PostgreSQL
  • Deployment: Docker

Proposed Solution

Option 1: Recalculate on save (Recommended)

When a draft campaign is saved after modifying lists, recalculate to_send:

func (c *Campaign) RecalculateToSend() error {
    if c.Status != CampaignStatusDraft {
        return nil // Only for drafts
    }
    
    query := `
        SELECT COUNT(DISTINCT sl.subscriber_id)
        FROM campaign_lists cl
        INNER JOIN subscriber_lists sl ON cl.list_id = sl.list_id
        WHERE cl.campaign_id = $1
        AND sl.status = 'confirmed'
    `
    
    var count int
    err := db.QueryRow(query, c.ID).Scan(&count)
    if err != nil {
        return err
    }
    
    c.ToSend = count
    return c.Save()
}

Option 2: Calculate on-demand

Don't store to_send for drafts, calculate it when displaying:

  • Store to_send only when campaign starts
  • For drafts, show calculated count in real-time

Option 3: Show warning

If to_send doesn't match actual count, show warning in UI:

⚠️ Recipients changed since campaign was created. Expected: 150, Stored: 100

Current Workaround

Manually update in database:

UPDATE campaigns
SET to_send = (
    SELECT COUNT(DISTINCT sl.subscriber_id)
    FROM campaign_lists cl
    INNER JOIN subscriber_lists sl ON cl.list_id = sl.list_id
    WHERE cl.campaign_id = campaigns.id
    AND sl.status = 'confirmed'
)
WHERE id = 20 AND status = 'draft';

Related Issues

This may be related to:

  • List management not triggering campaign updates
  • Draft campaigns not being "live" objects
  • Missing event hooks when lists are modified

Additional Context

This becomes especially problematic when:

  • Adding/removing lists multiple times
  • Subscribers are added/removed from lists while campaign is draft
  • Using the "Start campaign" button with incorrect recipient count

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions