# Message ordering preserves campaign sequence

- **Pattern:** `ab-002456` (`sending-pipeline-infrastructure.queue-architecture.message-ordering`)
- **Severity:** low
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-002456
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-002456)

## Why it matters

When multiple workers process a drip sequence concurrently, the order emails land in a recipient's inbox is determined by worker timing and network latency, not the sequence design. Email 3 can arrive before email 2 if workers race. For onboarding sequences where each message references the previous one, out-of-order delivery breaks the narrative and confuses recipients. CWE-362 covers race conditions on shared resources — concurrent workers competing for sequence jobs are precisely that. ISO-25010:2011 reliability.fault-tolerance requires the system to behave predictably under concurrent execution.

## Severity rationale

Low because mis-ordered emails harm user experience but do not cause data loss, security exposure, or system failure.

## Remediation

Schedule sequence jobs with explicit millisecond-separated delays to guarantee ESP ordering, or use BullMQ `FlowProducer` for strict job chains:

```ts
const DAY = 24 * 60 * 60 * 1000
await emailQueue.add('seq:step-1', { campaignId, recipientId }, { delay: 0 })
await emailQueue.add('seq:step-2', { campaignId, recipientId }, { delay: 3 * DAY })
await emailQueue.add('seq:step-3', { campaignId, recipientId }, { delay: 7 * DAY })
```

For same-day sequences where delay alone is insufficient, use `FlowProducer` in `lib/flows.ts` to enforce strict parent-child dependency between sequence steps.

## Detection

- **ID:** `message-ordering`
- **Severity:** `low`
- **What to look for:** Enumerate all sequence-based email send paths (drip campaigns, onboarding sequences) where message order matters. For each, count the number of ordering enforcement mechanisms: delayed jobs with explicit timestamps, sequential job chains (FlowProducer), or per-recipient locking. Report the ratio of ordered paths to total sequence paths.
- **Pass criteria:** Emails within a sequence are either scheduled with explicit timestamps separated by at least 1 second (so delivery order is determined by the ESP scheduling, not worker concurrency), or processed in a guaranteed-ordered manner via job chains. Out-of-order sends are not possible for a given recipient.
- **Fail criteria:** Multiple workers process campaign jobs concurrently with no ordering guarantee. Email 3 could be processed and sent before email 2 due to queue starvation or worker timing.
- **Skip (N/A) when:** The project only sends one-off emails with no drip sequences or multi-step campaigns — confirmed by the absence of sequence or drip models.
- **Detail on fail:** `"Campaign sequence jobs processed by 5 concurrent workers with no ordering constraint — email 3 can arrive before email 2"` or `"Delayed jobs scheduled at the same timestamp — ESP delivery order not guaranteed"`
- **Remediation:** Use BullMQ job chains or delayed scheduling with per-recipient locking:

  ```ts
  // Schedule sequence jobs with explicit delays
  await emailQueue.add('sequence:step-1', { campaignId, recipientId }, { delay: 0 })
  await emailQueue.add('sequence:step-2', { campaignId, recipientId }, { delay: 3 * 24 * 60 * 60 * 1000 }) // 3 days
  await emailQueue.add('sequence:step-3', { campaignId, recipientId }, { delay: 7 * 24 * 60 * 60 * 1000 }) // 7 days

  // Or use BullMQ flows for strict sequential processing
  import { FlowProducer } from 'bullmq'

  const flow = new FlowProducer({ connection })
  await flow.add({
    name: 'sequence:step-3',
    queueName: 'email',
    data: { step: 3 },
    children: [{
      name: 'sequence:step-2',
      queueName: 'email',
      data: { step: 2 },
      children: [{ name: 'sequence:step-1', queueName: 'email', data: { step: 1 } }]
    }]
  })
  ```

---

## External references

- cwe CWE-362
- iso-25010 reliability.fault-tolerance

Taxons: data-integrity

HTML version: https://auditbuffet.com/patterns/ab-002456
