# Reply detection implemented

- **Pattern:** `ab-000609` (`campaign-orchestration-sequencing.reply-engagement.reply-detection`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000609
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000609)

## Why it matters

A sequence that cannot detect replies is operationally blind — it continues sending follow-ups to contacts who have already engaged, replied, or expressed interest. The next scheduled step lands in the inbox of a prospect who replied two days ago asking for a demo, with no human having seen the reply and no system action taken. iso-25010:2011 reliability.fault-tolerance marks this as a reliability gap: the system lacks a mechanism to adapt its behavior to the most important signal a sequence can receive. In practice, the absence of reply detection turns outreach sequences into one-way blast channels that actively frustrate engaged prospects.

## Severity rationale

High because no reply detection means engaged contacts continue receiving automated outreach after responding, damaging the relationship at the highest-intent moment.

## Remediation

Implement inbound email handling via your ESP's webhook — SendGrid Inbound Parse, Postmark Inbound, or Mailgun Routes are all drop-in options. Register a POST handler at a stable path like `/api/webhooks/inbound-email`.

```ts
// POST /api/webhooks/inbound-email — SendGrid Inbound Parse example
export async function POST(request: Request) {
  const formData = await request.formData()
  const from    = formData.get('from') as string
  const to      = formData.get('to') as string
  const subject = formData.get('subject') as string
  const text    = formData.get('text') as string

  const contact = await db.contact.findFirst({ where: { email: extractEmail(from) } })
  if (!contact) return new Response('ok')

  await handleReply({ contactId: contact.id, from, to, subject, body: text })
  return new Response('ok')
}
```

If your ESP does not offer inbound routing, use IMAP polling on a dedicated reply-to mailbox (e.g., `replies@yourdomain.com`) as a fallback. The route must be actively receiving events — a placeholder route with no ESP configuration does not satisfy this check.

## Detection

- **ID:** `reply-detection`
- **Severity:** `high`
- **What to look for:** Check whether the system monitors for inbound replies to campaign emails. Effective methods include: IMAP polling (polling a dedicated reply-to mailbox), ESP inbound email webhooks (SendGrid inbound parse, Postmark inbound webhooks, Mailgun routes), or a third-party inbox service (Nylas, Gmail API with push notifications). Look for: IMAP connection setup, webhook handler routes for inbound email, or integration with a service that routes inbound emails to the application.
- **Pass criteria:** At least one reply detection mechanism is implemented and active. Inbound replies to campaign emails trigger an event in the application. Count all inbound email handler routes — at least 1 must be actively receiving inbound events. Report the count even on pass.
- **Fail criteria:** No reply detection found. The system sends emails but has no way to detect when a recipient replies.
- **Skip (N/A) when:** The sequences are one-way announcements with no expected replies, or replies are handled entirely outside the system (e.g., by a human mailbox with no integration).
- **Detail on fail:** `"No IMAP client, inbound webhook, or third-party inbox service found — replies are not detected"` or `"Using a no-reply@ sender address with no reply monitoring"`
- **Remediation:** Implement reply detection via your ESP's inbound webhook:

  ```ts
  // SendGrid inbound parse webhook
  // POST /api/webhooks/inbound-email

  export async function POST(request: Request) {
    const formData = await request.formData()
    const from = formData.get('from') as string
    const to = formData.get('to') as string
    const subject = formData.get('subject') as string
    const text = formData.get('text') as string

    // Find the contact by the from address
    const contact = await db.contact.findFirst({ where: { email: extractEmail(from) } })
    if (!contact) return new Response('ok')

    // Emit a reply event
    await handleReply({ contactId: contact.id, from, to, subject, body: text })
    return new Response('ok')
  }
  ```

## External references

- iso-25010 reliability.fault-tolerance

Taxons: error-resilience

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