A Discord bot that verifies members using their educational email addresses. Built for the MSU Denver Computer Science Department Discord server.
Operational docs: SECURITY.md (threat model + audit checklist) · DEPLOYMENT.md (runbook).
- A new member joins the Discord server and is automatically assigned a quarantine role (limited access)
- The member uses
/verify email:student@msudenver.eduto request a verification code - The bot validates the email domain and sends a code via Amazon SES
- The member uses
/verifycode code:ABC12345to submit the code - On success, the quarantine role is removed and a verified role is added
| Command | Description |
|---|---|
/verify email:<address> |
Request a verification code |
/verifycode code:<code> |
Submit your verification code |
/admin domain-add <domain> |
Add an allowed email domain |
/admin domain-remove <domain> |
Remove an allowed email domain |
/admin domain-list |
List allowed email domains |
/admin checkemail <email> |
Check verification history for an email |
/admin resetemail <email> |
Reset an email's verification count |
/admin storage-info |
Display storage configuration |
- Node.js 20+ (22 recommended)
- Docker (for local email testing with LocalStack)
- AWS Account with SES and DynamoDB access (production only)
- Discord Bot Application created in the Developer Portal
# Clone the repo
git clone https://github.com/msu-denver/discord-email-verification.git
cd discord-email-verification
# Install dependencies
npm install
# Copy and configure environment variables
cp .env.example .env
# Edit .env with your Discord and AWS credentials
# For local development, set these in .env:
# USE_LOCAL_STORAGE=true (uses local JSON files instead of DynamoDB)
# AWS_ENDPOINT_URL=http://localhost:4566 (routes SES to LocalStack)
# Run the bot
npm start
# Run tests
npm test
# Run tests with coverage
npm run test:coverageTo test email sending locally without a real AWS account, we use LocalStack to simulate Amazon SES. This requires Docker.
# Start the LocalStack container (simulates SES on localhost:4566)
docker compose up -d
# The seed script automatically verifies email identities for local dev.
# You can verify it's working:
docker exec verification-bot-localstack awslocal ses list-identities
# Make sure your .env has:
# AWS_ENDPOINT_URL=http://localhost:4566
# SES_FROM_EMAIL=verification@msudenver.edu
# Start the bot
npm start
# After a user runs /verify, inspect the captured email:
docker exec verification-bot-localstack ls /var/lib/localstack/state/ses/
docker exec verification-bot-localstack cat /var/lib/localstack/state/ses/<email-id>.json
# Stop LocalStack when done
docker compose downLocalStack captures all sent emails as JSON files, so you can verify email content, formatting, and delivery without sending real emails.
See .env.example for a fully documented list. Key variables:
| Variable | Description |
|---|---|
DISCORD_BOT_TOKEN |
Bot token from Discord Developer Portal |
SERVER_ID |
Your Discord server (guild) ID |
QUARANTINE_ROLE_ID |
Role assigned to unverified members |
VERIFIED_ROLE_ID |
Role assigned after email verification |
ADMIN_ROLE_ID |
Role required for admin commands |
SES_FROM_EMAIL |
Verified sender email in Amazon SES |
USE_LOCAL_STORAGE |
true for local file storage, false for DynamoDB |
AWS_ENDPOINT_URL |
LocalStack endpoint for local dev (e.g., http://localhost:4566) |
src/
index.js Entry point — initializes storage, events, and login
config.js Centralized configuration from environment variables
emailer.js Amazon SES email delivery
storage.js DynamoDB + LocalStorage backends (strategy pattern)
events.js Discord event handlers (ready, memberJoin, interactions)
utils.js Utility functions (code generation, validation)
commands/
index.js Slash command registration with Discord API
verify.js /verify and /verifycode handlers
admin.js /admin subcommand handlers
infrastructure/
template.yaml CloudFormation template for AWS deployment
scripts/
localstack/
seed_ses.sh Auto-seeds SES email identities for local dev
docker-compose.yml LocalStack container for local SES simulation
- DynamoDB (production): Single-table design with PK/SK pattern. Pay-per-request billing.
- Local files (development): JSON files in a
data/directory. SetUSE_LOCAL_STORAGE=true.
The infrastructure/template.yaml CloudFormation template creates everything needed:
- DynamoDB table
- SES email identity
- EC2 instance (t3.micro) with PM2
- IAM role with least-privilege permissions
- Security group (SSH only)
- SSM Parameter Store for the bot token
aws cloudformation deploy \
--template-file infrastructure/template.yaml \
--stack-name discord-verification-bot \
--capabilities CAPABILITY_NAMED_IAM \
--parameter-overrides \
KeyPairName=your-key-pair \
SESFromEmail=verification@yourdomain.edu \
DiscordBotToken=your-bot-token \
ServerID=your-server-id \
QuarantineRoleID=your-role-id \
VerifiedRoleID=your-role-id \
AdminRoleID=your-role-id \
VerificationChannelID=your-channel-id \
WelcomeChannelID=your-channel-idNew AWS accounts start in the SES sandbox, which only allows sending to verified email addresses. For testing, verify your test recipient emails. For production, request production access.
- Go to the Discord Developer Portal
- Click New Application and name it
- Go to Bot tab > enable Server Members Intent and Message Content Intent
- Copy the bot token for your
.env - Go to OAuth2 > URL Generator
- Select scopes:
bot,applications.commands - Select permissions:
Manage Roles,Send Messages,Read Message History - Open the generated URL to invite the bot to your server
The bot's role must be above the Quarantine and Verified roles in your server's role list. Otherwise it will get a "Missing Permissions" error when trying to add/remove roles. Go to Server Settings > Roles and drag the bot's role above the roles it needs to manage.
Enable Developer Mode in Discord (User Settings > Advanced > Developer Mode), then right-click any role, channel, or server to copy its ID.
npm test # Run all tests
npm run test:watch # Watch mode
npm run test:coverage # Coverage report (target: 80%+)- 30-minute code expiration
- Maximum 3 attempts per code
- Maximum 2 verifications per email address
- 5-minute throttle between code requests
- Email domain whitelist enforcement
- Admin role required for management commands
We welcome contributions. Start with .github/CONTRIBUTING.md for the development setup and PR flow, and our Code of Conduct. For vulnerability reports, see .github/SECURITY.md — please do not open public issues for security bugs.
- Original bot: Luke J Farchione (2025)
- Migration & enhancements: MSU Denver CS Department (2026)
MIT, with dual copyright (Luke J Farchione 2025 + Metropolitan State University of Denver 2026).
