Scheduling all sends in server UTC means a "send at 9am" step reaches a contact in Tokyo at 9pm and a contact in London at 8am in summer, 9am in winter. Open rates for email campaigns vary by as much as 30% based on local time of delivery — sending into the wrong timezone is a measurable conversion loss, not a cosmetic issue. iso-25010:2011 functional-suitability.functional-correctness treats this as a functional accuracy defect: the system executes the correct action (sends the email) at the wrong time (server timezone rather than recipient timezone), producing a systematically degraded outcome for any globally distributed contact list.
Medium because server-timezone scheduling consistently delivers emails at suboptimal local times for non-local contacts, producing measurable open-rate and conversion losses.
Store a timezone field on contact records (IANA format, e.g. 'America/New_York') and use it to convert the step's preferred local send hour to a UTC timestamp before scheduling.
import { zonedTimeToUtc, utcToZonedTime } from 'date-fns-tz'
function computeSendTime(delayDays: number, preferredHour: number, contactTimezone: string): Date {
const now = new Date()
const nowInContactTz = utcToZonedTime(now, contactTimezone)
const targetInContactTz = new Date(nowInContactTz)
targetInContactTz.setDate(targetInContactTz.getDate() + delayDays)
targetInContactTz.setHours(preferredHour, 0, 0, 0)
return zonedTimeToUtc(targetInContactTz, contactTimezone)
}
Fall back to a sensible default timezone (e.g. 'America/New_York') when a contact has no timezone set — do not silently use server UTC. Libraries like date-fns-tz, Luxon, or dayjs with the timezone plugin all handle DST transitions correctly.
ID: campaign-orchestration-sequencing.cadence-spacing.timezone-aware-sending
Severity: medium
What to look for: Check whether send times account for the recipient's local timezone. When a sequence step is scheduled with a delay of "3 days at 9am," verify that "9am" means 9am in the contact's timezone, not the server's timezone. Look for: a timezone field on contact records, timezone-aware date calculation using libraries like date-fns-tz, Luxon, or dayjs with timezone plugin, or a reference to the contact's timezone when computing the scheduled send timestamp.
Pass criteria: Send times are computed in the contact's local timezone. A contact in Tokyo receives a "9am" step email at 9am Tokyo time, not 9am UTC. Count all timezone-related fields on the contact model and verify at least 1 is used in scheduling logic.
Fail criteria: All sends are scheduled in UTC or the server's timezone, ignoring recipient location. Contacts in different time zones all receive emails at the same absolute time.
Skip (N/A) when: The audience is entirely in one timezone, or the system does not support per-contact send time preferences.
Detail on fail: "Scheduled send time computed in UTC — contacts in all timezones receive emails at the same absolute time" or "No timezone field on contact records — timezone-aware scheduling not possible"
Remediation: Compute scheduled times using the contact's timezone:
import { zonedTimeToUtc, utcToZonedTime } from 'date-fns-tz'
function computeSendTime(delayDays: number, preferredHour: number, contactTimezone: string): Date {
const now = new Date()
const nowInContactTz = utcToZonedTime(now, contactTimezone)
// Set to preferred hour on the target day in contact's timezone
const targetInContactTz = new Date(nowInContactTz)
targetInContactTz.setDate(targetInContactTz.getDate() + delayDays)
targetInContactTz.setHours(preferredHour, 0, 0, 0)
// Convert back to UTC for storage and scheduling
return zonedTimeToUtc(targetInContactTz, contactTimezone)
}