← All stories

BRANCH · ef-054-scheduled-messages

Scheduled messages

EF-054Persona: OrganizerRoots in: event-setup

An organizer schedules future guest messages, edits them before the cutoff, sees send-due fan-out happen, and can cancel before delivery begins. Tier-3 tightening: scheduled message administration now references ui-async-job-admin-page for the job list and tracker foundation.

Preconditions

Fixture event has an eligible audience, an empty audience, and scheduled mailings before, inside, and past the edit window.

Happy path

  1. Choose a send_at time.

    Date range picker is timezone-honest and refuses past send_at.

  2. Save schedule.

    The scheduled row shows recipient count, send time, and edit cutoff.

  3. Cancel or let due time pass.

    Cancel drains the queue; due send starts the async job tracker.

Failure modes

Send blocked on validation failure

Trigger: invalid send_at, unresolved token, or invalid audience.

Resolution: 400/409 blocks send before any email leaves.

Bounced recipient tracked and suppressed

Trigger: scheduled send bounces.

Resolution: bounce event is recorded and later scheduled sends skip the address.

Scheduled-message edit window

Trigger: edit is attempted after cutoff.

Resolution: 409 PAST_EDIT_WINDOW and stale row is not patched.

Retry on transient failure

Trigger: provider 5xx at due time.

Resolution: retry uses same Idempotency-Key and sent_count is one.

Idempotency-key on test-send

Trigger: test-send double-click while scheduling.

Resolution: one test email is produced.

Recipient resolution empty

Trigger: saved audience resolves to zero.

Resolution: 409 NO_RECIPIENTS and no queued scheduled message.

Token rendering fallback

Trigger: token becomes undefined when the schedule executes.

Resolution: fallback copy renders for every recipient.

Cancel scheduled before send

Trigger: organizer cancels before send-due.

Resolution: pending count decrements, sends refund, audit row records actor.

Past due edit rejected

Trigger: organizer opens row after send-due has passed.

Resolution: edit controls are disabled and API still returns 409.

Due-time fan-out starts once

Trigger: worker polls the same due scheduled message twice.

Resolution: lease/idempotency creates one async job.

Deployed-runtime gap

Trigger: deployed run on 2026-04-29 observed scheduled mailing create 409 NO_RECIPIENTS; the probe locks this in until the gap is closed.

Resolution: story keeps the documented 409 assertion until deployed audience resolution is proven.

Stable test attributes

Visibility teeth. Each attribute must be effectively visible when active.

scheduled-messages-pagePageScheduler
scheduled-message-date-pickerFormsend_at
scheduled-message-audience-pickerFormAudience
scheduled-message-editorFormMessage body
scheduled-message-save-ctaToolbarSchedule
scheduled-message-cancel-ctaRowCancel
scheduled-message-job-trackerPageDue fan-out
scheduled-message-warningPageValidation
scheduled-message-gap-panelPageRuntime gap

Agent test plan

- scheduled-messages-renders
- schedule-message
- cancel-scheduled-message
- deployed-runtime-gap