Scheduled Queue

Keeps scheduled sends visible: live countdowns, a last-chance window before the cancel cutoff, quiet-hour warnings and missed sends flagged.

StopsScheduled campaign sent at 3am

Installation

pnpm dlx shadcn@latest add https://bollard-ui.vercel.app/r/scheduled-queue.json

The files land in your project. They're yours to edit.

Usage

scheduled-queue-usage.tsx
import { hourIn, nextHourIn, ScheduledQueue, type ScheduledItem } from "@/components/ui/scheduled-queue"

<ScheduledQueue
  items={items}
  onCancel={(id) => (setLog(`Cancelled “${title(id)}”.`), drop(id))}
  onSendNow={(id) => (setLog(`Sent “${title(id)}”.`), drop(id))}
  onReschedule={(id, at) => {
    setLog(`Moved “${title(id)}”.`)
    setItems((list) => list?.map((i) => (i.id === id ? { ...i, at } : i)))
  }}
/>

What it catches

  • Time left before each send, and the last-chance window before cutoff
  • Sends locked in by the provider, with cancel disabled
  • Sends landing in quiet hours (default 22:00–07:00) in the audience's zone, with a move-to-9:00 fix
  • Missed or overdue sends, with Send now
  • Reschedule times in the past

Props

ScheduledQueue props
PropTypeDefault
items* (required)

Scheduled sends to list.

ScheduledItem[]—
onCancel* (required)

Cancels a send. May return a Promise; a rejection shows an error.

(id: string) => void | Promise<void>—
onReschedule* (required)

Moves a send to a new time. May return a Promise.

(id: string, at: Date) => void | Promise<void>—
onSendNow

Offered on items whose time has passed without sending.

(id: string) => void—
lastChance

How long before the cancel cutoff a row turns into a last-chance warning, in ms.

number5 * 60_000
cutoff

Items lock this many ms before sending (the provider is preparing them) and can no longer be cancelled.

number60_000
quietHours

Quiet hours in the item's time zone, e.g. 22 → 7.

{ start: number; end: number }{ start: 22, end: 7 }
fixHour

The hour the one-tap quiet-hours fix moves an item to.

number9
locale

Locale for times and countdowns.

string—
lastChanceText

Templates: `{left}`, `{time}`, `{zone}`, `{ago}` are filled in.

string"Last chance · {left} left to cancel"
lockedText

Shown once a send can no longer be cancelled.

string"Locked in · sending {left}"
sendingText

Shown while a send is going out.

string"Going out now"
overdueText

Shown for a send that missed its time.

string"Was due {ago} and hasn't gone out"
quietText

Warning for a send landing in quiet hours.

string"Lands at {time} in {zone}"
fixLabel

Label of the move-to-a-sane-hour fix.

string"Move to {time}"
sendNowLabel

Label of the Send now button.

string"Send now"
rescheduleLabel

Label of the Reschedule button.

string"Reschedule"
cancelLabel

Label of the Cancel button.

string"Cancel"
saveLabel

Label of the save-new-time button.

string"Save"
pastWarning

Shown when a new time is in the past.

string"That time has already passed."
emptyText

Shown when nothing is scheduled.

string"Nothing scheduled."
countdownText

Countdown chip; `{time}` is filled in ("in {time}").

string"in {time}"
missedLabel

Chip on an item whose time passed without sending.

string"Missed"
localZoneText

Zone name when an item has no `timeZone`.

string"your time zone"
newTimeLabel

Accessible name of the reschedule field; `{title}` is filled in.

string"New time for {title}"
lastChanceAnnouncement

Announced once when a row enters its last-chance window or locks; `{title}` is filled in.

string"Last chance to cancel {title}."
lockedAnnouncement

Announced when a send locks in.

string"{title} is locked in and can no longer be cancelled."

* required. Also accepts the native props of <ul> (className, aria-*, data-*…), passed through to it.

Accessibility

  • Last-chance and locked changes announced in a polite live region, not every tick
  • Reschedule input has an aria-label, autoFocus, aria-invalid for past times, and Escape to cancel
  • Errors use role=alert; locked rows disable Cancel/Reschedule with a lock icon and text
  • Row highlight animation is skipped under reduced motion

Only what the source does. Test with your own screen reader and content before shipping.

Limitations

This is a UX guard rail. Enforce on the server: Enforce the cancel cutoff and reject past send times on the server; the provider's lock is the source of truth.

  • Quiet hours are judged in the zone you give each item; mixed audiences need splitting
  • Cutoff and lock state are as you configure them, not read from the provider
  • Countdowns use the device clock

What it stops

Scheduled campaign sent at 3am

Also known as

schedule send · scheduled posts · campaign · newsletter · queue · cron · send later · whatsapp broadcast

Field type

Dependencies

Pairs well with