Bulk Send Guard

States who an SMS, WhatsApp or email blast will reach, who is excluded and who is in quiet hours, before an explicit confirm sends it.

StopsBulk blast sent at 2 AM

Installation

pnpm dlx shadcn@latest add https://bollard-ui.vercel.app/r/bulk-send-guard.json

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

What it catches

  • Opted-out, STOP and unreachable recipients, counted and left out
  • Recipients in quiet hours in their own IANA time zone (default 21:00–10:00 for SMS and WhatsApp)
  • The cost of the send
  • A send much bigger than the last one, which requires typing the count
  • Accidental double-click sends, via a separate confirm step

Props

BulkSendGuard props
PropTypeDefault
channel* (required)

Which channel the blast goes out on; sets the default quiet hours.

BulkChannel—
audience* (required)

Who is on the list, who is excluded, and their time zones.

BulkAudience—
costPerMessage

Cost of one message (one part, for SMS). Leave out to hide the estimate.

number—
segments

SMS parts per message.

number1
currency

ISO 4217 code for the cost. Defaults to INR.

string"INR"
locale

BCP 47 locale for numbers and money. Defaults to en-IN.

string"en-IN"
quietHours

Quiet hours in each recipient's local time. Defaults to 21 → 10 for SMS and WhatsApp (TRAI's default for promotional SMS), none for email. Pass `null` for transactional or service messages, which TRAI allows at any hour.

QuietHours | null—
fallbackTimeZone

IANA time zone for recipients with no known zone. Defaults to UTC.

string"UTC"
lastSend

The previous blast, for the jump check.

{ count: number; at: Date | number | string }—
jumpFactor

Flag a send this many times bigger than the last one, and ask for the count to be typed.

number2
now

Pass for deterministic rendering; otherwise the clock is read after mount and every 30 s.

Date—
onSend* (required)

Sends it. Return a promise for a pending state; a rejection returns to the confirm step.

(plan: BulkSendPlan) => void | Promise<void>—
messages

Override any visible string.

Partial<BulkSendGuardMessages>—

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

Accessibility

  • Section labelled by the reach summary; quiet-hours options are a labelled fieldset
  • Focus moves to the confirm step and back to Review; Escape goes back
  • Errors use role="alert" and status updates role="status"
  • Icons are aria-hidden; every number is written in text

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 opt-outs, DND registries, quiet hours and send limits in the sending service, with idempotency so one blast can't go out twice.

  • Audience counts, exclusions and time zones come from your props
  • The default quiet hours follow TRAI's default for promotional SMS in India; other countries and channels have different rules
  • It does not send, schedule or hold messages; onSend does

Rules

Rules last verified:

Informational; check the official source. TCCCPR 2018 sets time bands 00:00–10:00 and 21:00–24:00 default OFF for promotional communication, matching the 21:00–10:00 default. Later TRAI amendments (2025) were not reviewed for changes to this default. Copied components don't update themselves, so re-check after rule changes (see updating a component).

What it stops

Bulk blast sent at 2 AM

Also known as

bulk sms · whatsapp broadcast · email campaign · newsletter · dnd · opt out · quiet hours · marketing

Field type

Dependencies

Pairs well with