diff --git a/.gitignore b/.gitignore
index 00b41bc..3c809a1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -49,9 +49,6 @@ cursor.yml
*.local.yml
.claude/
-*.bak
-*.bak-*
-*.bak
-*.bak-*
CLAUDE.md
+*.bak*
diff --git a/handlers/messages.js.bak3-20260421 b/handlers/messages.js.bak3-20260421
deleted file mode 100644
index 0bd7666..0000000
--- a/handlers/messages.js.bak3-20260421
+++ /dev/null
@@ -1,106 +0,0 @@
-/**
- * Discord messageCreate handler – forwards staff replies to Gmail.
- */
-const { mongoose } = require('../db-connection');
-const { CONFIG } = require('../config');
-const { extractRawEmail } = require('../utils');
-const { getGmailClient, sendGmailReply } = require('../services/gmail');
-const { updateTicketActivity } = require('../services/tickets');
-const { getNotifyDm } = require('../services/staffSettings');
-
-const Ticket = mongoose.model('Ticket');
-
-/**
- * Handle a Discord message in a ticket channel → relay to Gmail (email tickets only).
- */
-async function handleDiscordReply(m) {
- if (m.author.bot || m.interaction) return;
-
- const ticket = await Ticket.findOne({ discordThreadId: m.channel.id }).lean();
- if (!ticket) return;
-
- // Track whether last message is from staff or customer
- const memberForCheck = await m.guild.members.fetch(m.author.id).catch(() => null);
- const isStaffMember = memberForCheck && CONFIG.ROLE_ID_TO_PING && memberForCheck.roles.cache.has(CONFIG.ROLE_ID_TO_PING);
- Ticket.updateOne(
- { discordThreadId: m.channel.id },
- { $set: { lastMessageAuthorIsStaff: !!isStaffMember, lastActivity: new Date() } }
- ).catch(() => {});
-
- // DM the claimer if they have notifydm on and a non-staff user replied.
- if (ticket.claimerId && !isStaffMember && m.author.id !== ticket.claimerId) {
- const dmEnabled = await getNotifyDm(ticket.claimerId);
- if (dmEnabled) {
- const staffMember = await m.guild.members.fetch(ticket.claimerId).catch(() => null);
- if (staffMember) {
- const jumpLink = `https://discord.com/channels/${m.guild.id}/${m.channel.id}/${m.id}`;
- await staffMember
- .send(
- `New customer reply in **${m.channel.name}**:\n> ${m.content.slice(0, 300)}\n[Jump to message](${jumpLink})`
- )
- .catch(() => {});
- }
- }
- }
-
- const authorName =
- m.member?.displayName ||
- m.member?.nickname ||
- m.author.globalName ||
- m.author.username;
-
- if (ticket.gmailThreadId.startsWith('discord-')) {
- return;
- }
-
- // Email tickets: send reply via Gmail.
- try {
- const gmail = getGmailClient();
- const thread = await gmail.users.threads.get({
- userId: 'me',
- id: ticket.gmailThreadId
- });
-
- const last = [...thread.data.messages].reverse().find(msg => {
- const from =
- msg.payload.headers.find(h => h.name === 'From')?.value || '';
- return !from.toLowerCase().includes(CONFIG.MY_EMAIL);
- });
-
- if (!last) return;
-
- let recipient =
- last.payload.headers.find(h => h.name === 'From')?.value || '';
- const replyTo =
- last.payload.headers.find(h => h.name === 'Reply-To')?.value;
- if (replyTo) recipient = replyTo;
-
- const subject =
- last.payload.headers.find(h => h.name === 'Subject')?.value ||
- 'Support';
- const msgId =
- last.payload.headers.find(h => h.name === 'Message-ID')?.value;
-
- const recipientEmail = extractRawEmail(recipient).toLowerCase();
- if (!recipientEmail || recipientEmail === CONFIG.MY_EMAIL) {
- console.warn('Bad recipient for reply:', recipientEmail);
- return;
- }
-
- await sendGmailReply(
- ticket.gmailThreadId,
- m.content,
- recipientEmail,
- subject,
- authorName,
- msgId,
- m.author.id
- );
-
- await updateTicketActivity(ticket.gmailThreadId);
- } catch (e) {
- console.error('REPLY ERROR:', e);
- }
-}
-
-module.exports = { handleDiscordReply };
diff --git a/services/gmail.js.bak3-20260421 b/services/gmail.js.bak3-20260421
deleted file mode 100644
index ee41a33..0000000
--- a/services/gmail.js.bak3-20260421
+++ /dev/null
@@ -1,346 +0,0 @@
-/**
- * Gmail service – OAuth client, send reply, send ticket-closed email.
- */
-const { google } = require('googleapis');
-const { CONFIG } = require('../config');
-const { extractRawEmail, escapeHtml } = require('../utils');
-const { getStaffSignatureBlocks } = require('./staffSignature');
-const { logError } = require('./debugLog');
-const { readEnvFile } = require('./configPersistence');
-
-function sanitizeHeaderValue(v) { return String(v || '').replace(/[\r\n]+/g, ' ').trim(); }
-const EMAIL_RE = /^[^@\s]+@[^@\s]+$/;
-
-function getGmailClient() {
- const auth = new google.auth.OAuth2(
- process.env.GOOGLE_CLIENT_ID,
- process.env.GOOGLE_CLIENT_SECRET
- );
- auth.setCredentials({ refresh_token: CONFIG.REFRESH_TOKEN });
- return google.gmail({ version: 'v1', auth });
-}
-
-/**
- * Re-read REFRESH_TOKEN from .env, update in-memory config, and probe Google.
- * Used by the internal /gmail/reload endpoint so the weekly reauth chore does
- * not require a full container restart.
- *
- * Throws if the env file is missing the token, or if the probe call (getProfile)
- * fails — the caller surfaces the error so the UI can see why.
- *
- * @returns {Promise<{emailAddress: string}>}
- */
-async function reloadGmailClient() {
- const envMap = readEnvFile();
- const newToken = envMap.get('REFRESH_TOKEN');
- if (!newToken) {
- const err = new Error('REFRESH_TOKEN not set in .env');
- err.code = 'ENOTOKEN';
- throw err;
- }
- process.env.REFRESH_TOKEN = newToken;
- CONFIG.REFRESH_TOKEN = newToken;
- const gmail = getGmailClient();
- const profile = await gmail.users.getProfile({ userId: 'me' });
- return { emailAddress: profile.data.emailAddress };
-}
-
-async function sendTicketClosedEmail(ticket, discordDisplayName) {
- try {
- const gmail = getGmailClient();
-
- // Send to the ticket sender (customer), not derived from thread (which can be support)
- const recipientEmail = sanitizeHeaderValue(extractRawEmail(ticket.senderEmail || '')).toLowerCase();
- if (!recipientEmail || recipientEmail === CONFIG.MY_EMAIL) return;
- if (!EMAIL_RE.test(recipientEmail)) {
- logError('sendTicketClosedEmail: invalid recipient', new Error(`Rejected: ${recipientEmail}`)).catch(() => {});
- return;
- }
-
- let subjectHeader = ticket.subject || 'Support';
- let msgId = null;
- try {
- const thread = await gmail.users.threads.get({
- userId: 'me',
- id: ticket.gmailThreadId
- });
- const messages = thread.data.messages || [];
- const lastMsg = [...messages].reverse()[0];
- if (lastMsg?.payload?.headers) {
- const subj = lastMsg.payload.headers.find(h => h.name === 'Subject')?.value;
- if (subj) subjectHeader = subj;
- msgId = sanitizeHeaderValue(lastMsg.payload.headers.find(h => h.name === 'Message-ID')?.value);
- }
- } catch (_) {
- /* use ticket.subject and no In-Reply-To if thread fetch fails */
- }
-
- const finalSubject = sanitizeHeaderValue(`${CONFIG.TICKET_CLOSE_SUBJECT_PREFIX} ${subjectHeader}`);
- const utf8Subject = `=?utf-8?B?${Buffer.from(
- finalSubject
- ).toString('base64')}?=`;
-
- const serverDisplayName = escapeHtml(discordDisplayName || CONFIG.SUPPORT_NAME || 'Support');
- const safeLogoUrl = escapeHtml(CONFIG.LOGO_URL || '');
- const safeSignature = escapeHtml(CONFIG.SIGNATURE || '').replace(/\n/g, '
');
- const safeCloseMessage = escapeHtml(CONFIG.TICKET_CLOSE_MESSAGE || '').replace(/\n/g, '
');
- const safeCloseSignature = escapeHtml(CONFIG.TICKET_CLOSE_SIGNATURE || '').replace(/\n/g, '
');
- const htmlBody = `
-
From: ${serverDisplayName} on Discord
-Message:
-${safeCloseMessage}
-${safeCloseSignature}
-|
- ${safeLogoUrl ? ` |
-
- ${serverDisplayName} -${safeSignature}
- |
-
From: ${serverDisplayName} on Discord
-Message:
-${safeCloseMessage}
-${safeCloseSignature}
-|
- ${safeLogoUrl ? ` |
-
- ${serverDisplayName} -${safeSignature}
- |
-
${escapeHtml(replyText).replace(/\n/g, '
')}
${safeStaffSigHtml}
` : ''} -