# Message priority separates transactional from marketing

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

## Why it matters

A single FIFO email queue shared by transactional and marketing sends creates a hidden contention point. A bulk marketing campaign pushing 100,000 jobs can delay a password-reset email by 30 minutes — precisely the window where a user is waiting at the browser. CWE-400 covers uncontrolled resource consumption; sharing a queue is the mechanism by which a marketing send consumes the delivery window for time-sensitive transactional messages. ISO-25010:2011 performance-efficiency.resource-utilization requires that resources are allocated in proportion to priority.

## Severity rationale

Low because the failure mode is latency degradation rather than data loss or security breach, and affects only deployments running simultaneous marketing and transactional sends.

## Remediation

Define separate BullMQ queues and workers for transactional and marketing sends in `lib/queues.ts`:

```ts
export const transactionalQueue = new Queue('email:transactional', { connection })
export const marketingQueue = new Queue('email:marketing', { connection })

const transactionalWorker = new Worker('email:transactional', processEmail, {
  connection,
  concurrency: 10
})

const marketingWorker = new Worker('email:marketing', processEmail, {
  connection,
  concurrency: 2,
  limiter: { max: 100, duration: 1000 }
})
```

Route sends at the call site based on the email type, not a shared queue name.

## Detection

- **ID:** `priority-queues`
- **Severity:** `low`
- **What to look for:** Count the number of distinct email queues in the codebase. Enumerate all email types (transactional: password resets, account verification, purchase confirmations; marketing: campaigns, newsletters, re-engagement) and classify each as transactional or marketing. Determine whether they share a single queue or are separated.
- **Pass criteria:** Transactional and marketing sends are in at least 2 separate queues (or use a priority queue where transactional jobs have higher priority). A large marketing blast of 100,000 emails does not delay password reset emails by more than 30 seconds. Do NOT pass when a single undifferentiated queue handles both types.
- **Fail criteria:** All email jobs share a single FIFO queue. A marketing campaign sending 100,000 emails blocks password reset emails until the batch completes.
- **Skip (N/A) when:** The application only sends one type of email (e.g., only transactional, or only marketing — not both) — confirmed by reviewing all email send paths.
- **Detail on fail:** `"Single 'email' queue used for both transactional and marketing sends — large campaigns delay time-sensitive system emails"` or `"No queue priority configured — password reset emails enter behind bulk campaign jobs"`
- **Remediation:** Use separate queues for different email types:

  ```ts
  // lib/queues.ts
  export const transactionalQueue = new Queue('email:transactional', { connection })
  export const marketingQueue = new Queue('email:marketing', { connection })

  // Workers — more concurrency on transactional, throttled marketing
  const transactionalWorker = new Worker('email:transactional', processEmail, {
    connection,
    concurrency: 10
  })

  const marketingWorker = new Worker('email:marketing', processEmail, {
    connection,
    concurrency: 2,           // Throttle to respect ESP rate limits
    limiter: { max: 100, duration: 1000 } // 100 sends/sec max
  })
  ```

## External references

- cwe CWE-400
- iso-25010 performance-efficiency.resource-utilization

Taxons: cost-efficiency, error-resilience

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