# `/forward` — forward a ticket's email thread to a third party ## Goal Let staff forward a ticket's entire email conversation to any email address from inside the ticket channel. The original customer must **never** be looped in. ## Command ``` /forward email:
[note:] ``` - `email` — required destination address; validated with `EMAIL_RE`, header-sanitized. - `note` — optional cover message prepended above the transcript (max 1000 chars). - Staff-only (the command dispatcher gates every command via `requireStaffRole`); `ManageMessages` default member permission, matching other ticket-mod commands. ## Customer-isolation guarantees (the hard requirement) 1. `To:` = target address **only**. No `Cc`, no `Bcc`. 2. The Gmail `messages.send` call carries **no `threadId`** → a brand-new thread, not an append to the customer's conversation. 3. **No `In-Reply-To` / `References`** headers. 4. The customer's `senderEmail` is never written into any recipient header. 5. Fresh `Subject: Fwd: `. ## Flow - `handlers/commands/forward.js` → `handleForward(interaction)`: 1. `findTicketForChannel` — else ephemeral "not a ticket channel". 2. Discord-origin ticket (`gmailThreadId` starts `discord-`) → ephemeral "no email thread". 3. `deferReply` ephemerally (fetch + attachment download can exceed 3s). 4. `forwardThread(gmailThreadId, email, note, userId)`. 5. Ephemeral confirmation: "Forwarded N messages (M attachments) to x@y.com" (+ note if any attachments were skipped for size). `logTicketEvent`. - `services/gmail.js` → new `forwardThread(threadId, targetEmail, note, userId)`: 1. Validate target (`EMAIL_RE`); throw `EBADRECIPIENT` if bad. 2. `threads.get(format:'full')`; throw `EEMPTY` if no messages. 3. Subject from first message → `Fwd: ` (strip existing `Fwd:`), RFC2047-encode. 4. Per message oldest→newest: header line (`From:`/`Date:`) + `getCleanBody`; build parallel text and HTML blocks (HTML body `escapeHtml`-ed — customer content). 5. Collect attachment parts (recursive walk for `filename` + `body.attachmentId`); download via `messages.attachments.get`. **Cap total ~20 MB**; past it → skip + count. 6. Compose a **new** outbound email: `From: support@`, `To: target only`, `multipart/mixed` (attachments) wrapping `multipart/alternative` (text+html). Plain-ASCII divider between messages in text; `
` in HTML. 7. `messages.send` (no `threadId`). Return `{ messageCount, attachmentCount, skipped }`. ## Files - `commands/register.js` — `/forward` builder (after `folder`). - `services/gmail.js` — `forwardThread()` + `collectAttachmentParts()` helper; export; import `getCleanBody` from `utils`. - `handlers/commands/forward.js` — new handler. - `handlers/commands/index.js` — import + `COMMAND_HANDLERS.forward`; `/help` line. ## Out of scope - Forwarding a single message or letting staff pick one (whole-thread only). - Native RFC822 re-send (we build a fresh email instead).