Email ticketing fixes, comms polish, and .env cleanup
Inbound: - Gmail poll query is:unread in:inbox (was category:primary, which matched nothing on a no-tabs Workspace inbox) Outbound email: - Closed/escalation auto-emails editable via TICKET_CLOSE_MESSAGE and new TICKET_ESCALATION_EMAIL_MESSAGE; drop the staff signature from closing emails - Replies quote the customer's latest message (gmail_quote markup so clients collapse it), embed custom emoji inline via CID attachment, and strip Discord role mentions - Tagline spacing fix in the company signature Discord side: - Suppress all mentions in log + transcript posts (no more pinging on close) - Drop the staff-role ping from new-ticket and follow-up notifications - Ticket channels inherit category permissions instead of setting per-channel overwrites (removes the Manage Roles requirement) Gmail folders: - Folder/label routing (gmailLabels.js) with /folder; close files to Complete Config: - Remove ~56 stale .env keys for long-removed features; refresh stale copy Docs: - Design specs for folder routing, email-flow toggle, and per-staff metrics
This commit is contained in:
@@ -21,6 +21,7 @@ const {
|
||||
sanitizeEmbedText
|
||||
} = require('./utils');
|
||||
const { getGmailClient } = require('./services/gmail');
|
||||
const { moveThreadToFolder } = require('./services/gmailLabels');
|
||||
const { getNextTicketNumber, checkTicketLimits, getOrCreateTicketCategory, toDiscordSafeName, getSenderLocal } = require('./services/tickets');
|
||||
const { logError } = require('./services/debugLog');
|
||||
const { enqueueSend } = require('./services/channelQueue');
|
||||
@@ -153,22 +154,11 @@ async function findOrCreateTicketChannel(guild, parsed, number) {
|
||||
const channel = await guild.channels.create({
|
||||
name: chanName,
|
||||
type: ChannelType.GuildText,
|
||||
parent: parentCategoryId,
|
||||
// Email tickets have no Discord creator — the customer is reachable
|
||||
// only by email. So the only per-channel allow is the staff role; we
|
||||
// still explicitly deny @everyone in case the category permissions
|
||||
// are ever misconfigured to grant View Channel server-wide.
|
||||
permissionOverwrites: [
|
||||
{ id: guild.id, deny: [PermissionFlagsBits.ViewChannel] },
|
||||
...(CONFIG.ROLE_ID_TO_PING ? [{
|
||||
id: CONFIG.ROLE_ID_TO_PING,
|
||||
allow: [
|
||||
PermissionFlagsBits.ViewChannel,
|
||||
PermissionFlagsBits.SendMessages,
|
||||
PermissionFlagsBits.ReadMessageHistory
|
||||
]
|
||||
}] : [])
|
||||
]
|
||||
parent: parentCategoryId
|
||||
// Permissions are inherited from the ticket category — configure that
|
||||
// category to deny @everyone View Channel and allow the staff role, so
|
||||
// tickets stay staff-only. Inheriting (rather than setting per-channel
|
||||
// overwrites here) means the bot does not need the Manage Roles permission.
|
||||
});
|
||||
return { channel, parentCategoryId };
|
||||
} catch (createErr) {
|
||||
@@ -277,7 +267,7 @@ async function poll(client) {
|
||||
const gmail = getGmailClient();
|
||||
const list = await gmail.users.messages.list({
|
||||
userId: 'me',
|
||||
q: 'is:unread category:primary'
|
||||
q: 'is:unread in:inbox'
|
||||
});
|
||||
if (!list.data.messages) return;
|
||||
|
||||
@@ -310,11 +300,15 @@ async function poll(client) {
|
||||
if (ticketChan) {
|
||||
// Append follow-up to existing channel.
|
||||
const truncatedFollowup = parsed.followupBody.slice(0, 1800);
|
||||
// Role ping is intentional; body is attacker-controlled email content — suppress user/everyone mentions.
|
||||
// No staff role ping; body is attacker-controlled email content — suppress all mentions.
|
||||
await enqueueSend(ticketChan, {
|
||||
content: `<@&${CONFIG.ROLE_ID_TO_PING}>\n**New Follow-up from ${parsed.senderEmail}:**\n${truncatedFollowup}`,
|
||||
allowedMentions: { parse: ['roles'] }
|
||||
content: `**New Follow-up from ${parsed.senderEmail}:**\n${truncatedFollowup}`,
|
||||
allowedMentions: { parse: [] }
|
||||
});
|
||||
// Follow-up on an existing thread: archive the new message only. Leave
|
||||
// whatever managed folder staff filed this thread under untouched.
|
||||
console.log('Archiving/reading Gmail message', msgRef.id);
|
||||
await markGmailMessageRead(gmail, msgRef);
|
||||
} else {
|
||||
// Create a new ticket channel.
|
||||
const limitCheck = await checkTicketLimits(parsed.senderEmail);
|
||||
@@ -345,10 +339,9 @@ async function poll(client) {
|
||||
);
|
||||
|
||||
const welcomeMsg = await enqueueSend(ticketChan, {
|
||||
content: `<@&${CONFIG.ROLE_ID_TO_PING}>`,
|
||||
embeds: [ticketInfoEmbed],
|
||||
components: [buttons],
|
||||
allowedMentions: { parse: ['roles'] }
|
||||
allowedMentions: { parse: [] }
|
||||
});
|
||||
|
||||
const { createStaffThread } = require('./services/staffThread');
|
||||
@@ -392,10 +385,13 @@ async function poll(client) {
|
||||
},
|
||||
{ upsert: true, new: true }
|
||||
));
|
||||
}
|
||||
|
||||
console.log('Archiving/reading Gmail message', msgRef.id);
|
||||
await markGmailMessageRead(gmail, msgRef);
|
||||
// New (or reopened) ticket: file the email thread into Triage — out of
|
||||
// the inbox, marked read, awaiting staff action. The threads.modify also
|
||||
// clears UNREAD, so a success archives it like markGmailMessageRead did.
|
||||
console.log('Filing Gmail thread into Triage', parsed.threadId);
|
||||
await moveThreadToFolder(parsed.threadId, 'TRIAGE', gmail);
|
||||
}
|
||||
}
|
||||
authErrorNotified = false;
|
||||
} catch (e) {
|
||||
|
||||
Reference in New Issue
Block a user