Open Mail CLI is a command-line email client with IMAP/SMTP support built with Node.js 18+. It provides offline-first email management with SQLite storage, supporting features like email threading, contact management, signatures, spam detection, filters, templates, notifications, import/export, and multi-account support. The project uses modular architecture with clear separation of concerns across CLI, storage, IMAP/SMTP clients, and utility modules.
Key Stats: 100+ files, 32,000+ lines of code, 16 CLI commands, 11 modules
- Runtime: Node.js >= 18.0.0
- Package Manager: pnpm (pnpm@10.15.1+)
- Database: SQLite3 via better-sqlite3 (synchronous API, WAL mode, foreign keys enabled)
- Email Protocols: IMAP (node-imap v0.9.6+), SMTP (nodemailer v6.9.7+)
- CLI Framework: Commander.js v11.1.0+, Inquirer.js v8.2.6+
- Email Parsing: mailparser v3.6.5+
- UI/UX: Chalk v4.1.2+, Ora v5.4.1+, CLI-Table3 v0.6.3+
- Other: dotenv v16.3.1+, node-notifier v10.0.1+
# Install dependencies
pnpm install
# Run the application
pnpm start
# or
node src/index.js
# Run tests (currently no test files exist)
pnpm test
pnpm test:watch
pnpm test:coverage
# Lint code
pnpm lint
pnpm lint:fix
# Link globally for development
pnpm linkNote: The project has test scripts configured but no actual test files were found in tests/ directory. Tests should be created using Jest.
open-mail-cli/
├── src/
│ ├── index.js # Main entry point (#!/usr/bin/env node)
│ ├── cli/
│ │ ├── index.js # CLI application setup with Commander.js
│ │ ├── commands/ # 16+ command implementations
│ │ │ ├── account.js # Multi-account management
│ │ │ ├── config.js # Configuration management
│ │ │ ├── contact.js # Contact management
│ │ │ ├── delete.js # Email deletion
│ │ │ ├── draft.js # Draft management
│ │ │ ├── filter.js # Email filter rules
│ │ │ ├── filter-quick.js # Quick filters
│ │ │ ├── folder.js # Folder management
│ │ │ ├── forward.js # Email forwarding
│ │ │ ├── import-export.js # EML/MBOX import/export
│ │ │ ├── list.js # List emails
│ │ │ ├── mark.js # Star/flag emails
│ │ │ ├── notify.js # Notifications
│ │ │ ├── read.js # Read email details
│ │ │ ├── reply.js # Reply to emails
│ │ │ ├── search.js # Search emails
│ │ │ ├── send.js # Send emails
│ │ │ ├── signature.js # Email signatures
│ │ │ ├── spam.js # Spam detection
│ │ │ ├── sync.js # IMAP sync (with daemon support)
│ │ │ ├── tag.js # Tag management
│ │ │ ├── template.js # Email templates
│ │ │ ├── thread.js # Thread management
│ │ │ └── trash.js # Trash management
│ │ └── utils/
│ │ └── formatter.js # Output formatting utilities
│ ├── config/
│ │ ├── index.js # ConfigManager singleton (load/save/validate)
│ │ ├── schema.js # Configuration validation schema
│ │ └── defaults.js # Default configuration values
│ ├── storage/
│ │ ├── database.js # DatabaseManager singleton (SQLite)
│ │ ├── models/ # Data model classes
│ │ │ ├── account.js # Account model
│ │ │ ├── attachment.js # Attachment model
│ │ │ ├── contact.js # Contact model
│ │ │ ├── contact_group.js # Contact group model
│ │ │ ├── email.js # Email model
│ │ │ ├── filter.js # Filter model
│ │ │ ├── folder.js # Folder model
│ │ │ ├── saved-search.js # Saved search model
│ │ │ ├── signature.js # Signature model
│ │ │ ├── spam.js # Spam model
│ │ │ ├── tag.js # Tag model
│ │ │ ├── template.js # Template model
│ │ │ └── thread.js # Thread model
│ │ └── migrations/
│ │ ├── 001_initial.js # Initial schema (emails, attachments, folders)
│ │ ├── 002_p0_features.js # Reply/Forward, Drafts, Delete/Trash, Spam, Signatures
│ │ ├── 003_p1_features.js # Tags, Filters, Contacts, Multi-account, Threading
│ │ └── 004_p2_features.js # Templates, Notifications, Import/Export
│ ├── imap/
│ │ ├── client.js # IMAPClient class (connection, folders, fetch)
│ │ └── sync.js # Sync logic
│ ├── smtp/
│ │ ├── client.js # SMTPClient class (connection, send)
│ │ └── composer.js # Email composition
│ ├── accounts/
│ │ └── manager.js # Account management
│ ├── contacts/
│ │ └── manager.js # Contact management
│ ├── filters/
│ │ ├── engine.js # FilterEngine (apply filters to emails)
│ │ ├── executor.js # Execute filter actions
│ │ └── matcher.js # Match emails against filter conditions
│ ├── spam/
│ │ ├── detector.js # Spam detection logic
│ │ ├── filter.js # Spam filtering
│ │ └── rules.js # Spam rule management
│ ├── templates/
│ │ └── manager.js # Template management
│ ├── threads/
│ │ ├── analyzer.js # ThreadAnalyzer (analyze email relationships)
│ │ ├── builder.js # ThreadBuilder (build thread trees)
│ │ └── index.js # Thread module exports
│ ├── signatures/
│ │ └── manager.js # Signature management
│ ├── notifications/
│ │ └── manager.js # Notification management
│ ├── import-export/
│ │ ├── eml.js # EML format handler
│ │ ├── mbox.js # MBOX format handler
│ │ └── manager.js # Import/export manager
│ ├── sync/
│ │ ├── index.js # Sync module exports
│ │ ├── daemon.js # Sync daemon
│ │ ├── daemon-worker.js # Daemon worker
│ │ ├── scheduler.js # Sync scheduler
│ │ └── account-manager.js # Account sync management
│ └── utils/
│ ├── logger.js # Logger singleton (ERROR, WARN, INFO, DEBUG)
│ ├── errors.js # Custom error classes
│ ├── helpers.js # Helper functions (encrypt/decrypt, paths)
│ └── email-parser.js # Email parsing utilities
├── docs/ # Documentation (mostly in Chinese)
├── data/ # Runtime data directory (created automatically)
│ ├── config.json # User configuration (passwords encrypted)
│ ├── mail.db # SQLite database
│ └── logs/
│ └── open-mail-client.log # Application logs
└── package.json # Project configuration
- Indentation: 2 spaces (not tabs)
- Variable declarations: Prefer
constoverlet. Never usevar - String concatenation: Use template literals (backticks) instead of
+ - Asynchronous code: Use async/await instead of callbacks
- Semicolons: Use semicolons at end of statements
- Quotes: Use single quotes for strings, double quotes in JSON
- Files: kebab-case (e.g.,
email-parser.js,filter-quick.js) - Classes: PascalCase (e.g.,
IMAPClient,ConfigManager,FilterEngine) - Variables/Functions: camelCase (e.g.,
fetchEmails,markAsRead,emailData) - Constants: UPPER_SNAKE_CASE (e.g.,
MAX_ATTACHMENT_SIZE,DEFAULT_PORT) - Private methods: Prefix with underscore (e.g.,
_ensureDataDir(),_matchFilter()) - Singletons: Export singleton instances (e.g.,
module.exports = new ConfigManager())
- JSDoc comments: Required for all functions and classes
- Format: Block comments with description, parameters, and return values
/**
* Fetch emails from folder (headers only for sync)
*/
async fetchEmails(criteria = ['ALL'], options = {}) {
// implementation
}- Custom error classes: Extend
MailClientError(defined insrc/utils/errors.js)ConfigError- Configuration-related errorsConnectionError- Network/connection failuresAuthenticationError- Authentication failuresSyncError- Synchronization errorsStorageError- Database/storage errors
- Try-catch: Wrap all async operations in try-catch blocks
- Error logging: Use
logger.error()with context object - User-facing errors: Display friendly messages, log technical details
- Global error handlers: Implemented in
src/index.js(lines 26-36)
- Use prepared statements: Always use parameterized queries to prevent SQL injection
- Transactions: Use for multi-step operations
- Error handling: Wrap in try-catch, throw
StorageError - Singleton pattern: Access database via
database.getDb()
Commands in src/cli/commands/ follow this pattern:
function commandName(options) {
try {
// Command logic
} catch (error) {
console.error(chalk.red('Error:'), error.message);
logger.error('Command failed', { error: error.message });
process.exit(1);
}
}
module.exports = commandName;Used for managers to ensure single instance:
ConfigManager(src/config/index.js)DatabaseManager(src/storage/database.js)Logger(src/utils/logger.js)FilterEngine(src/filters/engine.js)ThreadAnalyzer(src/threads/analyzer.js)
Data models in src/storage/models/ follow this structure:
- Constructor with database reference
- Private
_getDb()method to get database instance - CRUD methods:
create(),findById(),update(),delete() - Query methods:
findByFolder(),search(), etc. - Private
_formatEmail()helper for formatting database records
- Main entry:
src/index.jsinitializes database and runs CLI - CLI setup:
src/cli/index.jsdefines all commands using Commander.js - Commands: Individual files in
src/cli/commands/handle specific operations
- Migration system: Versioned migrations in
src/storage/migrations/ - WAL mode: Enabled for better concurrency
- Foreign keys: Enabled with
ON DELETE CASCADE - Indexes: Created on frequently queried columns
- Timestamps:
created_atandupdated_aton most tables
- Linux/macOS:
~/.config/open-mail-client/config.json - Windows:
%APPDATA%\open-mail-client\config.json
{
imap: {
host: 'imap.example.com',
port: 993,
secure: true,
user: 'user@example.com',
password: 'encrypted_password'
},
smtp: {
host: 'smtp.example.com',
port: 465,
secure: true,
user: 'user@example.com',
password: 'encrypted_password'
}
}- Passwords are encrypted using Node.js crypto module (see
src/utils/helpers.js) - TLS/SSL enforced for IMAP/SMTP connections
- Database file with restricted permissions
- Sensitive information never logged
- Test scripts configured in
package.jsonbut no test files exist - Should use Jest for testing (configured as dev dependency)
- Test files should be placed in
tests/directory with.test.jsor.spec.jsextension
# Run all tests
pnpm test
# Run specific test file
pnpm test -- path/to/test/file.test.js
# Watch mode
pnpm test:watch
# Coverage report
pnpm test:coverage- Unit tests for individual modules
- Integration tests for database operations
- End-to-end tests for CLI commands
- Mock IMAP/SMTP connections for testing
- Incremental sync: Uses IMAP UID to track new emails
- Batch processing: Fetches emails in batches (default 100) to avoid timeouts
- Lazy loading: Email body fetched on demand (headers only during sync)
- Performance logging: Extensive
[PERF]logging in sync operations
- Subject normalization: Removes Re:, Fwd:, etc. prefixes
- Relationship detection: Uses In-Reply-To, References headers, and subject similarity
- Levenshtein distance: For calculating subject similarity
- Confidence scoring: Different methods have different confidence levels
- Condition matching: Supports AND (matchAll) and OR (matchAny) logic
- Action execution: Can move, tag, mark read/starred, delete, forward emails
- Priority system: Filters execute in priority order
- Enabled/disabled: Filters can be toggled without deletion
- Bayesian filtering: Statistical approach to spam detection
- Blacklist/whitelist: Domain and email address lists
- Rule-based: Configurable spam rules with priority
- Keywords: Pattern matching for common spam terms
- Create command file in
src/cli/commands/ - Export a function that accepts
optionsparameter - Register command in
src/cli/index.jsusingprogram.command() - Follow error handling pattern with try-catch
- Use
chalkfor colored output,loggerfor debugging
- Create migration file in
src/storage/migrations/ - Implement
up(db)anddown(db)functions - Register migration in
src/storage/database.jsrunMigrations() - Create model file in
src/storage/models/ - Add indexes for frequently queried columns
- Create directory under appropriate category (e.g.,
src/feature/) - Implement manager class with singleton pattern
- Add model in
src/storage/models/if needed - Create migration if database changes required
- Add CLI command in
src/cli/commands/ - Update documentation
- Never log passwords or sensitive data
- Always use parameterized queries to prevent SQL injection
- Encrypt passwords in config file using provided encryption functions
- Validate all user input before processing
- Use TLS/SSL for all IMAP/SMTP connections
- Sanitize email content before display to prevent XSS (when rendering HTML)
- Restrict file permissions on config and database files
- Database indexes: Created on uid, folder, date, from_address, subject
- WAL mode: Enabled for better write performance
- Batch operations: Fetch/process emails in batches
- Lazy loading: Email bodies fetched on demand
- Connection pooling: IMAP connections reused when possible
- Query optimization: Use LIMIT/OFFSET for pagination
- Database locked: Ensure only one instance running, check WAL file
- IMAP connection failed: Check credentials, host, port, TLS settings
- Migration errors: Database schema may be out of sync, check migrations
- Config not loading: Check file path and permissions
- Permission denied: Check data directory permissions
- Set log level to DEBUG:
logger.setLevel('DEBUG') - Check log file:
data/logs/open-mail-client.log - Use
--helpflag for command options - Enable verbose logging for sync operations
- Primary documentation language: Chinese (docs/ directory)
- Code comments: English
- User-facing messages: English in CLI, Chinese in docs
- Error messages: English
- JSDoc comments: English
Key dependencies and their purposes:
better-sqlite3: Synchronous SQLite database driverchalk: Terminal string stylingcli-table3: Formatted table outputcommander: CLI framework for command parsinginquirer: Interactive command-line promptsmailparser: Email parsing (MIME, HTML, attachments)node-imap: IMAP client implementationnode-notifier: Desktop notificationsnodemailer: SMTP client for sending emailsora: Elegant terminal spinners
- Always check existing patterns before adding new code
- Use established singleton managers instead of creating new instances
- Follow the error handling pattern with custom error classes
- Log all operations with appropriate levels (ERROR, WARN, INFO, DEBUG)
- Validate configuration before use
- Use prepared statements for all database operations
- Encrypt sensitive data before storing
- Add JSDoc comments to all functions and classes
- Test CLI commands manually before submitting
- Run lint command (
pnpm lint) before committing changes
- Make code changes
- Run
pnpm lintand fix any issues - Test CLI commands manually
- Check logs for errors or warnings
- Update documentation if needed
- Add tests if adding new functionality
- Run
pnpm testif tests exist