Mandrill Template Manager for the modern web
Built with Next.js 16 β’ Deployed on Cloudflare Workers β‘
- Tree & Table Views - Switch between hierarchical tree and flat table views
- Visual HTML Editor - GrapesJS integration for WYSIWYG editing
- Multisite & Multilanguage Support - Organize templates by site (labels) and locale
- Smart Naming Pattern -
{theme}_{locale}pattern for easy organization - Locale Selection - Choose your working locales in settings (60+ locales supported)
- Missing Translation Indicators - See which locales are missing for each theme with inline red flags
- AI-Powered Translation - Translate templates with space-preserving placeholder protection
- Placeholder Detection - Supports Mailchimp (|VAR|), Handlebars ({{var}}), Global (|GLOBAL:VAR|), Conditionals
- Template Testing - Live preview with merge vars, send test emails, save test scenarios
- Real-time Editing - Live preview and instant updates
- Theme & Locale Filtering - Filter templates by theme, label, and locale
- Clone & Delete - Duplicate templates and manage lifecycle
- 4 Translation Providers - Cloudflare Workers AI (tested β
), Google Translate (tested β
), Azure Translator (not tested
β οΈ ), Crowdin (not testedβ οΈ ) - Placeholder Protection - XML PI tokens preserve placeholders and spacing during translation
- Row-by-Row Translation - Preserves HTML structure while translating text
- Placeholder Validation - Detects missing, added, or corrupted placeholders after translation
- Visual Comparison - Side-by-side original and translated text review
- One-Click Localization - Translate and save as new locale variant
- IndexedDB Storage - Translation settings stored securely client-side
- Live Preview - Real-time template preview with merge variables
- Placeholder Detection - Automatic detection of all placeholders (Mailchimp, Handlebars, Global, Conditionals)
- Test Email Sending - Send test emails with custom merge vars using your Mandrill API key
- Test Scenarios - Save and manage test scenarios with IndexedDB for quick testing
- Visual Placeholder List - See all placeholders used in template with usage count
- Complete Operation Tracking - Logs all template create, update, and delete operations
- Before/After State Capture - Full template state snapshots for every change
- Change Summaries - Field-level change tracking with old and new values
- Bulk Operation Support - Track batch operations with success/failure counts
- Detailed Modal View - Inspect audit logs with tabbed views (Changes, Before, After, Raw JSON)
- User Identification - Track which user performed each operation
- Operation Status - Success, partial, or failure status for each operation
- Cloudflare D1 Storage - Persistent audit logs with Drizzle ORM
- Configurable Retention - Auto-cleanup based on retention policies
- Export Capability - Export audit logs for compliance and reporting
- Message Timeline - Real-time view of all outbound email activity
- Advanced Filtering - Filter by email, subject, tags, status, date range
- Message Details - Inspect full message metadata, recipients, tags, headers
- Status Tracking - Monitor delivery status (sent, queued, rejected, bounced, deferred)
- Performance Metrics - Opens, clicks, timestamps for each message
- Pagination - Efficient handling of large message volumes
- Search - Full-text search across messages
- Analytics with reputation scores
- Delete tags from UI
- Track opens, clicks, bounces, unsubscribes
- Monitor sender performance
- DKIM/SPF validation status
- 7-day & 30-day metrics
- π Dark/Light mode with system detection
- π± Fully responsive design
- π Real-time search & filtering
- πΎ Settings stored in IndexedDB
- β‘ Loading states & skeletons
- π― Parallel routes for simultaneous views
- π¨ Custom teal scrollbars throughout
- β¨ No browser alerts - Beautiful inline notifications and confirmations
- π Elegant error handling with dismissible alerts
- β±οΈ Auto-dismissing success messages (3 seconds)
- π Type-to-confirm for destructive actions
RedDrill is designed for multisite and multilanguage email template management using a smart naming convention:
Example:
welcome_en- Welcome template in Englishwelcome_es- Welcome template in Spanishnewsletter_de- Newsletter template in Germanreceipt_fr- Receipt template in French
Templates can be organized by site using Mandrill's label system:
Example Organization:
- Label:
site-shopβ Templates:welcome_en,welcome_es,order_en,order_es - Label:
site-blogβ Templates:newsletter_en,newsletter_de,digest_en,digest_de - Label:
site-appβ Templates:reset_en,reset_fr,verify_en,verify_fr
The tree view intelligently organizes templates using this pattern:
Mode 1: Theme β Label β Locale
βββ welcome (theme)
β βββ site-shop (label)
β β βββ π¬π§ en
β β βββ πͺπΈ es
β βββ site-app (label)
β βββ π¬π§ en
β βββ π«π· fr
βββ newsletter (theme)
βββ site-blog (label)
βββ π¬π§ en
βββ π©πͺ de
Mode 2: Label β Theme β Locale
βββ site-shop (label)
β βββ welcome (theme)
β β βββ π¬π§ en
β β βββ πͺπΈ es
β βββ order (theme)
β βββ π¬π§ en
β βββ πͺπΈ es
βββ site-blog (label)
βββ newsletter (theme)
β βββ π¬π§ en
β βββ π©πͺ de
βββ digest (theme)
βββ π¬π§ en
βββ π©πͺ de
- Smart Flattening - Single-child nodes automatically flattened for cleaner hierarchy
- Flag Icons - Locale displayed with country flags (π¬π§, πͺπΈ, π©πͺ, etc.)
- Missing Locales - Red flags show missing translations inline:
Missing: πΊπ¦ ua, πΉπ· tr - Counters - Shows number of templates at each level
- Sorting - Default templates appear last for better organization
- Expand/Collapse All - Quick navigation controls
RedDrill supports 4 translation providers. Choose based on your needs:
β Default Provider - No configuration needed when deployed to Cloudflare Workers
Pricing:
- Free Tier: 10,000 neurons/day (~322 translations)
- Model: @cf/meta/m2m100-1.2b (multilingual translation)
- Languages: 100+ language pairs supported
Setup:
- Deploy RedDrill to Cloudflare Workers (already configured in
wrangler.toml) - That's it! Translation works out-of-the-box π
Note: Only works when deployed to Cloudflare Workers, not in local development.
Pricing:
- $20 per 1 million characters
- First 500,000 characters free per month
- Pay-as-you-go, no monthly commitments
Setup Instructions:
-
Go to Google Cloud Console
-
Create or Select Project
- Click project dropdown β "New Project"
- Enter project name β "Create"
-
Enable Cloud Translation API
- Search for "Cloud Translation API" in top search bar
- Click "Cloud Translation API"
- Click "Enable"
- Or direct link: https://console.cloud.google.com/apis/library/translate.googleapis.com
-
Create API Key
- Go to: APIs & Services β Credentials
- Click "Create Credentials" β "API Key"
- Copy the generated API key (save it securely!)
-
Restrict API Key (Recommended)
- Click on the created key to edit
- Under "API restrictions":
- Select "Restrict key"
- Choose "Cloud Translation API" only
- Under "Application restrictions":
- Choose "HTTP referrers (web sites)"
- Add your domain (e.g.,
https://your-app.workers.dev/*)
- Click "Save"
-
Configure in RedDrill
- Go to Settings β Translation Provider Settings
- Select "Google Cloud Translation"
- Paste your API Key
- Set as primary provider (optional)
- Click "Save Provider Settings"
Pricing:
- Free Tier: 2 million characters per month (F0 tier)
- Paid: $10 per 1 million characters (S1 tier)
- Includes 50+ languages
Setup Instructions:
-
Go to Azure Portal
- Visit: https://portal.azure.com
- Sign in with Microsoft account
-
Create Translator Resource
- Click "Create a resource" (top left)
- Search for "Translator"
- Select "Translator" β Click "Create"
-
Configure Resource
- Subscription: Select your Azure subscription
- Resource Group: Create new or select existing
- Region: Choose closest to your users (e.g., East US, West Europe)
- Name: Enter unique name (e.g.,
reddrill-translator) - Pricing Tier:
- F0 (Free): 2M characters/month
- S1 (Standard): Pay-as-you-go, $10/1M chars
-
Get API Key and Region
- Wait for deployment to complete
- Click "Go to resource"
- Click "Keys and Endpoint" in left menu
- Copy KEY 1 or KEY 2 (either works)
- Note the Location/Region (e.g.,
eastus,westeurope)
-
Configure in RedDrill
- Go to Settings β Translation Provider Settings
- Select "Microsoft Azure Translator"
- Paste Subscription Key (KEY 1 or KEY 2)
- Enter Region (e.g.,
eastus,westeurope, orglobal) - Set as primary provider (optional)
- Click "Save Provider Settings"
Direct Link: https://portal.azure.com/#create/Microsoft.CognitiveServicesTextTranslation
Pricing:
- Paid plans only - Includes machine translation credits
- Open Source: Free for qualifying projects
- Team: Starting at $40/month
- Enterprise: Custom pricing
Setup Instructions:
-
Create Crowdin Account
- Visit: https://crowdin.com
- Sign up or log in
-
Create or Select Project
- Go to your Crowdin dashboard
- Create new project or select existing one
- Note: Project must support machine translation
-
Get Project ID
- Go to project Settings β General
- Find Project ID (numeric ID in URL or settings)
- Example: In URL
https://crowdin.com/project/12345, the ID is12345
-
Generate Personal Access Token
- Go to: Account Settings β API
- Direct link: https://crowdin.com/settings#api-key
- Click "New Token"
- Enter token name (e.g., "RedDrill Translation")
- Select scopes:
- β
projects(read) - β
translations(read/write)
- β
- Click "Create"
- Copy the token immediately (shown only once!)
-
Configure in RedDrill
- Go to Settings β Translation Provider Settings
- Select "Crowdin"
- Paste Personal Access Token
- Enter Project ID (numeric)
- Set as primary provider (optional)
- Click "Save Provider Settings"
Note: Crowdin uses your project's configured languages and translation memory.
| Provider | Free Tier | Paid Pricing | Languages | Best For |
|---|---|---|---|---|
| Cloudflare Workers AI | 10K neurons/day (~322 translations) | N/A | 100+ | Default choice, deployed apps |
| Google Cloud Translation | 500K chars/month | $20/1M chars | 130+ | High volume, best quality |
| Azure Translator | 2M chars/month | $10/1M chars | 50+ | Microsoft ecosystem, free tier |
| Crowdin | Open Source projects | $40+/month | 100+ | Professional localization workflows |
All providers support:
- β Row-by-Row Translation - Preserves HTML structure
- β Text-Only Translation - HTML tags and variables preserved
- β Side-by-Side Review - Compare original and translated text
- β One-Click Save - Create new locale variant instantly
Frontend: Next.js 16.0 β’ React 19.2 β’ TypeScript 5.9 β’ Tailwind CSS 4 β’ Radix UI β’ Zustand 5
Editor: GrapesJS β’ CodeMirror
Translation: Cloudflare Workers AI β’ Google Cloud Translation β’ Azure Translator β’ Crowdin
Database: Drizzle ORM β’ Cloudflare D1 (audit logs) β’ IndexedDB (settings, cache, test scenarios)
Performance: React Compiler β’ Cache Components β’ Turbopack File System Caching
Deployment: Cloudflare Workers β’ OpenNext.js
Dev Tools: ESLint 9 β’ Next.js DevTools MCP β’ Turbopack
Storage: IndexedDB (idb) for client-side settings and translation cache β’ SQLite for audit trail persistence
- Node.js 20+ or 24+
- Mandrill API Key
- (Optional) Translation provider API keys
# Clone repository
git clone https://github.com/erimeilis/reddrill.git
cd reddrill
# Install dependencies
npm install
# Configure environment (optional - copy .env.example)
cp .env.example .env
# Initialize local D1 database for audit trail (optional)
npm run db:migrate:local
# Start dev server
npm run devOpen http://localhost:3000 π
Configuration:
- Go to Settings page
- Configure Mandrill API key (required for template management)
- (Optional) Enable Audit Trail and configure translation providers
Note: Cloudflare Workers AI translation only works when deployed to Cloudflare Workers, not in local development. Use Google, Azure, or Crowdin for local testing.
# Build for Cloudflare Workers
npm run build:full
# Preview locally with Cloudflare environment
npm run preview
# Deploy to production (includes automatic D1 migration)
npm run deploy-
Get Cloudflare Account ID:
npx wrangler whoami
-
Update
wrangler.toml:account_id = "your-account-id" name = "reddrill" compatibility_date = "2025-10-01" [ai] binding = "AI" # Enables Cloudflare Workers AI [[d1_databases]] binding = "DB" database_name = "reddrill-audit" database_id = "your-d1-database-id" # Get from step 3
-
Create D1 Database:
# Create the D1 database npm run db:create # This will output: database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" # Copy this ID and update wrangler.toml
-
Run Migrations:
# Apply migrations to remote D1 database npm run db:migrate:remote -
Deploy:
npm run deploy
-
Configure API Keys:
- Open your deployed app URL
- Go to Settings
- Enter Mandrill API key
- (Optional) Enable Audit Trail
- (Optional) Configure translation providers
# Database Management
npm run db:create # Create new D1 database
npm run db:list # List all D1 databases
# Migrations
npm run db:migrate:local # Apply migrations to local database
npm run db:migrate:remote # Apply migrations to remote D1 database
npm run db:migrations:create # Create new migration file
# Query Database
npm run db:query:local "SELECT * FROM audit_logs LIMIT 10"
npm run db:query:remote "SELECT * FROM audit_logs LIMIT 10"See DEPLOYMENT.md for detailed instructions.
The Audit Trail system tracks all template operations (create, update, delete) with before/after state capture.
-
D1 Database Setup (Required)
- For local development:
npm run db:migrate:local - For production: Create D1 database and run
npm run db:migrate:remote(see Deployment section)
- For local development:
-
Enable in Settings (Required)
- Open your app β Go to Settings β Audit Trail tab
- Toggle "Enable Audit Trail" to ON
- Configure retention period (default: 30 days)
- Optionally set user identifier for tracking
β Automatic Tracking - All template operations logged automatically when enabled β Before/After Snapshots - Full template state captured for every change β Field-Level Changes - See exactly what changed (name, content, labels, etc.) β Search & Filter - Find logs by operation type, template name, status, date range β Restore from History - Restore any previous version from audit log β Bulk Operations - Track batch operations with success/failure counts β Configurable Retention - Auto-cleanup old logs based on your policy
- Navigate to the Audit page from the sidebar
- Browse all operations in chronological order
- Use filters to narrow down:
- Operation Type (create, update, delete, restore, import)
- Status (success, partial, failure)
- Date range
- Click "Details" on any log to see:
- Changes: Field-by-field comparison
- Before: Complete state before operation
- After: Complete state after operation
- Raw JSON: Full audit log data
- Open audit log details for deleted template
- Click "Restore Template" button
- Template will be recreated with its original state
- Restoration is also logged in audit trail
To disable audit logging:
- Go to Settings β Audit Trail
- Toggle "Enable Audit Trail" to OFF
- Existing logs are preserved
- No new logs will be created until re-enabled
Note: Audit trail data is stored in D1 database (SQLite). Each log entry includes full template state, so storage grows with usage. Configure retention period to balance history vs. storage.
reddrill/
βββ app/ # Next.js App Router
β βββ @entity/ # Entity details parallel route
β β βββ templates/[slug]/ # Template edit form
β β β βββ test/ # Template testing page
β β βββ activity/[id]/ # Message detail view
β β βββ audit/default.tsx # Clear @entity slot on audit page
β β βββ tags/default.tsx # Clear @entity slot on tags page
β β βββ senders/default.tsx # Clear @entity slot on senders page
β βββ @structure/ # List views parallel route (all pages)
β β βββ templates/ # Template list with table/tree views
β β βββ activity/ # Outbound activity list
β β βββ tags/ # Tags management page
β β βββ senders/ # Senders overview page
β β βββ audit/ # Audit logs list
β βββ api/
β β βββ audit/
β β β βββ log/ # Create audit log entry
β β β βββ logs/ # Query audit logs
β β β βββ logs/[id]/ # Get audit log by ID
β β β βββ stats/ # Audit statistics
β β β βββ cleanup/ # Cleanup old logs
β β β βββ settings/ # Audit settings
β β βββ mandrill/ # Mandrill API proxy (CORS resolver)
β β β βββ route.ts # All Mandrill operations centralized
β β βββ templates/[slug]/
β β β βββ preview/ # Template preview API
β β β βββ send-test/ # Send test email API
β β βββ translate/ # Translation API route
β βββ globals.css # Global styles + custom scrollbar
β βββ layout.tsx # Root layout with parallel routes
β
βββ components/
β βββ ui/ # Reusable UI components
β β βββ button.tsx # Radix button component
β β βββ dialog.tsx # Radix dialog component
β β βββ input.tsx # Radix input component
β β βββ page-header.tsx # Reusable page header component
β β βββ search-with-actions.tsx # Search bar with action buttons
β β βββ ... # Other Radix UI components
β βββ settings/
β β βββ locale-selector.tsx # Locale multiselect with search
β β βββ settings-dialog.tsx # Main settings dialog
β βββ audit/
β β βββ audit-logs-viewer.tsx # Audit logs table view
β β βββ audit-detail-modal.tsx # Detailed audit log modal
β β βββ audit-settings.tsx # Audit configuration
β βββ templates/
β β βββ template-edit-form.tsx # GrapesJS editor
β β βββ template-tree-view.tsx # Hierarchical tree view
β β βββ template-detail.tsx # Template detail dialog
β β βββ template-filters.tsx # Template filtering UI
β β βββ tree-node.tsx # Tree node component
β β βββ placeholder-list.tsx # Placeholder detection UI
β β βββ template-preview.tsx # Live preview with merge vars
β β βββ test-data-form.tsx # Test data input form
β β βββ test-scenario-selector.tsx # Save/load test scenarios
β β βββ send-test-dialog.tsx # Send test email dialog
β βββ translation/
β β βββ translate-template-dialog.tsx # Translation UI
β β βββ translation-settings.tsx # Provider configuration
β β βββ placeholder-validation.tsx # Placeholder validation display
β βββ tags/
β β βββ delete-tag-dialog.tsx # Tag deletion confirmation
β βββ senders/
β βββ sender-detail-dialog.tsx # Sender details modal
β
βββ lib/
β βββ api/
β β βββ mandrill.ts # Mandrill API client (legacy)
β βββ db/
β β βββ client.ts # Drizzle D1 database client
β β βββ schema.ts # Drizzle database schema
β β βββ audit-db.ts # Drizzle/D1 audit operations
β β βββ translation-settings-db.ts # IndexedDB for settings
β β βββ test-scenarios-db.ts # IndexedDB for test scenarios
β βββ hooks/
β β βββ use-templates.ts # Template operations hook
β βββ services/
β β βββ audit-service.ts # Audit trail business logic
β βββ store/
β β βββ useMandrillStore.ts # Mandrill API key management
β β βββ useSettingsStore.ts # App settings store
β βββ types/
β β βββ audit.ts # Audit trail types
β βββ utils/
β β βββ html-translator.ts # HTML parsing for translation
β β βββ placeholder-parser.ts # Placeholder detection & validation
β β βββ template-parser.ts # Parse {theme}_{locale} pattern
β β βββ template-tree.ts # Build tree from templates
β β βββ template-diff.ts # Calculate template diffs
β βββ constants/
β βββ locales.ts # Locale to flag mappings
β
βββ drizzle/
β βββ meta/ # Drizzle migration metadata
β βββ *.sql # Database migration files
β
βββ types/ # TypeScript type definitions
βββ wrangler.toml # Cloudflare Workers config
βββ next.config.ts # Next.js configuration
Parallel Routes:
@structure/- Main list views for all pages@entity/- Detail views that appear alongside lists
API Routes:
/api/mandrill/- Centralized Mandrill proxy (resolves CORS issues)/api/audit/- Audit trail operations/api/translate/- Translation service
Component Organization:
- Page logic in
app/@structure/routes - Reusable UI components in
components/ui/ - Feature-specific components in
components/{feature}/
State Management:
- Zustand stores for global state (API keys, settings)
- IndexedDB for persistent client-side data (test scenarios, translation settings)
- D1/SQLite for server-side audit logs
- π API keys stored in IndexedDB (not localStorage)
- π HTTPS-only API calls to Mandrill and translation services
- π« No server-side secrets - all credentials client-side
- π No logging of sensitive data (API keys, email content)
- π‘οΈ CSP headers for XSS protection
- π API key restrictions recommended for all providers
- π Edge deployment on Cloudflare's global network (300+ cities)
- π¦ Static generation with Next.js App Router
- β‘ Sub-100ms response times from edge locations
- π― Code splitting - Only load what you need
- πΎ Client-side caching with IndexedDB
- π Turbopack for fast builds
- βοΈ React Compiler for optimized renders
β Chrome 120+ β’ Edge 120+ β’ Firefox 120+ β’ Safari 17+ β’ iOS Safari 17+ β’ Chrome Mobile 120+
Requirements:
- ES2022 support
- IndexedDB support
- CSS Grid & Flexbox
- CSS Custom Properties
We welcome contributions! Here's how:
- Fork the repository
- Create feature branch:
git checkout -b feature/amazing-feature - Commit changes:
git commit -m 'Add amazing feature' - Push to branch:
git push origin feature/amazing-feature - Open Pull Request
- Use TypeScript for all new code
- Follow existing code style (ESLint)
- Add tests for new features
- Update documentation as needed
- Keep commits atomic and descriptive
MIT License - see LICENSE file for details.
- Mandrill - Email infrastructure by Mailchimp
- Next.js - React framework by Vercel
- Cloudflare Workers - Edge compute platform
- OpenNext.js - Next.js adapter for serverless
- Radix UI - Accessible component primitives
- Tailwind CSS - Utility-first CSS framework
- Tabler Icons - Beautiful icon library
- GrapesJS - Web builder framework
- Cloudflare Workers AI - Edge AI inference
- Google Cloud Translation - Neural machine translation
- Azure Translator - Microsoft translation service
- Crowdin - Localization management platform
- Zustand - Lightweight state management
- idb - IndexedDB wrapper
- Drizzle ORM - TypeScript ORM for SQL databases
Made with ππ using Next.js and Cloudflare Workers