3.0 KiB
3.0 KiB
/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:<address> [note:<text>]
email— required destination address; validated withEMAIL_RE, header-sanitized.note— optional cover message prepended above the transcript (max 1000 chars).- Staff-only (the command dispatcher gates every command via
requireStaffRole);ManageMessagesdefault member permission, matching other ticket-mod commands.
Customer-isolation guarantees (the hard requirement)
To:= target address only. NoCc, noBcc.- The Gmail
messages.sendcall carries nothreadId→ a brand-new thread, not an append to the customer's conversation. - No
In-Reply-To/Referencesheaders. - The customer's
senderEmailis never written into any recipient header. - Fresh
Subject: Fwd: <original subject>.
Flow
handlers/commands/forward.js→handleForward(interaction):findTicketForChannel— else ephemeral "not a ticket channel".- Discord-origin ticket (
gmailThreadIdstartsdiscord-) → ephemeral "no email thread". deferReplyephemerally (fetch + attachment download can exceed 3s).forwardThread(gmailThreadId, email, note, userId).- Ephemeral confirmation: "Forwarded N messages (M attachments) to x@y.com"
(+ note if any attachments were skipped for size).
logTicketEvent.
services/gmail.js→ newforwardThread(threadId, targetEmail, note, userId):- Validate target (
EMAIL_RE); throwEBADRECIPIENTif bad. threads.get(format:'full'); throwEEMPTYif no messages.- Subject from first message →
Fwd: <subject>(strip existingFwd:), RFC2047-encode. - Per message oldest→newest: header line (
From:/Date:) +getCleanBody; build parallel text and HTML blocks (HTML bodyescapeHtml-ed — customer content). - Collect attachment parts (recursive walk for
filename+body.attachmentId); download viamessages.attachments.get. Cap total ~20 MB; past it → skip + count. - Compose a new outbound email:
From: support@,To: target only,multipart/mixed(attachments) wrappingmultipart/alternative(text+html). Plain-ASCII divider between messages in text;<hr>in HTML. messages.send(nothreadId). Return{ messageCount, attachmentCount, skipped }.
- Validate target (
Files
commands/register.js—/forwardbuilder (afterfolder).services/gmail.js—forwardThread()+collectAttachmentParts()helper; export; importgetCleanBodyfromutils.handlers/commands/forward.js— new handler.handlers/commands/index.js— import +COMMAND_HANDLERS.forward;/helpline.
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).