B2B sequences that fire at 3am local time or Saturday afternoon land in the recipient's inbox alongside spam and weekend cleanup, tanking open rates and conversion. Without a business-hours gate in the scheduler, computed send timestamps push cold outreach outside the 9-5 Monday-Friday window when decision-makers actually read email, degrading user experience for both senders configuring campaigns and recipients receiving off-hours pings that signal low-effort automation.
Low because the impact is reduced engagement and sender reputation drift, not data loss or outages.
Add a send_in_business_hours flag to the sequence schema and gate the step scheduler on it. Before calling scheduleStep(sendAt), route the timestamp through an advanceToBusinessHours(dt, timezone) helper that checks day-of-week (skip 0=Sunday, 6=Saturday) and hour (clamp to 9-17), advancing to the next 9am weekday when outside the window. See src/lib/scheduler/business-hours.ts:
if (day === 6) local.setDate(local.getDate() + 2)
if (hour >= 17) { local.setDate(local.getDate() + 1); local.setHours(9, 0, 0, 0) }
ID: campaign-orchestration-sequencing.cadence-spacing.business-hours
Severity: low
What to look for: Check whether sequences can be configured to send only during business hours (e.g., 9am-5pm Monday-Friday). This is distinct from timezone support — it means the system can delay a send that falls on a weekend or outside working hours until the next business-hours window. Look for: a send_in_business_hours flag on sequences or steps, logic that checks if a computed send time falls within business hours before scheduling, and logic that advances the timestamp to the next business-hours window if it falls outside.
Pass criteria: Sequences have a business-hours option. When enabled, sends scheduled outside 9am-5pm Mon-Fri are deferred to the start of the next business day. Count the enforcement points and enumerate all day/hour checks in the scheduler — at least 2 boundary conditions must be checked (start hour and end hour).
Fail criteria: No business-hours setting. All sends are scheduled for the computed time regardless of day or hour. A configuration flag with no enforcement logic does not count as pass.
Skip (N/A) when: The product sends to consumers (B2C) rather than businesses, where business-hours scheduling is not applicable.
Detail on fail: "No business-hours option — sequences always send at the computed time regardless of day of week or hour" or "business_hours flag exists on sequence but no enforcement logic found in step scheduler"
Remediation: Add a business-hours advancement function:
function advanceToBusinessHours(dt: Date, timezone: string): Date {
const { zonedTimeToUtc, utcToZonedTime } = require('date-fns-tz')
let local = utcToZonedTime(dt, timezone)
const day = local.getDay() // 0=Sun, 6=Sat
const hour = local.getHours()
if (day === 0) local.setDate(local.getDate() + 1) // Sunday → Monday
if (day === 6) local.setDate(local.getDate() + 2) // Saturday → Monday
if (hour < 9) local.setHours(9, 0, 0, 0)
if (hour >= 17) { local.setDate(local.getDate() + 1); local.setHours(9, 0, 0, 0) }
// Re-check after advancement
const newDay = local.getDay()
if (newDay === 0) local.setDate(local.getDate() + 1)
if (newDay === 6) local.setDate(local.getDate() + 2)
return zonedTimeToUtc(local, timezone)
}