← All stories

BRANCH · ef-049-resend-invitations

Resend invitations and confirmations

EF-049Persona: OrganizerRoots in: event-setup

An organizer resends invitations or confirmations when a guest did not receive email, changed address, or needs a fresh copy. Voyage has a single-guest resend invitation endpoint at workers/events/src/routes/guests.ts:782; this branch captures that working path and exposes the missing bulk resend, confirmation resend, timeframe exclusions, and ticket-block-specific resend parity.

Preconditions

Organizer can access the event guest list and fixture guests include invited, confirmed, bounced, and recently-contacted states.

Happy path / Lifecycle

  1. Open a guest or select rows in the guest list.

    Single resend is available from the row actions; bulk resend appears only when selected guests are eligible.

  2. Choose invitation or confirmation resend.

    The dialog previews recipients, exclusions, message type, and the email job progress.

  3. Send.

    The single-guest resend endpoint queues the invitation and updates last-contacted/email interaction state.

Failure modes

Permission denied at the right boundary

Trigger: viewer/support attempts resend.

Resolution: 403, no message is queued, and recipient details are not exposed.

Cross-tenant isolation

Trigger: tenant A user guesses tenant B guest id.

Resolution: 404 masks guest existence and sends nothing.

Soft-delete leaves audit trail

Trigger: organizer removes a scheduled resend from queue.

Resolution: resend job is cancelled/tombstoned with audit row and prior queued state.

Archive vs delete distinction

Trigger: event or guest is archived.

Resolution: archived items block resend with reversible-state copy; deleted items are unavailable and destructive.

Edit lock during publish

Trigger: publish/email template snapshot occurs while resend dialog is open.

Resolution: send uses the published snapshot or blocks with conflict copy; it never sends mixed template state.

Audit row on every state change

Trigger: resend requested, queued, cancelled, sent, failed, or retried.

Resolution: audit rows record actor, guest id, message type, and outbox id.

Two organizers concurrent

Trigger: two organizers resend to the same guest simultaneously.

Resolution: idempotency prevents duplicate email within the resend window and both sessions see final status.

Undo window for destructive actions

Trigger: organizer cancels a queued resend.

Resolution: 10 second undo restores the queued job when delivery has not started.

Bulk resend gap

Trigger: organizer selects many guests and expects EventFarm-style bulk resend.

Resolution: bulk control remains gap-marked until implemented.

Confirmation resend gap

Trigger: organizer tries to resend confirmations to confirmed guests.

Resolution: confirmation resend is exposed as missing parity until the endpoint exists.

Timeframe exclusion gap

Trigger: organizer wants to avoid guests contacted in the last N hours.

Resolution: exclusion UI is a required gap panel until supported.

Suppressed or bounced recipient

Trigger: selected guest is suppressed or hard-bounced.

Resolution: resend is blocked with row-level reason and no outbox row is created.

Stable test attributes

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

data-testWherePurpose
resend-pageGuest listResend-capable surface
resend-guest-rowTableRecipient row
resend-single-ctaRow actionsSingle resend
resend-bulk-barSelection stateBulk resend
resend-modalModalConfirm resend
resend-message-type-pickerModalInvitation/confirmation
resend-recipient-previewModalRecipients/exclusions
resend-send-ctaModalSend
resend-progressModal/pageAsync progress
resend-undo-toastToastUndo cancel
resend-conflict-modalModalSnapshot conflict
resend-gap-panelPageMissing parity
resend-suppressed-reasonRowSuppressed/bounced reason

Agent test plan

- resend-page-renders
- single-resend-invitation
- resend-progress-updates
- permission-denied-boundary
- cross-tenant-404
- soft-delete-audit
- archive-delete-distinction
- publish-edit-lock
- audit-row-every-change
- concurrent-organizers-idempotent
- destructive-undo-window
- bulk-resend-gap
- confirmation-resend-gap
- timeframe-exclusion-gap
- suppressed-recipient-blocked