# Relay Desk — Product Requirements (v3.3)

Relay Desk is a customer-service platform. Agents work tickets in a console with views, first-reply SLA timers, a ticket workspace (public reply vs internal note, macros, properties), bulk actions, a live chat queue, a knowledge base and reports. Customers use a public help center to file requests, look up their status and search articles.

**Problem.** Support teams need to answer customers quickly without ever exposing internal discussion, losing a teammate’s reply, or silently breaking an SLA; customers need a simple way to ask for help and follow up.

## Users
- **Support agent** — Work a queue of tickets, reply, add internal notes, use macros and chat, and see which tickets are about to breach.
- **Customer** — Submit a request, receive a request number and check its status without seeing internal notes.
- **Team lead** — Trust that reports, SLA counts and merges reflect what actually happened.

## Goals
- Internal notes are never visible to customers.
- SLA state always matches the ticket’s priority, age and status.
- A reply is never sent over a teammate’s newer reply without the agent seeing it first, and destructive actions need explicit confirmation.

## Agent sign-in (AUT)

Email and password sign-in to the console.

- **FR-AUT-01** (Must) A valid agent signs in and lands on "Your unsolved tickets" with the Views sidebar showing counts.
  - URL becomes /helpdesk/agent/views/mine; the header shows the agent name and role
  - Seven views: Your unsolved tickets, Unassigned tickets, All unsolved tickets, SLA breached, Pending tickets, Recently solved, All tickets (60)
- **FR-AUT-02** (Should) Empty and wrong credentials are rejected on the sign-in page.
  - Empty form: "Enter your email and password"
  - Wrong password: "Invalid email or password"; the page stays on sign-in

## Ticket list (LST)

Views with search, filters, sorting and paging.

- **FR-LST-01** (Should) The list pages 25 tickets at a time and supports search, status/priority filters and Clear.
  - "All tickets" shows "60 tickets" and the pager "1–25 of 60"; after Next twice "51–60 of 60" with Next disabled
  - Search "refund" lists only tickets whose subject or tags mention refund, including #1001
  - A priority filter with no matches shows "No tickets match this view and "refund"."; Clear restores 60 tickets
- **FR-LST-02** (Could) Opening a ticket link while signed out signs the agent in and then opens that ticket; reload keeps the agent on it.

## SLA timers (SLA)

First-reply SLA per ticket.

- **FR-SLA-01** (Must) The first-reply deadline is the ticket’s creation time plus its priority target: Urgent 1 h, High 4 h, Normal 8 h, Low 24 h; a ticket past the deadline with no reply is breached and listed in "SLA breached".
  - #1001 (High, 6 h old, no reply) is in "SLA breached" with a red negative badge such as "−2h 0m"
  - The "SLA breached" view count matches the number of breached tickets
- **FR-SLA-02** (Should) Badges reflect state: running, warning, paused, met.
  - #1004 (High, New, 90 min old) shows a neutral badge of about "2h 30m"
  - Pending and On-hold tickets show "⏸ Paused"
  - Solved tickets or tickets with a first reply show "✓ Met", never a running timer

## Ticket workspace (WRK)

Reply, internal notes, solving rules and properties.

- **FR-WRK-01** (Must) A public reply is added to the thread under the agent’s name, keeps the status the agent chose and records the first response.
  - The composer defaults to "Public reply" with hint "Reply to Elena Park — the customer will see this"
  - Choosing Pending changes the button to "Submit as Pending"; after sending: toast "Ticket #1001 updated · Pending", reply last in the thread, status pill Pending
  - "First reply due" in Properties shows "✓ Met"
- **FR-WRK-02** (Must) An internal note is visible to agents only and is never shown to the customer.
  - The Internal note tab turns the composer yellow with "Internal note — only agents can see this"
  - The note appears in the thread with a yellow "Internal note" tag
  - The customer’s status lookup shows only public messages; no note, including seeded ones, appears
- **FR-WRK-03** (Must) A ticket cannot be solved without an assignee.
  - Submitting as Solved on an unassigned ticket shows "Assignee is required to solve a ticket" and the status stays New
  - "Take it" assigns the agent; solving then succeeds with toast "Ticket #1002 updated · Solved" and the ticket leaves "Unassigned tickets"
- **FR-WRK-04** (Should) The Root cause field is shown only for tickets of type Incident, is required to solve one and is cleared when the type changes.
  - Type Question: no Root cause field; Type Incident: "Root cause — required to solve" appears
  - Solving an Incident without it shows "Root cause is required to solve an Incident" and the status stays New
  - Switching the type back to Question, saving, then to Incident again shows an empty Root cause
- **FR-WRK-05** (Should) Assignee is a searchable combobox listing "— Unassigned —" and every agent.
  - Typing "pri" leaves only Priya Nair; "zzz" shows "No agent matches "zzz""
  - Enter selects the highlighted option and closes the list
  - "Save properties" persists the assignee across reload
- **FR-WRK-06** (Should) Agents can create a ticket on behalf of a customer; it links to an existing customer record.
  - "Requester email is required" then "Subject is required" when missing
  - Success: toast "Ticket #NNNN created" and the workspace opens with status New
  - "Other tickets" lists the customer’s earlier tickets
- **FR-WRK-07** (Could) Unknown ticket ids show a friendly page: "Ticket not found" and "There is no ticket #9999."

## Sending and concurrency (SND)

Undo window and protection against overwriting teammates.

- **FR-SND-01** (Must) If a teammate has replied since the agent opened the ticket, the agent’s reply is not sent; the conflict is announced and the draft is kept.
  - Alert "Sam Okoye updated this ticket while you were writing. Review the new reply before you submit."; Submit is disabled; the draft stays in the composer
  - "Review changes" shows the teammate’s reply and the new status without altering the draft
  - The second submit succeeds; the thread contains exactly one copy of each reply, the teammate’s first
  - Presence banners "👀 Sam Okoye is also viewing this ticket" and "✍ Sam Okoye is typing a reply…" appear on the ticket
- **FR-SND-02** (Should) Replies are held for 5 seconds with an Undo; undoing sends nothing and keeps the draft.
  - Status bar "Sending in 5s…" with "Undo"; Submit is disabled
  - Undo shows "Sending cancelled — your draft is kept"; the thread, status and SLA are unchanged after reload

## Macros (MAC)

Canned replies that also set properties.

- **FR-MAC-01** (Must) Applying "Refund issued" fills the reply, sets status Solved and adds the tag refund; submitting applies all three.
  - Reply starts "Hi Elena, we have issued a full refund."; button reads "Submit as Solved"; hint lists tags: refund
  - After submit: toast "Ticket #1001 updated · Solved", status pill Solved, tag refund present and the ticket is in "Recently solved"
- **FR-MAC-02** (Could) A private macro such as "Escalate to Tier 2" switches the composer to an internal note and sets group Technical and the priority it defines; the button reads "Submit as Open".

## Bulk actions (BLK)

Act on several selected tickets.

- **FR-BLK-01** (Should) Bulk assign and bulk status update the selected tickets; ineligible tickets are reported and left unchanged.
  - Two selected tickets: "2 tickets assigned to you"; both leave "Unassigned tickets" and the count drops by 2
  - Setting Solved on an unassigned ticket: "0 updated · 1 skipped (no assignee): #NNNN" and the row stays

## Merge tickets (MRG)

Destructive merge of a duplicate into another ticket.

- **FR-MRG-01** (Must) Merge requires choosing the target, previewing it and typing MERGE; the source is closed and cannot be merged again.
  - The preview shows the target ticket and requester; "Merge tickets" stays disabled until exactly "MERGE" is typed (lowercase does not count)
  - Result: toast "#1005 merged into #1001"; #1001 gains a system note about the merge
  - The merged ticket is Closed with banner "This ticket was merged into #1001 and is closed."; its "Merge into another ticket…" is disabled
  - Merging a ticket into itself shows "A ticket cannot be merged into itself"
- **FR-MRG-02** (Should) Requesters with similar names are separate customers and are told apart by email.
  - #1001 Elena Park and #1005 Elena Parks have different emails and different orders (BZ-48213 vs BZ-48231)

## Live chat (CHT)

Queue of waiting visitors and chat-to-ticket conversion.

- **FR-CHT-01** (Must) An agent can accept a waiting chat, converse with the visitor and convert the transcript into a ticket when ending the chat.
  - The queue shows "Waiting (3)" with Visitor 4821 on /orders/BZ-77120
  - Accept opens "Chat with Visitor 4821" with the visitor’s opening message; sending a message shows a typing indicator and then the visitor’s answer
  - End chat with "Convert to ticket" ticked and a subject: toast "Chat ended · ticket #NNNN created"; the ticket has channel Chat, the agent as assignee and a transcript note

## Help center (POR)

Public request form, status lookup and knowledge base.

- **FR-POR-01** (Must) The request form validates input and, once valid, creates a request and returns its number.
  - Invalid email: "Enter a valid email address" with the field marked invalid
  - Description shorter than 10 characters: "Describe the issue in at least 10 characters"
  - Success: "Request #NNNN received" and "We emailed a confirmation to <email>."
  - Agents see the request in "Unassigned tickets" with status New, the chosen group and tag portal
- **FR-POR-02** (Should) Status lookup needs the matching request number and email, and shows only public messages.
  - Wrong email: "No request matches that number and email"
  - Right email for #1003: "#1003 Package arrived damaged" with status "Awaiting your reply"
- **FR-POR-03** (Could) The knowledge base lists 12 articles grouped Accounts, Billing, Shipping, Technical, is searchable and expands articles inline.
  - Search "password" gives "1 article" — "Reset your password" ("Links expire after 30 minutes.")
  - No match: "No articles match "zzz"."

## Reports (RPT)

KPIs and distributions from the live ticket set.

- **FR-RPT-01** (Could) Reports show Total tickets (60), Unsolved, SLA compliance %, SLA breached and CSAT, plus bars by status, channel and priority; they update after tickets change.
  - Solving one unsolved ticket lowers Unsolved by 1 and raises the Solved bar by 1

## Non-functional requirements

- **NFR-01** (Should) The console is usable at phone width (390 px): navigation moves to a bottom bar, Views open in a drawer and the page does not scroll horizontally.
- **NFR-02** (Must) Customer-facing pages never expose internal notes or other customers’ requests, and the agent console requires sign-in.

## Glossary and rules

- **Internal note** — A message only agents can see.
- **Macro** — A canned reply that can also set status, tags, priority and group.
- **First reply SLA** — Time allowed for the first public reply, by priority.

## Out of scope

- Email delivery
- Attachments
- Real push between agents
