A comprehensive security solution for preventing registration with privileged, reserved, or system-critical email addresses. Includes backend API, PostgreSQL database, admin management UI, and integration examples for your registration endpoint.
- Three Matching Types: Exact match (localpart), domain-level blocking, and regex patterns
- Multi-Domain Support: Apply global rules or domain-specific restrictions
- Audit Trail: Complete logging of all blocklist changes with timestamps and user attribution
- Flexible Management: Add/edit/delete entries, bulk import/export (CSV)
- Pre-Populated Defaults: Includes common reserved addresses (admin, support, noreply, etc.)
- Quick Start
- Architecture
- Installation
- Configuration
- API Documentation
- Admin Dashboard
- Integration Guide
- Testing
- Deployment
- Node.js 18+
- PostgreSQL 12+
- npm or yarn
cd backend
npm install
cp .env.example .env
# Edit .env with your database credentials
npm run db:migrate
npm run devcd frontend
npm install
npm run devVisit http://localhost:3001 to access the admin dashboard.
- Runtime: Node.js with Express.js
- Language: TypeScript
- Database: PostgreSQL with audit triggers
- API: RESTful API with JSON responses
- Framework: React 18 with TypeScript
- Build Tool: Vite
- Styling: Tailwind CSS
- UI Components: Custom with Lucide icons
restricted_emails
βββ id (UUID, PK)
βββ pattern (VARCHAR, the email pattern)
βββ type (VARCHAR: 'exact' | 'domain' | 'regex')
βββ applicable_domains (TEXT[], NULL for all domains)
βββ reason (TEXT, optional)
βββ created_by (VARCHAR)
βββ created_at (TIMESTAMP)
βββ updated_at (TIMESTAMP)
βββ is_active (BOOLEAN)
βββ ...
restricted_emails_audit
βββ id (UUID, PK)
βββ restricted_email_id (UUID, FK)
βββ action (VARCHAR: 'CREATE' | 'UPDATE' | 'DELETE')
βββ pattern_before / pattern_after
βββ type_before / type_after
βββ domains_before / domains_after
βββ reason_before / reason_after
βββ changed_by (VARCHAR)
βββ change_timestamp (TIMESTAMP)
βββ ...
cd /path/to/Restricted-Registered-Emailscd backend
npm install
cp .env.example .envEdit .env with your PostgreSQL credentials:
DATABASE_URL=postgresql://postgres:password@localhost:5432/restricted_emails_db
DB_HOST=localhost
DB_PORT=5432
DB_NAME=restricted_emails_db
DB_USER=postgres
DB_PASSWORD=yourpassword
PORT=3000
NODE_ENV=development
CORS_ORIGIN=http://localhost:3001npm run db:migrateThis will create all tables, indexes, triggers, and seed default restricted emails.
npm run devServer will start on http://localhost:3000
cd ../frontend
npm install
npm run devAccess the admin dashboard at http://localhost:3001
| Variable | Description | Default |
|---|---|---|
DATABASE_URL |
PostgreSQL connection string | - |
DB_HOST |
Database host | localhost |
DB_PORT |
Database port | 5432 |
DB_NAME |
Database name | restricted_emails_db |
DB_USER |
Database user | postgres |
DB_PASSWORD |
Database password | postgres |
PORT |
Server port | 3000 |
NODE_ENV |
Environment | development |
CORS_ORIGIN |
CORS allowed origin | http://localhost:3001 |
JWT_SECRET |
(Optional) JWT secret for auth | your-secret-key |
Edit frontend/vite.config.ts to change the API proxy target:
server: {
proxy: {
'/api': {
target: 'http://localhost:3000', // Change to your backend URL
changeOrigin: true,
},
},
},http://localhost:3000/api/blocklist
GET /api/blocklist?page=1&pageSize=20&search=admin&type=exact&domain=example.comQuery Parameters:
page(integer, default: 1) - Page numberpageSize(integer, default: 20) - Entries per pagesearch(string) - Search by patterntype(string) - Filter by type (exact, domain, regex)domain(string) - Filter by applicable domain
Response:
{
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"pattern": "admin",
"type": "exact",
"applicableDomains": null,
"reason": "Reserved for admin accounts",
"createdBy": "system",
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z",
"isActive": true
}
],
"total": 150,
"page": 1,
"pageSize": 20,
"totalPages": 8
}GET /api/blocklist/{id}POST /api/blocklist
Content-Type: application/json
{
"pattern": "admin",
"type": "exact",
"applicableDomains": null,
"reason": "Reserved for administrative accounts"
}Response: 201 Created with created entry
PUT /api/blocklist/{id}
Content-Type: application/json
{
"reason": "Updated reason"
}DELETE /api/blocklist/{id}Response: 204 No Content (soft delete - marks as inactive)
POST /api/blocklist/validate
Content-Type: application/json
{
"email": "admin@example.com",
"domain": "example.com"
}Response:
{
"isBlocked": true,
"blockedPattern": {
"id": "...",
"pattern": "admin",
"type": "exact",
"reason": "Reserved for admin accounts"
},
"reason": "This email address is reserved for system use."
}POST /api/blocklist/bulk-import
Content-Type: application/json
{
"csvContent": "pattern,type,applicable_domains,reason\nadmin,exact,,Admin account\nsupport,exact,,Support team",
"createdBy": "admin"
}CSV Format:
pattern,type,applicable_domains,reason
admin,exact,,Reserved for admin accounts
support,exact,,Reserved for support team
noreply,exact,,Reserved for system notifications
security,exact,,Reserved for security team
help,exact,,Reserved for help deskResponse:
{
"success": true,
"inserted": 5,
"errors": [],
"data": [...]
}GET /api/blocklist/exportResponse: CSV file download
GET /api/blocklist/{id}/audit?page=1&pageSize=20GET /api/blocklist/audit/all?page=1&pageSize=20&action=CREATEThe React dashboard provides a user-friendly interface for managing the blocklist.
-
View Blocklist
- Paginated table with sorting
- Search by pattern
- Filter by type (exact, domain, regex)
- Filter by applicable domain
-
Add Entry
- Pattern input
- Match type selector
- Domain picker (multi-select or global)
- Reason/notes field
-
Edit Entry
- Inline editing modal
- Update any field
- Automatic audit logging
-
Delete Entry
- Soft delete with confirmation
- Preserves history in audit log
-
Bulk Operations
- CSV import with preview
- Drag-and-drop file upload
- CSV export with templates
- Error reporting for failed imports
-
Audit Log View
- Track all changes
- View who changed what and when
- Filter by action type
See backend/src/routes/register-example.ts for a complete example.
import { EmailBlocklistService } from '../services/emailBlocklist';
import { getPool } from '../database/connection';
const blocklistService = new EmailBlocklistService(getPool());app.post('/api/register', async (req, res) => {
const { email, password, domain } = req.body;
// Validate email format
if (!isValidEmailFormat(email)) {
return res.status(400).json({ error: 'Invalid email format' });
}
// β KEY STEP: Check if email is blocked
const blockCheck = await blocklistService.isEmailBlocked(email, domain);
if (blockCheck.isBlocked) {
return res.status(403).json({
error: blockCheck.reason || 'This email address cannot be used for registration',
code: 'EMAIL_RESTRICTED',
});
}
// Check if already registered
const existing = await findUserByEmail(email);
if (existing) {
return res.status(409).json({ error: 'Email already registered' });
}
// Create user account
const user = await createUser({ email, password });
res.status(201).json({ userId: user.id });
});app.post('/api/validate-email', async (req, res) => {
const { email, domain } = req.body;
const isBlocked = await blocklistService.isEmailBlocked(email, domain);
const isRegistered = await checkUserExists(email);
res.json({
isValid: !isBlocked.isBlocked && !isRegistered,
isBlocked: isBlocked.isBlocked,
message: isBlocked.reason,
});
});- Email Validation: Before creating user account in registration/signup endpoint
- API Endpoints: Make requests to
POST /api/blocklist/validate - Frontend: Show validation errors in signup form
- Logging: Log blocked attempts for security monitoring
cd backend
npm testnpm test -- emailBlocklist.test.tsnpm test -- --coverage- β Exact match email validation
- β Domain-level blocking
- β Regex pattern matching
- β Case-insensitive matching
- β Whitespace trimming
- β Domain-specific restrictions
- β Invalid regex handling
- β Edge cases (multiple @, missing @, etc.)
- Update
.envwith production database credentials - Set
NODE_ENV=production - Enable HTTPS/SSL
- Configure CORS for production domain
- Set up database backups
- Configure logging and monitoring
- Rate limit registration endpoint
- Set up audit log retention policy
- Deploy backend and frontend separately
- Test integration with your registration endpoint
Create backend/Dockerfile:
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY dist ./dist
EXPOSE 3000
CMD ["node", "dist/index.js"]Build and run:
docker build -t restricted-emails-api .
docker run -p 3000:3000 -e DATABASE_URL=... restricted-emails-apiDevelopment:
NODE_ENV=development
CORS_ORIGIN=http://localhost:3001Production:
NODE_ENV=production
CORS_ORIGIN=https://yourdomain.com
DATABASE_URL=postgresql://prod_user:secure_password@prod-db-host:5432/restricted_emails_dbMatches the local part of the email exactly (case-insensitive).
Examples:
- Pattern:
adminβ Blocks:admin@any.com,ADMIN@example.com - Pattern:
supportβ Blocks:support@example.com,support@other.io
Matches specific domain(s).
Examples:
- Pattern:
example.comβ Blocks:anyone@example.com,user@sub.example.com - Pattern:
@domain.comβ Blocks:any@domain.com
Uses regular expressions for complex matching rules.
Examples:
- Pattern:
^noreply-.*@example\.com$β Blocks:noreply-system@example.com,noreply-alerts@example.com - Pattern:
admin.*@(example|test)\.comβ Blocks:admin.main@example.com,adminuser@test.com, etc.
Pattern: noreply | Type: Exact | Domains: None (global)
Pattern: suspicious-domain.com | Type: Domain | Domains: None
Pattern: admin | Type: Exact | Domains: example.com
Pattern: ^(noreply|mailer|daemon)-.* | Type: Regex | Domains: domain1.com, domain2.com
Out-of-the-box, these addresses are blocked globally:
admin,administrator- Administrative accountssupport,help- Support teamsnoreply,no-reply- Notification systemssecurity,abuse- Security/abuse reportingcontact,info- Generic addressessales- Sales teamroot,postmaster,mailer-daemon- System accounts
Edit or delete these from the admin dashboard as needed.
Error: connect ECONNREFUSED 127.0.0.1:5432
Solution: Ensure PostgreSQL is running. Check DB_HOST, DB_PORT, DB_USER, DB_PASSWORD in .env.
Solution: Check the audit log to see which pattern is blocking. The blocklist may be too strict. Adjust domain restrictions if needed.
Solution: Test your regex in a tool like regex101.com. Remember patterns are case-insensitive by default.
Solution: Update CORS_ORIGIN in .env to match your frontend URL.
MIT
For issues, questions, or feature requests, please refer to the integration documentation or contact your security team.