2026-02-17 21:49:58 -06:00
2026-02-17 21:49:58 -06:00
2026-02-17 21:49:58 -06:00
2026-02-17 21:49:58 -06:00
2026-02-17 21:49:58 -06:00
2026-02-10 08:22:19 -06:00
2026-02-17 21:49:58 -06:00
2026-02-17 21:49:58 -06:00
2026-02-17 21:49:58 -06:00
2026-02-17 21:49:58 -06:00
2026-02-17 21:49:58 -06:00
2026-02-10 08:22:19 -06:00
2026-02-10 08:22:19 -06:00
2026-02-17 21:49:58 -06:00
2026-02-17 21:49:58 -06:00

Broccolini Bot

A Node.js support-ticket bot that connects Gmail, Discord, and MongoDB into a unified ticketing system. Incoming support emails become Discord ticket channels; staff replies in Discord are sent back to the sender via Gmail. All ticket state is persisted in MongoDB.

Built for game-server hosting support (Indifferent Broccoli), with game detection from email content, configurable automation (auto-close, reminders, auto-unclaim), and a full set of Discord slash commands, buttons, modals, and context menus.

Quick links: Installation · Configuration · Discord Commands · Documentation


Table of Contents


Features

Email-to-Discord Ticketing

  • Polls Gmail every 30 seconds for unread emails in the primary inbox
  • Creates a dedicated Discord channel per ticket (ticket-{sender}-{number})
  • Detects the game from the email subject/body and tags the ticket accordingly
  • Sends a rich embed with ticket metadata and action buttons (Claim, Close)

Discord-to-Email Replies

  • Staff messages in a ticket channel are forwarded to the original sender via Gmail
  • Replies are threaded in Gmail so the sender sees a continuous conversation

Ticket Management

  • Claim / Unclaim -- Staff can claim tickets; optional auto-unclaim after inactivity
  • Priority Levels -- Low, Normal, High with color-coded embeds
  • Escalation -- Move urgent tickets to a dedicated escalation category
  • Transfer / Move -- Reassign tickets between staff or categories
  • Close Confirmation -- Prevents accidental closes with a confirmation prompt
  • Transcripts -- Full conversation transcripts posted to a dedicated channel on close
  • Auto-Close -- Automatically close tickets after configurable hours of inactivity
  • Inactivity Reminders -- Notify the channel when a ticket goes stale

Panel System

  • Deploy a "Open Ticket" button panel to any channel with /panel
  • Users click the button, fill out a modal form, and a ticket is created

Tag System (Saved Responses)

  • Set ticket category with /tag (dropdown); create reusable response templates with /response create
  • Dynamic template variables: {ticket.user}, {staff.name}, {server.name}, {date}, etc.
  • Autocomplete-enabled /tag command for instant use

Account Info Lookup

  • /accountinfo searches website users by email or Discord ID
  • Results show linked servers, game details, and user metadata

Analytics & Logging

  • In-memory tracking of command usage, button clicks, and errors
  • /stats shows uptime, interaction counts, and error rate
  • Configurable logging channel for ticket lifecycle events

Architecture

┌────────────────────────────────────────────────────────────────────┐
│                        BROCCOLINI BOT                              │
├────────────────────────────────────────────────────────────────────┤
│                                                                    │
│  ┌───────────┐      ┌────────────────┐      ┌──────────────────┐  │
│  │  Gmail    │─────>│  gmail-poll.js │─────>│  Discord         │  │
│  │  (inbox)  │      │  (every 30s)   │      │  (ticket channel)│  │
│  └───────────┘      └───────┬────────┘      └───────▲──────────┘  │
│                              │                       │             │
│                              v                       │             │
│                      ┌────────────────┐      ┌──────────────────┐  │
│                      │  services/     │      │  handlers/        │  │
│                      │  gmail.js      │<────>│  messages.js      │  │
│                      │  tickets.js    │      │  buttons.js       │  │
│                      │  guildSettings │      │  commands.js      │  │
│                      └───────┬────────┘      └──────────────────┘  │
│                              │                                     │
│                              v                                     │
│                      ┌────────────────┐      ┌──────────────────┐  │
│                      │  MongoDB        │      │  Express         │  │
│                      │  (Mongoose)     │      │  (healthcheck)   │  │
│                      └────────────────┘      └──────────────────┘  │
│                                                                    │
│  Events:                                                           │
│    ready            → Connect DB, register commands, start jobs    │
│    interactionCreate → Buttons, slash commands, modals, menus      │
│    messageCreate     → Discord replies → Gmail                       │
└────────────────────────────────────────────────────────────────────┘

Ticket lifecycle:

  1. Inbound email -- Gmail poll detects a new unread message, creates a Discord channel and a MongoDB record.
  2. Staff reply -- A message in the Discord ticket channel is forwarded to the sender via Gmail.
  3. Close -- A transcript is generated, a closure email is sent, and the Discord channel is deleted.

Prerequisites

Requirement Version
Node.js ≥ 18.x
npm ≥ 9.x
MongoDB ≥ 5.x (Atlas or self-hosted)

You will also need:

  • A Discord bot with the following intents enabled: Guilds, Guild Messages, Message Content, Guild Members
  • A Google Cloud project with the Gmail API enabled and OAuth2 credentials (Client ID, Client Secret, Refresh Token)

Installation

Single-level repo: all commands run from the repo root. Create .env in the repo root (copy from .env.example).

git clone <your-repo-url>
cd broccolini-bot
npm install
cp .env.example .env
# Edit .env with your Discord, Gmail, and MongoDB credentials (see Configuration).

Configuration

Create a .env file in the repo root (same directory as this README). All configuration is loaded via environment variables.

Important: After changing .env, you must restart the process (npm start / node broccolini-discord.js) for new values to take effect. If you add or change slash commands (e.g. /escalate, /email-routing, /panel options), restart the bot so it can re-register commands with Discord; otherwise new or updated commands may not appear.

Agent rule: Changes to .env by an AI/agent must require explicit user confirmation. Prefer proposing changes to .env.test first and migrating to .env only after the user approves. See ENV_AND_SECURITY.md.

Discord

Variable Required Description
DISCORD_TOKEN Yes Bot token from the Discord Developer Portal
DISCORD_GUILD_ID Yes Server (guild) ID where the bot operates
DISCORD_APPLICATION_ID Yes Application ID for registering slash commands
TICKET_CATEGORY_ID Yes Channel category ID where email ticket channels are created
EMAIL_TICKET_OVERFLOW_CATEGORY_IDS No Comma-separated category IDs; used when main email category has 50 channels
DISCORD_TICKET_CATEGORY_ID No Category for Discord panel tickets (defaults to TICKET_CATEGORY_ID)
DISCORD_TICKET_OVERFLOW_CATEGORY_IDS No Comma-separated category IDs; used when main Discord ticket category has 50 channels
ROLE_ID_TO_PING Yes Role ID to ping when a new ticket arrives
TRANSCRIPT_CHANNEL_ID No Channel ID for posting ticket transcripts
LOGGING_CHANNEL_ID No Channel ID for lifecycle log messages
DEBUGGING_CHANNEL_ID No Channel ID for error logs (escalate, deescalate, email-routing, Gmail poll, etc.)
BACKUP_EXPORT_CHANNEL_ID No Channel ID where /backup and /export post ticket dump files
ACCOUNT_INFO_CHANNEL_ID No Channel ID for account info lookups (and /accountinfo visibility)
EMAIL_ESCALATED_CATEGORY_ID No Category ID for escalated email tickets (tier 2+)
DISCORD_ESCALATED_CATEGORY_ID No Category ID for escalated Discord-origin tickets
ESCALATION_MESSAGE No Message sent when a ticket is escalated (supports {support_name})

Google OAuth2 / Gmail

Variable Required Description
GOOGLE_CLIENT_ID Yes OAuth2 Client ID from Google Cloud Console
GOOGLE_CLIENT_SECRET Yes OAuth2 Client Secret
REFRESH_TOKEN Yes OAuth2 Refresh Token for the support inbox
MY_EMAIL Yes The support email address (e.g. support@example.com)

MongoDB

Variable Required Description
MONGODB_URI Yes MongoDB connection string (e.g. mongodb+srv://user:pass@cluster/dbname)

Branding & Messages

Variable Default Description
SUPPORT_NAME -- Display name for the support system
LOGO_URL -- URL to the logo shown in embeds
EMAIL_SIGNATURE -- HTML signature appended to outgoing emails (use \n for line breaks)
TICKET_CLOSE_SUBJECT_PREFIX [Resolved] Prefix added to the subject of closure emails
TICKET_CLOSE_MESSAGE (see config.js) Body of the ticket closure email
TICKET_CLOSE_SIGNATURE (see config.js) Signature on the closure email
TICKET_WELCOME_MESSAGE (see config.js) Message posted when a ticket channel is created
TICKET_CLAIMED_MESSAGE (see config.js) Message posted when a ticket is claimed (supports {staff_name})
TICKET_UNCLAIMED_MESSAGE (see config.js) Message posted when a ticket is unclaimed

Automation

Variable Default Description
AUTO_CLOSE_ENABLED false Enable automatic ticket closure after inactivity
AUTO_CLOSE_AFTER_HOURS 72 Hours of inactivity before auto-close triggers
AUTO_CLOSE_MESSAGE (see config.js) Message sent when a ticket is auto-closed
REMINDER_ENABLED false Enable inactivity reminder messages
REMINDER_AFTER_HOURS 24 Hours of inactivity before a reminder is sent
REMINDER_MESSAGE (see config.js) Reminder message (supports {hours} variable)

Ticket Limits & Permissions

Variable Default Description
GLOBAL_TICKET_LIMIT 5 Maximum concurrent open tickets globally
TICKET_LIMIT_PER_CATEGORY 3 Maximum tickets per category
RATE_LIMIT_TICKETS_PER_USER 0 Max tickets a user can create per window (0 = disabled)
RATE_LIMIT_WINDOW_MINUTES 60 Window in minutes for per-user ticket creation limit
BLACKLISTED_ROLES -- Comma-separated role IDs that cannot open tickets
ADDITIONAL_STAFF_ROLES -- Comma-separated role IDs with staff-level permissions

Priority Levels

Variable Default Description
PRIORITY_ENABLED false Enable the priority system
DEFAULT_PRIORITY normal Default priority for new tickets
PRIORITY_HIGH_EMOJI 🔴 Emoji for high-priority tickets
PRIORITY_MEDIUM_EMOJI 🟡 Emoji for normal/medium-priority tickets (default level is normal)
PRIORITY_LOW_EMOJI 🟢 Emoji for low-priority tickets

Claiming Options

Variable Default Description
AUTO_UNCLAIM_ENABLED false Automatically unclaim tickets after inactivity
AUTO_UNCLAIM_AFTER_HOURS 24 Hours before auto-unclaim triggers
ALLOW_CLAIM_OVERWRITE false Allow claiming an already-claimed ticket
CLAIM_TIMEOUT_ENABLED false Enable claim timeout
CLAIM_TIMEOUT_HOURS 48 Hours before a claim times out

Channel rename rate limit

Ticket channels are renamed automatically when you claim, unclaim, escalate, or deescalate. Discords API allows 2 channel renames per 10 minutes per channel. The bot enforces this: if the limit is reached, the rename is skipped and the channel gets:

Channel renamed too quickly. Try again <t:unlock_timestamp:R>.

The timestamp is a Discord relative-time marker (e.g. “in 8 minutes”). After the window resets, the next claim/unclaim/escalate/deescalate can rename again.

Button & Embed Customization

Variable Default Description
BUTTON_LABEL_CLOSE Close Ticket Label for the close button
BUTTON_LABEL_CLAIM Claim Label for the claim button
BUTTON_LABEL_UNCLAIM Unclaim Label for the unclaim button
BUTTON_EMOJI_CLOSE 🔒 Emoji on the close button
BUTTON_EMOJI_CLAIM 📌 Emoji on the claim button
BUTTON_EMOJI_UNCLAIM 🔓 Emoji on the unclaim button
EMBED_COLOR_OPEN 0x00FF00 Embed color for open tickets
EMBED_COLOR_CLOSED 0xFF0000 Embed color for closed tickets
EMBED_COLOR_CLAIMED 0xFFFF00 Embed color for claimed tickets
EMBED_COLOR_ESCALATED 0xFF6600 Embed color for escalated tickets
EMBED_COLOR_INFO 0x1e2124 Embed color for info messages (and embeds next to ticket buttons)

Game List

Set GAME_LIST to a comma-separated list of game names. The bot uses this list for auto-detection from email subjects/bodies:

GAME_LIST=Project Zomboid, Satisfactory, Palworld, Minecraft, Valheim, ...

Running the Bot

# Start the bot
npm start

# Or directly
node broccolini-discord.js

On startup the bot will:

  1. Validate required environment variables
  2. Connect to MongoDB (with automatic reconnection)
  3. Register all slash commands to the configured guild
  4. Begin polling Gmail every 30 seconds
  5. Start background jobs (auto-close, reminders, auto-unclaim)
  6. Launch an Express healthcheck server

Note: Changing .env requires restarting the bot. Slash commands are registered on startup; if commands dont update, restart the bot to re-register.

Test Environment

To try config changes without affecting production, use a test env. Copy .env.test.example to .env.test, fill it with test-only values (e.g. test guild, test MongoDB database), and run:

npm run start:test

Other test scripts: npm run test-mongodb:test. After confirming behavior in test, migrate only the desired variables to .env. See ENV_AND_SECURITY.md for the full workflow, security checklist, and agent rules.

To test the MongoDB connection from the repo root: npm run test-mongodb.


Discord Commands

Ticket Management

Command Description
/claim Claim the current ticket
/unclaim Release your claim on the current ticket
/close Close the current ticket (with confirmation)
/force-close Close the current ticket without confirmation
/priority <level> Set ticket priority (low, normal, medium, high). Posts: upgraded to [Emoji][Level][Emoji], downgraded to..., or returned to Normal. Email sent when set to high.
/topic <text> Set the ticket channel topic
/escalate [reason] [tier] Escalate the ticket to tier 2 or 3 (optional tier; buttons also available)
/deescalate De-escalate the ticket one step

User & Channel Management

Command Description
/add <user> Add a user to the current ticket channel
/remove <user> Remove a user from the current ticket channel
/transfer <staff> Transfer the ticket to another staff member
/move <category> Move the ticket to a different category

Tags & Saved Responses

Command Description
/tag Set ticket category (dropdown: ⬇️ Server Down, Stuck Restarting, 📵 Can't Connect, 🐌 Server Lag, 💳 Billing, 💸 Refund Request, 🔧 Mod Help, 💾 Backup Restore, 🌍 World / Save, ⚙️ Server Config). Posts: Your ticket has been categorized as [Emoji][Tag][Emoji].
/response send <name> Send a saved response (autocomplete-enabled)
/response create <name> <content> Create a new saved response
/response edit <name> <content> Edit an existing saved response
/response delete <name> Delete a saved response
/response list List all saved responses

Utilities

Command Description
/panel [channel] [type] [title] [description] Deploy a ticket-creation panel (type: thread, category, or both)
/email-routing Switch where new email tickets are created (threads or category channels)
/accountinfo <email or discord> Look up a user's account information
/search <query> Search tickets
/stats Show bot statistics and analytics
/backup Export full ticket list to a .txt file in the backup/export channel
/export [status] [limit] Export tickets (optional filter and limit) to a .txt file in the backup/export channel
/help Display the command reference

Context Menus

Menu Description
Create Ticket From Message Right-click a message to create a ticket from it

Tag & Response System

Ticket category (/tag)

Use /tag in a ticket channel and pick a category from the dropdown (e.g. ⬇️ Server Down, 💳 Billing, 🔧 Mod Help). The bot posts: Your ticket has been categorized as [Emoji][Tag][Emoji]. Channel name is not changed.

Saved response tags (/response)

Saved responses are reusable templates stored in MongoDB. Use /response send, /response create, etc. They support dynamic variables that are replaced at send time:

Variable Resolves To
{ticket.user} Ticket sender's name
{ticket.email} Ticket sender's email
{ticket.number} Ticket number
{ticket.subject} Ticket subject line
{staff.name} Current staff member's display name
{staff.mention} Current staff member's mention
{server.name} Discord server name
{date} Current date
{time} Current time

Example:

/response create name:greeting content:Hi {ticket.user}! Thanks for reaching out about "{ticket.subject}". I'm {staff.name} and I'll be helping you today.

Panel System

The panel system allows users to create tickets directly from Discord without sending an email.

  1. Deploy a panel: /panel #support title:Need Help? description:Click below to open a ticket!
  2. Users click the Open Ticket button
  3. A modal form appears asking for subject, description, and priority
  4. On submission, a ticket channel is created with all the same features as email tickets

Project Structure

broccolini-bot/
├── broccolini-discord.js       # Entry point - initializes bot, events, and jobs
├── config.js                  # Environment variable loading and CONFIG export
├── db-connection.js           # MongoDB connection with reconnect logic
├── models.js                  # Mongoose schemas (Ticket, User, Tag, etc.)
├── utils.js                   # Text processing, game detection, template vars
├── gmail-poll.js              # Gmail polling loop and ticket creation
├── game-options.json          # Game configuration data
│
├── commands/
│   └── register.js            # Slash command and context menu registration
│
├── handlers/
│   ├── accountinfo.js         # /accountinfo command and button handler
│   ├── analytics.js           # In-memory analytics and error tracking
│   ├── buttons.js             # Button interactions (claim, close, priority, etc.)
│   ├── commands.js            # All slash command handlers
│   ├── messages.js            # Discord → Gmail reply forwarding
│   └── setup.js               # Guild setup / configuration flow
│
├── services/
│   ├── debugLog.js            # Structured debug logging
│   ├── gmail.js                # Gmail OAuth2, send replies, closure emails
│   ├── guildSettings.js       # Guild-specific settings (DB + cache)
│   └── tickets.js              # Ticket CRUD, auto-close, reminders, auto-unclaim
│
├── scripts/
│   ├── backup-env.js           # Copy .env to .env.backup
│   └── test-mongodb.js         # MongoDB connection test
│
├── docs/                      # Additional documentation (QUICKSTART, MONGODB_SETUP, ENV_AND_SECURITY, etc.)
├── .env                       # Environment variables (not committed)
├── package.json
└── package-lock.json

Database Schema

The bot uses MongoDB via Mongoose. Key collections:

Collection Purpose
Ticket Core ticket data: Gmail thread ID, Discord channel ID, sender info, status, priority, claimed-by, timestamps
TicketCounter Auto-incrementing ticket numbers per sender
Transcript Transcript message references for closed tickets
Tag Saved response templates (name, content, creator)
CloseRequest Tracks pending close confirmations
User Website user accounts (email, Discord ID, linked servers)
Host Game server/host metadata and metrics
DashboardMetrics Aggregated dashboard statistics
ErrorLog Persisted error records

API Integrations

Gmail API

  • Authentication: OAuth2 with Client ID, Client Secret, and Refresh Token
  • Polling: users.messages.list for unread messages in the primary inbox
  • Reading: users.messages.get to fetch full message content
  • Sending: users.messages.send for threaded replies and closure emails

Discord API (discord.js v14)

  • Intents: Guilds, GuildMessages, MessageContent, GuildMembers
  • Interactions: Slash commands, buttons, modals, context menus, autocomplete
  • Channels: Create/delete ticket channels, manage permissions per user

Healthcheck

An Express server runs on the port defined by DISCORD_ONLY_PORT (default: 5000).

GET /  →  "Active"

Use this endpoint for uptime monitoring or container health probes. Optional: set HEALTHCHECK_HOST=127.0.0.1 in .env to bind the healthcheck server to localhost only; omit to listen on all interfaces.


Documentation

Additional guides and reference docs live in docs/. See docs/README.md for the full index.

Doc Description
QUICKSTART Get started in a few minutes: first response, panel, tags, priority
ENV_AND_SECURITY Test env workflow, security checklist, agent rules
MONGODB_SETUP MongoDB connection, schemas, and testing
PROJECT_STRUCTURE File and directory layout
PROPOSAL Roadmap and possible next steps
PHASE_FEATURES Phased feature list and variables
FEATURES_SUMMARY · NEW_FEATURES Feature overview and changelog
DISCORD_API_IMPROVEMENTS · DISCORD_API_VALIDATION Discord API implementation notes

Troubleshooting

Slash commands not appearing in Discord

  • Commands are registered per-guild on startup. Wait up to one hour for Discord to propagate.
  • Verify DISCORD_APPLICATION_ID and DISCORD_GUILD_ID are correct.
  • Restart the bot.

Gmail polling not working

  • Ensure GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, and REFRESH_TOKEN are set correctly.
  • The refresh token may have expired -- regenerate it via the Google OAuth2 Playground.
  • Check that the Gmail API is enabled in your Google Cloud Console project.

MongoDB connection failures

  • Verify MONGODB_URI is correct and the database is accessible.
  • Run npm run test-mongodb from the repo root to test the connection.
  • If using MongoDB Atlas, ensure your IP is whitelisted.
  • The bot has automatic reconnection -- check logs for retry attempts.

Tickets not creating

  • Check that TICKET_CATEGORY_ID points to a valid Discord category.
  • Ensure the bot has Manage Channels and View Channel permissions in that category.
  • Review the logging channel for error messages.

Modal not appearing when clicking "Open Ticket"

  • Verify the bot has proper guild permissions.
  • Try in a different channel.
  • Restart the bot.

References

This project builds on or references the following:

Technology Description Links
discord.js Node.js library for the Discord API; used for the bot, slash commands, buttons, and embeds. discord.js · GitHub
Discord Tickets Open-source ticket bot; referenced for patterns and feature inspiration (panels, tags, transcripts). Discord Tickets · GitHub
Node.js JavaScript runtime used to run the bot. Node.js
MongoDB Database for tickets, transcripts, and persistence (via Mongoose). MongoDB
Express HTTP server for the healthcheck endpoint. Express
Mongoose MongoDB ODM used for schemas and connection handling. Mongoose
Google APIs (googleapis) Gmail API client for polling and sending email. Google APIs Node.js

License

ISC

Description
No description provided
Readme 2.4 MiB
Languages
JavaScript 85.2%
CSS 8.1%
HTML 6.6%