← All stories

BRANCH · ef-050-last-contacted

Last Contacted and Last Email Interaction

EF-050Persona: OrganizerRoots in: event-setup

Organizers need to know when each guest was last contacted and what the last email interaction was: delivered, opened, clicked, bounced, failed, or suppressed. Voyage has partial evidence: the email activity report joins mailings, outbox, and guest email events at workers/reporting/src/report-catalog.ts:594, and guest export includes last notified/action fields at workers/events/src/routes/guests.ts:383. This story pins the guest-list UI and flags the missing complete ingestion parity.

Preconditions

Organizer can view the event guest list; fixture guests have outbox, delivered, opened, clicked, bounced, and no-email activity states.

Happy path / Lifecycle

  1. Open guest list columns.

    Last Contacted and Last Email Interaction columns appear with sortable timestamps and status pills.

  2. Filter by email state.

    Organizer filters for bounced or never-contacted guests and can take row actions such as update email or resend.

  3. Export.

    Guest export includes last notified/action fields consistently with the UI.

Failure modes

Permission denied at the right boundary

Trigger: viewer without guest communication permission opens email interaction columns.

Resolution: 403 for detail API or masked columns with no email activity leak.

Cross-tenant isolation

Trigger: tenant A requests tenant B guest email activity.

Resolution: 404, not 403, with no timestamp/status leak.

Soft-delete leaves audit trail

Trigger: organizer clears or suppresses email activity metadata.

Resolution: state is tombstoned with audit row containing prior status and actor.

Archive vs delete distinction

Trigger: event or guest is archived.

Resolution: archived activity remains readable to organizers; deleted activity is unavailable and audit remains.

Edit lock during publish

Trigger: report/export snapshot starts while activity refresh is running.

Resolution: snapshot uses a consistent cursor and UI labels stale data rather than mixing states.

Audit row on every state change

Trigger: activity status is ingested, suppressed, cleared, or corrected.

Resolution: audit row exists with provider event id or outbox id.

Two organizers concurrent

Trigger: one organizer resends while another filters/export activity.

Resolution: last-contacted updates deterministically and both sessions converge after refresh.

Undo window for destructive actions

Trigger: organizer suppresses or clears a guest from contact tracking.

Resolution: 10 second undo restores previous tracking state when no downstream send has occurred.

Incomplete event ingestion gap

Trigger: open/click/bounce webhooks are not fully ingested.

Resolution: UI exposes a gap panel and does not claim complete interaction parity.

Guest-list UI parity gap

Trigger: report/export has fields but guest list lacks columns.

Resolution: story requires visible columns and filters; missing UI remains a failing probe.

Clock ordering ambiguity

Trigger: delivered, opened, and clicked events arrive out of order.

Resolution: latest interaction is ordered by provider event time with deterministic tie-breaker.

Stable test attributes

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

data-testWherePurpose
email-activity-pageGuest listActivity-enabled surface
email-activity-tablePageGuest table
email-activity-filtersToolbarStatus filters
last-contacted-columnTableLast contacted
last-email-interaction-columnTableLast interaction
email-interaction-status-pillRowStatus
email-activity-export-ctaToolbarExport
email-activity-refresh-ctaToolbarRefresh
email-activity-suppress-ctaRowSuppress/clear
email-activity-undo-toastToastUndo suppress
email-activity-conflict-bannerPageSnapshot/stale state
email-activity-gap-panelPageMissing ingestion/UI parity
email-activity-permission-deniedPagePermission boundary

Agent test plan

- email-activity-renders
- filter-bounced-guests
- export-includes-last-fields
- permission-denied-boundary
- cross-tenant-404
- soft-delete-audit
- archive-delete-distinction
- publish-edit-lock
- audit-row-every-change
- concurrent-organizers-converge
- destructive-undo-window
- ingestion-gap-visible
- guest-list-ui-gap
- clock-ordering-deterministic