← Back to projects
Rustmail

Rustmail

πŸ“…
Rust

Rewriting an entire Discord bot from scratch. In a language I didn’t know yet.

Modmailbot was the go-to solution for Discord modmail systems - 767 stars, 41 contributors, used by thousands of servers. But it was written in JavaScript, hadn’t seen meaningful updates in years, and the codebase was showing its age. I needed a modmail bot, but more importantly, I wanted to learn Rust properly. So I built Rustmail.

Why Rust?

Three reasons pushed me toward Rust:

  1. Learning - Reading tutorials only gets you so far. Building a real project with complex async operations, database interactions, and API integrations forces you to actually understand the language.

  2. Performance - JavaScript bots eat memory. A Rust binary runs for months without restarts, uses minimal resources, and handles concurrent operations without breaking a sweat.

  3. Reliability - Rust’s type system catches bugs at compile time that would crash a JavaScript bot at 3 AM. No more undefined is not a function.

What it does

Rustmail connects users with server staff through private DM conversations. User sends a DM β†’ bot creates a ticket channel β†’ staff responds β†’ bot relays messages back. Simple concept, complex implementation.

Core features

FeatureDescription
Dual-server modeSeparate community and staff servers
Single-server modeEverything on one server
Web panelFull admin dashboard in the browser
REST APIHTTP endpoints for external integrations
10 languagesEN, FR, ES, DE, IT, PT, RU, ZH, JA, KO
Message editingEdit/delete with full change tracking
Scheduled closuresAuto-close tickets after delay
Reminders & alertsNever forget to follow up
Anonymous repliesStaff can respond without revealing identity

Architecture

One binary. Everything included.

The entire application - Discord bot, HTTP server, web panel, database engine - compiles into a single executable. No runtime dependencies, no npm install, no external services. Just drop the binary and a config file on any machine and it runs.

rustmail/
β”œβ”€β”€ rustmail/           # Main bot (Serenity + Axum)
β”œβ”€β”€ rustmail_panel/     # Web UI (Yew/WASM, embedded in binary)
β”œβ”€β”€ rustmail_types/     # Shared types
β”œβ”€β”€ migrations/         # SQLite schema (embedded)
└── Dockerfile

The web panel is compiled to WebAssembly and embedded directly into the binary. SQLite migrations are baked in at compile time. A single Rust process handles Discord gateway events (Serenity), serves the REST API and panel (Axum), and manages the database (SQLx) - all concurrently via Tokio.

Key dependencies

  • serenity - Discord API client
  • axum - HTTP server
  • sqlx - Async database
  • tokio - Async runtime
  • yew - WASM frontend framework

Commands

Both slash commands and text commands. Same functionality, different interface.

# General
/help                              # Display available commands
/ping                              # Check bot responsiveness

# Ticket management
/new_thread <user>                 # Create ticket for user
/close [time] [silent] [cancel]    # Close ticket (scheduled or cancel)
/force_close                       # Force close an orphan ticket
/move_thread <category>            # Move to different category

# Messaging
/reply <content> [attachment] [anonymous]  # Reply to user
/anonreply <content>               # Anonymous reply (text command only)
/edit <message_id> <new_content>   # Edit previous message
/delete <message_id>               # Delete message
/recover                           # Force message recovery
/snippet <key>                     # Use a saved response

# Staff
/add_staff <user>                  # Add staff to ticket
/remove_staff <user>               # Remove staff from ticket
/take                              # Claim ticket
/release                           # Release assignment

# Utilities
/add_reminder <time> [content]     # Set reminder with optional message
/remove_reminder <reminder_id>     # Cancel a reminder
/alert [cancel]                    # Get notified on reply (or cancel)
/logs                              # View ticket history
/id                                # Display user's Discord ID
/status [new_status]               # View or change bot status

Web Panel

Full administration without touching Discord:

  • Dashboard with statistics
  • Real-time ticket management
  • Configuration editor
  • API key management
  • Permission system by user/role
  • OAuth2 authentication via Discord

The panel is served directly by the bot on port 3002. Works behind reverse proxies (Nginx, Caddy, Traefik) with SSL termination.

REST API

External integrations through HTTP:

# Create ticket from external source
curl -X POST https://panel.example.com/api/externals/tickets/create \
  -H "Content-Type: application/json" \
  -H "X-API-Key: rustmail_your_key_here" \
  -d '{"discord_id": "123456789012345678"}'

Endpoints for bot control, configuration, tickets, API keys, and administration. Session-based auth for the panel, API keys for external tools.

Configuration

TOML-based config with an online generator:

[bot]
token = ""
status = "DM FOR SUPPORT"
welcome_message = "Your message has been received by the Staff team! We will respond as soon as possible."
close_message = "Thank you for contacting support! Your ticket is now closed."
typing_proxy_from_user = true
typing_proxy_from_staff = true
enable_logs = true
enable_features = true
enable_panel = true
client_id = 0
client_secret = ""
redirect_url = ""
timezone = "Europe/Paris"
panel_super_admin_users = []
panel_super_admin_roles = []

[bot.mode]
type = "single"
guild_id = 0

[command]
prefix = "!"

[thread]
inbox_category_id = 0
embedded_message = true
user_message_color = "3d54ff"
staff_message_color = "ff3126"
system_message_color = "00ff00"
block_quote = true
time_to_close_thread = 5
create_ticket_by_create_channel = true
close_on_leave = false
auto_archive_duration = 10080

[language]
default_language = "en"
fallback_language = "en"
supported_languages = [
    "en",
    "fr",
]

[error_handling]
show_detailed_errors = false
log_errors = true
send_error_embeds = true
auto_delete_error_messages = true
error_message_ttl = 30
display_errors = true

[notifications]
show_success_on_edit = false
show_partial_success_on_edit = true
show_failure_on_edit = true
show_success_on_reply = false
show_success_on_delete = false
show_success = true
show_error = true

[reminders]
embed_color = "ffb800"

[logs]
show_log_on_edit = true
show_log_on_delete = true

Deployment

Drop and run. That’s it.

# Just two files needed
./rustmail          # The binary
./config.toml       # Your configuration

# Or Docker
docker run -v ./config.toml:/app/config.toml ghcr.io/rustmail/rustmail:latest

No Node.js, no Python, no Java runtime. No npm install that downloads half the internet. The SQLite database is created automatically on first run. Works on Linux, macOS, and Windows.

What I learned

Building Rustmail taught me more about Rust than any book could:

  • Async Rust - Tokio, futures, async/await patterns. Handling concurrent Discord events without data races.
  • Error handling - Result<T, E> everywhere. Propagating errors properly instead of swallowing them.
  • Ownership - Fighting the borrow checker until it clicked. Now it feels like a safety net, not a constraint.
  • Macros - SQLx compile-time query checking. Proc macros for command definitions.
  • WASM - Building a full frontend in Rust with Yew. Sharing types between backend and frontend.

Compared to modmailbot

Aspectmodmailbot (JS)Rustmail
LanguageJavaScriptRust
DeploymentNode.js + npm installSingle binary
Memory usageHighMinimal
Web panelNoYes
REST APINoYes
Multi-languagePlugin requiredNative (10 langs)
Type safetyRuntime errorsCompile-time

Takeaways

  • Rewriting a project in a new language is the best way to learn that language
  • Rust’s learning curve is steep, but the payoff is real - no 3 AM crashes
  • A web panel changes everything for user experience
  • SQLite is underrated for single-instance applications
  • Good documentation (MdBook) matters as much as good code
  • Open source under AGPL-3.0 means anyone can self-host and contribute