An OOO auto-responder and a genuine human reply are structurally identical inbound emails. Without explicit OOO detection, the system treats every Auto-Submitted: auto-replied as a real engagement signal — permanently pausing sequences, creating rep notifications, and potentially marking the contact as 'replied' when they are simply on vacation. The inverse failure is equally bad: some implementations silently discard all inbound emails to avoid OOO noise, missing genuine replies in the process. iso-25010:2011 reliability.fault-tolerance requires that the system handle this expected abnormal input class explicitly, not by accident.
Medium because undetected OOO messages either incorrectly terminate sequences (false-positive reply) or create spurious rep notifications, degrading signal quality and operational trust.
Check IETF-standard headers before routing an inbound email to reply handling. The Auto-Submitted header is the authoritative signal; subject/body patterns catch non-compliant mail clients.
function isOutOfOffice(headers: Record<string, string>, subject: string, body: string): boolean {
if (headers['auto-submitted'] && headers['auto-submitted'] !== 'no') return true
if (headers['x-autoreply']) return true
const oooPatterns = [/out of office/i, /on vacation/i, /automatic reply/i, /auto-reply/i, /away from/i]
return oooPatterns.some(p => p.test(subject) || p.test(body.slice(0, 500)))
}
async function handleInboundEmail(email: InboundEmail) {
if (isOutOfOffice(email.headers, email.subject, email.body)) {
// Temporarily defer — do NOT mark as replied or notify reps
const returnDate = extractReturnDate(email.body) ?? addDays(new Date(), 7)
await deferEnrollmentUntil(email.contactId, returnDate)
return
}
await handleReply(email)
}
Check at least two signal types — Auto-Submitted header plus at least one subject/body pattern — to handle both compliant and non-compliant mail servers.
ID: campaign-orchestration-sequencing.reply-engagement.ooo-detection
Severity: medium
What to look for: When inbound email processing is in place, check whether the system distinguishes between genuine replies and auto-responders (out-of-office, vacation replies). OOO messages should not count as engagement (they should not advance a contact to a different branch or trigger rep notifications), but the sequence should be temporarily paused or the next step delayed until after the return date if detectable. Look for: pattern matching on Auto-Submitted headers, X-Autoreply headers, or subject line keywords (Re: Out of Office, "I'm out of office", "automatic reply"); and different handling paths for OOO versus real replies.
Pass criteria: Inbound email handler identifies OOO messages by headers or content patterns. OOO messages trigger a temporary pause (not a permanent reply exit) with a reschedule for after the return date, or are silently ignored without counting as engagement. Count the OOO detection signals checked (headers, subject patterns, body patterns) — at least 2 must be inspected.
Fail criteria: All inbound emails are treated identically regardless of whether they are genuine replies or OOO auto-responders. OOO messages either incorrectly pause the sequence permanently or incorrectly count as engagement signals.
Skip (N/A) when: Reply detection is not implemented.
Detail on fail: "All inbound emails processed as genuine replies — OOO auto-responses pause the sequence permanently and trigger rep notifications" or "No Auto-Submitted header inspection — no OOO detection"
Remediation: Check OOO headers before processing a reply:
function isOutOfOffice(headers: Record<string, string>, subject: string, body: string): boolean {
if (headers['auto-submitted'] && headers['auto-submitted'] !== 'no') return true
if (headers['x-autoreply']) return true
const oooPatterns = [/out of office/i, /on vacation/i, /automatic reply/i, /auto-reply/i, /away from/i]
return oooPatterns.some(p => p.test(subject) || p.test(body.slice(0, 500)))
}
async function handleInboundEmail(email: InboundEmail) {
if (isOutOfOffice(email.headers, email.subject, email.body)) {
// Silently pause or defer — do NOT count as engagement
await deferEnrollmentUntil(email.contactId, extractReturnDate(email.body) || addDays(new Date(), 7))
return
}
await handleReply(email)
}