A self-hosted Discord ModMail bot that lets your members DM the bot to open a private support ticket with your staff team, built with discord.js v14 and local SQLite storage.
⭐ If this project is useful to you, please consider starring the repo! It helps other server owners find it and motivates future updates.
- Features
- Getting started
- Configuration
- Bot permissions & intents
- Commands
- Project structure
- Ideas & roadmap
- Contributing
- License
- DM ↔ thread relay: a user's DM opens a private thread for staff, and every message flows both ways in real time.
- Confirmation flow: the first DM from a user asks for a Yes/No confirmation before a ticket is created, to avoid accidental or spam tickets.
- Live edit sync: if a user or a staff member edits a message they sent, the relayed copy on the other side is edited too, so both sides always see the same conversation.
- Ticket panels:
/config-ticketlets an admin drop a public button in any channel that opens a modal (reason) and creates a ticket, fully configurable (title, description, DM message, closed-DM message). - Typing indicators: typing in DM shows up as typing in the thread, and vice versa.
- Delivery reactions: every relayed message gets a ✅ or ❌ reaction depending on whether delivery succeeded.
- Block / unblock: staff can block a user from opening new tickets, right from buttons on the ticket control panel or with
/blockand/unblock. - Saved replies:
/snippet add|remove|list|sendlets staff store frequently used answers and send them to a ticket in one command, with autocomplete on saved names. - i18n: every bot-generated message (errors, buttons, confirmations, control panel, etc.) is translated, with
/setlangletting an admin switch the bot's language (English/French) at runtime, no restart needed. - Ticket transcripts: closing a ticket generates an HTML transcript of the whole thread, attached in the thread, sent to the user by DM, and saved to disk so it survives even if the thread is later deleted.
- Anti-spam auto-ignore: a user who keeps DMing without answering the confirmation prompt gets automatically ignored, with an optional log channel.
- Components V2 control panel: a rich control panel is posted in every thread (Close / Block / Unblock buttons).
- SQLite persistence: tickets, panels, blocklist, saved replies, bot settings and message mappings survive restarts; upgrading the bot never touches or drops existing data (new tables are additive only).
- Legacy JSON import: if you're migrating from an older JSON-based version, it's imported automatically the first time the SQLite database is empty.
- Node.js
>= 18.19.1 - A Discord application with a bot user (Discord Developer Portal)
git clone https://github.com/Cut0x/ModMail.git
cd ModMail
npm installcp .env.example .envFill in .env (see Configuration below), then run:
npm startUse npm run dev during development: it restarts automatically on file changes (node --watch).
| Variable | Description |
|---|---|
DISCORD_TOKEN |
Your bot's token |
MODMAIL_GUILD_ID |
The server (guild) ID the bot operates in |
MODMAIL_THREADS_CHANNEL_ID |
A regular text channel (GuildText) where ticket threads are created |
| Variable | Description | Default |
|---|---|---|
STAFF_ROLE_ID |
Role mentioned when a new ticket opens; also used to detect staff members | (none) |
BOT_ACTIVITY_PLAYING |
Sets the bot's status to "Playing …" | (none) |
THREAD_AUTO_ARCHIVE_MINUTES |
60, 1440, 4320 or 10080 |
1440 |
MODMAIL_SQLITE_FILE |
Path to the SQLite database file | ./data/modmail.sqlite |
MODMAIL_DB_FILE |
Legacy JSON file, imported automatically once if the SQLite DB is empty | ./data/modmail.json |
MODMAIL_TRANSCRIPTS_DIR |
Folder where HTML ticket transcripts are saved on close | ./data/transcripts |
LOGS_IGNORED_MP_USER_CHANNEL |
Channel where auto-ignored DM spammers are logged | (none) |
REACTION_SUCCESS_EMOJI |
Reaction added when a message is relayed successfully (unicode or <:name:id>) |
✅ |
REACTION_FAILURE_EMOJI |
Reaction added when relaying fails | ❌ |
Enable in the Developer Portal → Bot:
- Message Content Intent
- Server Members Intent (required to detect when a member leaves the server while their ticket is open)
Recommended permissions when inviting the bot:
- View Channels
- Send Messages / Send Messages in Threads
- Create Private Threads
- Manage Threads
- Read Message History
- Attach Files
- Use External Emojis (optional, for custom reaction emojis)
| Command | Description |
|---|---|
/close [reason] |
Closes the ticket and notifies the user |
/block [reason] |
Blocks the user from opening new tickets |
/unblock |
Unblocks the user |
/snippet add name:<name> |
Opens a modal to create or update a saved reply |
/snippet remove name:<name> |
Deletes a saved reply |
/snippet list |
Lists all saved replies |
/snippet send name:<name> |
Sends a saved reply to the ticket's user |
/help |
Lists available commands |
| Command | Description |
|---|---|
/config-ticket channel:#channel |
Configures and sends a public ticket-opening panel (requires Administrator) |
/setlang locale:<English|Français> |
Sets the bot's language for all its messages (requires Administrator) |
src/
├── config.js # Environment variables & validation
├── index.js # Entry point, wires everything and logs in
├── i18n/ # Translated bot strings (en/fr) + the t()/setLocale() helpers
├── db/ # SQLite persistence layer (tickets, panels, blocklist, relayed messages, settings)
└── bot/
├── client.js # Discord client instance
├── ui/ # Embeds, modals, buttons, slash command builders
├── tickets/ # Thread lifecycle (create, close, transcript generation)
├── handlers/ # DM / staff message relay + edit sync
├── commands/ # Slash command & modal logic
├── interactions/ # Button / modal routing
└── events/ # Discord event listeners
The codebase is kept intentionally split into small, single-purpose files (each well under 150 lines), so it's easy to navigate, review and extend: a good starting point if you want to add your own feature.
Nothing here is planned or promised, these are just ideas for anyone who wants to open a PR:
- 🗑️ Delete sync: mirror message deletions the same way edits are now mirrored.
- 🏷️ Tags / categories: let staff label tickets (billing, bug report, etc.) for easier triage.
- ⏱️ SLA reminders: ping staff if a ticket has gone unanswered for too long.
- 🕵️ Anonymous staff replies: an option to sign replies as "Staff" instead of a display name.
- 🐳 Docker support: a
Dockerfile+docker-compose.ymlfor easier self-hosting. - ✅ Automated tests: unit tests for the
db/layer and handler logic. - 📊 Basic stats/dashboard: ticket volume, response time, per-staff activity.
Have another idea? Open an issue to discuss it before starting a big PR.
Contributions are welcome!
- Fork the repo and create a branch from
main. - Keep changes focused and files small/single-purpose, matching the existing structure.
- Test your changes locally (
npm startagainst a test server) before opening a PR. - Open a PR describing what changed and why.
If you run into a bug or have a feature request, please open an issue.
ISC (see package.json).
If this project saved you time, a ⭐ on the repo goes a long way. Thanks for checking it out!