---
skill: user-story-writer
title: User Story Writer
input: "Feature: let users export a dashboard to PDF and schedule a recurring email of it. Users are analysts and their managers. Keep stories small and testable with clear acceptance criteria."
source: generated by claude-sonnet-4-6
---

# Epic Decomposition: Dashboard Export & Scheduled Email

**Epic:** Dashboard Distribution
**Goal:** Enable analysts and managers to export dashboards as PDFs and automate recurring email delivery, reducing manual reporting overhead.

---

## Story Map

| # | Story | Priority | Dependencies |
|---|---|---|---|
| 1 | Export current dashboard view as PDF | P1 | — |
| 2 | Configure PDF export options (orientation, page range) | P2 | #1 |
| 3 | Send dashboard PDF as a one-off email | P2 | #1 |
| 4 | Create a recurring email schedule for a dashboard | P1 | #1, #3 |
| 5 | View and manage existing schedules | P1 | #4 |
| 6 | Edit an existing schedule | P2 | #5 |
| 7 | Delete a schedule | P1 | #5 |
| 8 | Receive and view the scheduled email | P1 | #4 |

**MVP sprint (P1):** Stories 1, 4, 5, 7, 8
**Follow-on sprint (P2):** Stories 2, 3, 6

---
---

## Story 1: Export current dashboard view as PDF

**Epic:** Dashboard Distribution
**Story ID:** _(blank)_
**Priority:** P1
**Story points:** _(blank)_

---

### User Story

> **As an** analyst,
> I want to **export my current dashboard view as a PDF**,
> so that I can **share a static snapshot of the data with stakeholders who don't have system access**.

---

### Context

This is the foundational story for the epic — all scheduling and email features depend on the PDF generation capability. The export must reflect exactly what the analyst sees on screen at the moment of export, including any filters, date ranges, or segments they have applied. This is a manual, on-demand action.

---

### Acceptance Criteria

**AC1: Export button is accessible on the dashboard**
```
Given I am viewing any dashboard
When I look at the dashboard toolbar
Then I can see an "Export" button (or icon with label) that is keyboard-accessible
```

**AC2: Successful PDF download**
```
Given I am viewing a dashboard with at least one populated widget
When I click "Export" and confirm the action
Then a PDF file downloads to my browser's default download location
  And the filename follows the pattern: [Dashboard Name]_[YYYY-MM-DD].pdf
  And the PDF renders all visible widgets at their current state
```

**AC3: Applied filters are preserved in the export**
```
Given I have applied a date filter and a segment filter to the dashboard
When I export to PDF
Then the exported PDF reflects only the filtered data, not the unfiltered default view
  And the active filters are displayed as a summary line at the top of the PDF
```

**AC4: Loading or processing state shown during export**
```
Given I have clicked "Export"
When the PDF is being generated (taking more than 1 second)
Then a loading indicator is shown and the Export button is disabled to prevent duplicate requests
  And once the download begins, the loading state clears
```

**AC5: Dashboard with no data**
```
Given I am viewing a dashboard where all widgets return no data (e.g. filters return zero results)
When I export to PDF
Then the export still succeeds
  And each empty widget renders with its title and an "No data available" placeholder — no blank white boxes
```

---

### Edge Cases

- [ ] **Very long dashboard:** Dashboard taller than one page — PDF must paginate cleanly, with no widget cut off mid-render across pages
- [ ] **Dashboard with a live video or animation widget:** Static snapshot only — render the widget's still/placeholder state with a note "Live content not available in export"
- [ ] **Slow network during generation:** If PDF generation exceeds 30 seconds, cancel and show an error — "Export failed. Please try again or contact support."
- [ ] **Browser popup blocker:** If the download is blocked, show an inline message with a manual "Click here to download" fallback link
- [ ] **Analyst has view-only permissions:** Export is still permitted — exporting does not require edit rights

---

### Out of Scope

- Choosing page orientation or paper size (Story 2)
- Emailing the PDF directly from this action (Story 3)
- Scheduling recurring exports (Story 4)
- Exporting to formats other than PDF (not in this epic)

---

### Definition of Done

- [ ] Acceptance criteria all pass
- [ ] Edge cases handled or explicitly deferred with a ticket raised
- [ ] Unit tests for PDF generation logic; E2E test covering happy path download
- [ ] PDF renders correctly in Adobe Acrobat, Chrome PDF viewer, and macOS Preview
- [ ] Export button is keyboard-navigable and labelled for screen readers (aria-label)
- [ ] Loading state prevents duplicate submissions
- [ ] Error states have approved copy
- [ ] Tested on Chrome, Firefox, Safari, and Edge
- [ ] Product and design reviewed in staging

---
---

## Story 2: Configure PDF export options

**Epic:** Dashboard Distribution
**Story ID:** _(blank)_
**Priority:** P2
**Story points:** _(blank)_

---

### User Story

> **As an** analyst,
> I want to **choose page orientation and which widgets to include before exporting**,
> so that I can **produce a PDF that fits the layout expected by the recipient without unnecessary content**.

---

### Context

Some analysts share focused reports with a subset of dashboard widgets — for example, only revenue widgets for a finance audience. This story adds an options step before the export from Story 1 triggers. It must not slow down users who want the default full export — the quick path must remain one click.

---

### Acceptance Criteria

**AC1: Export options panel opens on demand**
```
Given I am viewing a dashboard
When I click the dropdown arrow next to the "Export" button
Then an options panel appears with controls for: page orientation (Portrait / Landscape) and widget selection (all selected by default)
```

**AC2: Page orientation applies to the PDF**
```
Given I have selected "Landscape" orientation in the export options
When I confirm the export
Then the downloaded PDF is rendered in landscape orientation
  And all widgets reflow to fit the wider page width
```

**AC3: Deselecting widgets excludes them from the PDF**
```
Given I have unchecked 2 of 6 widgets in the export options panel
When I confirm the export
Then the downloaded PDF contains only the 4 selected widgets
  And the excluded widgets leave no blank space in the PDF layout
```

**AC4: Default export (no options selected) unchanged**
```
Given I click the main "Export" button directly (not the dropdown)
When the PDF downloads
Then it uses the last-used orientation setting (defaulting to Portrait on first use)
  And includes all widgets — no options panel appears
```

**AC5: Attempting to export with zero widgets selected**
```
Given I have deselected all widgets in the options panel
When I attempt to confirm the export
Then the confirm button is disabled
  And an inline message reads "Select at least one widget to export"
```

---

### Edge Cases

- [ ] **Single-widget dashboard:** Widget selection shows one item — deselecting it triggers AC5 immediately
- [ ] **Options panel on small screen / tablet viewport:** Panel must be scrollable if content overflows; all controls must remain usable at 768px width
- [ ] **User changes orientation after deselecting widgets:** Widget selection state must persist through orientation toggle

---

### Out of Scope

- Custom margins, font sizes, or branding (not in this epic)
- Saving export preferences per dashboard (not in this epic)
- Export formats other than PDF

---

### Definition of Done

- [ ] Acceptance criteria all pass
- [ ] Options panel is keyboard-navigable; all inputs are properly labelled
- [ ] Orientation preference persisted in local storage for default (not a backend call)
- [ ] Unit tests for widget selection and orientation logic
- [ ] Product and design reviewed in staging

---
---

## Story 3: Send a dashboard PDF as a one-off email

**Epic:** Dashboard Distribution
**Story ID:** _(blank)_
**Priority:** P2
**Story points:** _(blank)_

---

### User Story

> **As an** analyst,
> I want to **email the current dashboard PDF directly to one or more recipients from within the app**,
> so that I can **share a report instantly without downloading it and attaching it manually**.

---

### Context

This is a precursor pattern to the scheduling feature (Story 4) — the same email composition UI will be reused for scheduling. Recipients can be internal users (selectable from a directory) or external email addresses typed manually. This story is one-off only — no recurrence.

---

### Acceptance Criteria

**AC1: Email send option is accessible**
```
Given I am viewing a dashboard
When I click the "Export" dropdown
Then I can see a "Send by email" option alongside the PDF download option
```

**AC2: Email composition form opens**
```
Given I click "Send by email"
When the form opens
Then I can enter: one or more recipient email addresses, an optional subject line (pre-filled with "[Dashboard Name] — [Today's Date]"), and an optional message body
```

**AC3: Successful email delivery confirmation**
```
Given I have entered at least one valid email address and clicked "Send"
When the system processes the request
Then a success toast appears: "Dashboard sent to [n] recipient(s)"
  And the PDF attached to the email reflects the dashboard state at the moment "Send" was clicked
```

**AC4: Invalid email address blocked**
```
Given I have typed an invalid email address (e.g. "notanemail")
When I attempt to submit the form
Then the form does not submit
  And an inline error highlights the invalid field: "Enter a valid email address"
```

**AC5: Email with no recipient blocked**
```
Given the recipient field is empty
When I click "Send"
Then the Send button remains disabled
  And the recipient field shows helper text: "Add at least one recipient"
```

**AC6: Recipient from internal directory**
```
Given I begin typing a colleague's name in the recipient field
When at least 2 characters are entered
Then a dropdown suggests matching internal users by name and email
  And selecting a suggestion populates their email address in the field
```

---

### Edge Cases

- [ ] **Recipient list exceeds 50 addresses:** Show a warning — "Sending to more than 50 recipients. Consider scheduling a distribution list instead." Allow user to proceed or cancel
- [ ] **Email delivery failure (bounce or SMTP error):** Show an error notification — "Email could not be delivered to [address]. Please check the address and retry." Do not silently fail
- [ ] **PDF generation fails before send:** Do not send the email — surface the PDF generation error first; do not send a blank or broken attachment
- [ ] **User closes the form mid-composition:** Show a "Discard changes?" confirmation if the recipient field is populated

---

### Out of Scope

- Scheduling recurring emails (Story 4)
- Tracking email open rates or delivery receipts (not in this epic)
- Sending on behalf of another user
- Distribution list management

---

### Definition of Done

- [ ] Acceptance criteria all pass
- [ ] Email delivery handled via existing transactional email service (confirm provider with engineering before starting)
- [ ] Recipient autocomplete is accessible via keyboard
- [ ] Subject line and body sanitised before send (no script injection via message body)
- [ ] Unit tests for validation logic; integration test for email send flow
- [ ] Product and design reviewed in staging

---
---

## Story 4: Create a recurring email schedule for a dashboard

**Epic:** Dashboard Distribution
**Story ID:** _(blank)_
**Priority:** P1
**Story points:** _(blank)_

---

### User Story

> **As a** manager,
> I want to **set up a recurring email schedule that automatically sends a dashboard PDF to my team**,
> so that I can **ensure my team receives up-to-date reports without me manually sending them each time**.

---

### Context

Managers currently export dashboards and email them manually — often weekly or monthly. This story automates that workflow. The schedule runs a PDF snapshot at the configured time and emails it. The dashboard state at send-time is used — not the state at schedule-creation time. The scheduler must support timezone selection because teams are distributed.

---

### Acceptance Criteria

**AC1: Schedule creation entry point is accessible**
```
Given I am viewing a dashboard
When I click the "Export" dropdown
Then I can see a "Schedule email" option
```

**AC2: Schedule form captures required fields**
```
Given I click "Schedule email"
When the schedule creation form opens
Then I can configure:
  - Recipient email addresses (at least one required)
  - Frequency: Daily / Weekly / Monthly
  - Day of week (if Weekly) or day of month (if Monthly)
  - Time of day (hour and minute)
  - Timezone (searchable dropdown, defaults to my account timezone)
  - Optional subject line and message body
```

**AC3: Schedule saves successfully**
```
Given I have completed all required fields
When I click "Save schedule"
Then a success message confirms: "Schedule created. First email will send on [Next Occurrence Date] at [Time] [Timezone]"
  And the schedule appears in my Schedules list (Story 5)
  And no email is sent immediately
```

**AC4: Frequency — daily schedule**
```
Given I create a schedule with frequency set to "Daily" at 08:00 Europe/London
When the schedule runs
Then an email is sent every calendar day at 08:00 London time
  And daylight saving time transitions are handled automatically (the time stays 08:00 local, not UTC-fixed)
```

**AC5: Frequency — weekly schedule**
```
Given I create a schedule with frequency "Weekly", day "Monday", time 09:00
When Monday arrives at 09:00 in the specified timezone
Then the email is sent
  And the next send date shown in the Schedules list updates to the following Monday
```

**AC6: Frequency — monthly schedule**
```
Given I create a schedule with frequency "Monthly", day of month "31"
When a month has fewer than 31 days (e.g. February)
Then the email sends on the last day of that month
  And the schedule does not skip the month or error
```

**AC7: Required field validation**
```
Given I attempt to save a schedule with no recipients entered
When I click "Save schedule"
Then the form does not submit
  And the recipient field is highlighted with: "Add at least one recipient"
```

---

### Edge Cases

- [ ] **Analyst (not manager) creates a schedule:** Analysts can also create schedules for their own dashboards — role restriction is not applied here; permission model is dashboard-level, not role-level
- [ ] **Dashboard is deleted after a schedule is created:** Send the scheduled email team a notification — "Dashboard '[Name]' has been deleted. Your schedule '[Name]' has been paused." Do not silently fail
- [ ] **Dashboard owner changes access permissions:** If the schedule creator loses access to the dashboard, pause the schedule and notify them
- [ ] **Two schedules created for the same dashboard, same time, same recipients:** Allow it — do not deduplicate silently; user is responsible for managing duplicates (they'll see both in Story 5)
- [ ] **Timezone not set on user account:** Default to UTC and prompt the user to confirm with a banner: "We've defaulted to UTC. Update your timezone in account settings."

---

### Out of Scope

- Editing an existing schedule (Story 6)
- Deleting a schedule (Story 7)
- Viewing schedule history / delivery logs (not in this epic)
- Scheduling exports to formats other than PDF
- Setting an end date for a schedule (not in this epic — schedules run until manually deleted)

---

### Definition of Done

- [ ] Acceptance criteria all pass
- [ ] Timezone handling validated for DST transitions (London, New York, Sydney at minimum)
- [ ] Monthly "short month" edge case has a passing automated test
- [ ] Schedule is persisted in the database — survives server restart
- [ ] Unit tests for recurrence calculation logic; integration test for schedule creation
- [ ] No email is triggered on schedule creation — only on the first scheduled occurrence
- [ ] Product and design reviewed in staging

---
---

## Story 5: View and manage existing schedules

**Epic:** Dashboard Distribution
**Story ID:** _(blank)_
**Priority:** P1
**Story points:** _(blank)_

---

### User Story

> **As a** manager,
> I want to **see a list of all the email schedules I have created**,
> so that I can **understand what's currently active and avoid sending duplicate or outdated reports to my team**.

---

### Context

Without visibility into active schedules, users will create duplicates or forget to update schedules when dashboards change. This view is the management hub for all schedule-related actions (editing in Story 6, deleting in Story 7). Scope is intentionally read-only for this story.

---

### Acceptance Criteria

**AC1: Schedules list is accessible**
```
Given I am
