Business hours sending option
Why it matters
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.
Severity rationale
Low because the impact is reduced engagement and sender reputation drift, not data loss or outages.
Remediation
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) }
Detection
-
ID:
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_hoursflag 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) }
Taxons
History
- 2026-04-18·v1.0.0·Initial import from campaign-orchestration-sequencing·automated