Skip to content

Latest commit

 

History

History
171 lines (124 loc) · 7.49 KB

File metadata and controls

171 lines (124 loc) · 7.49 KB

NeoMail — Engineering Guidelines

Project Overview

NeoMail is a self-hosted AI email client with a CommonJS Node.js backend and a Flutter frontend. It provides multi-user mailbox isolation, IMAP and SMTP connectivity, admin-managed AI providers, a secure external API, and a hosted MCP surface.

Directory Map

server/          Express HTTP server, all backend logic
  config/        Static configuration (origins, deployment flags)
  db/            SQLite via better-sqlite3
  http/          Middleware, routes registration, socket setup, static serving
  routes/        One file per feature domain (auth.js, mail.js, admin.js, etc.)
  services/      Business logic, grouped by domain
    ai/          LLM provider routing and mail assist flows
    auth/        API-key, bearer, session, and OAuth auth logic
    mail/        Mail accounts, sync, send queue, drafts, search, threading
    mcp/         Hosted MCP authorization and tool execution
    integrations/ External auth and secret handling
  utils/         Shared server-side helpers
runtime/         Startup paths, env resolution, release channel
lib/             Install helpers and migration logic
flutter_app/     Cross-platform Flutter client (web, Android, macOS, Windows, Linux)
  lib/src/       Feature-specific Dart source files
scripts/         Build and release utility scripts

Core Principles

  • No duplication. Before adding a helper, search server/utils/, runtime/, and the relevant service directory. Reuse what exists.
  • No premature abstraction. Three similar lines is fine. Abstract only when a fourth copy would appear.
  • No speculative features. Implement exactly what the task requires. No extra fallbacks, feature flags, or backwards-compat shims for callers that don't yet exist.
  • No dead code. Remove unused exports, variables, and branches. Do not leave commented-out blocks behind.
  • Readable over clever. Prefer direct, boring code. Avoid chained .reduce, nested ternaries, or dense one-liners that obscure intent.

Node.js / Server

Module system

  • CommonJS (require / module.exports) throughout the server. Do not introduce ES import/export in server files.
  • Every new server file starts with 'use strict';.

File layout

  • Routes (server/routes/<domain>.js) handle only HTTP concerns: parse the request, call a service, return a response. No business logic.
  • Services (server/services/<domain>/) own all business logic. If a file grows past ~300 lines, split it.
  • Utils (server/utils/) are pure helpers with no side effects and no service imports.

Database

  • All DB access goes through server/db/database.js. Never instantiate a second Database.
  • Use the better-sqlite3 synchronous API. No async wrappers or callbacks.
  • All queries use parameterized statements. No string interpolation into SQL.
  • Schema changes belong in the shared migration path. No ALTER TABLE or CREATE TABLE inline in service files.

AI / LLM

  • All LLM calls go through shared AI service entry points. Do not call provider SDKs directly from routes or unrelated services.
  • Provider-specific code belongs in server/services/ai/providers/.
  • AI behavior must be model-driven over structured message or thread context. Do not add phrase-based filters.

Error handling

  • Validate at system boundaries: HTTP request bodies, external API responses, user-supplied env values. Do not defensively validate arguments passed between internal functions.
  • Wrap I/O and external calls in try/catch. Log errors with a [ServiceName] prefix for searchability.
  • Every route handler must send a response. Unhandled errors propagate to server/http/errors.js.

Security

  • Never log secrets, tokens, or PII. Log boolean presence (Boolean(process.env.KEY)) in startup diagnostics.
  • Parameterized queries everywhere — no SQL string interpolation, ever.
  • CORS and origin validation go through server/config/origins.js. No ad-hoc bypasses.
  • Server startup must reject (and log clearly) if SESSION_SECRET is absent.
  • API keys are stored hashed, scoped, auditable, and revocable. Never persist raw keys after creation.
  • Per-user isolation is a hard requirement. No route, service, or MCP tool may read or mutate mailbox data without an owning user context.

Environment & paths

  • Use the constants exported from runtime/paths.js for all filesystem paths. Never hardcode runtime home directories or absolute system paths.
  • Use runtime/env.js helpers for env access in service code. Raw process.env is acceptable only for simple top-level feature flags in entry points.

Logging

  • Use server/utils/logger.js in all service and library code. Plain console.log is only acceptable in entry-point startup sequences.

Flutter / Dart

Architecture

  • flutter_app/lib/main_controller.dart is the single root ChangeNotifier. Do not introduce competing global state objects.
  • Feature logic goes in lib/src/<feature>_bridge.dart or lib/src/<feature>_service.dart. Widgets are thin — no business logic in widget files.
  • Platform-conditional code uses the established _io / _web / _stub file-suffix pattern in lib/src/. Do not use kIsWeb inline branches when a conditional export covers the case.

State and side effects

  • ChangeNotifier + ListenableBuilder / addListener is the state model. Do not add additional state management packages.
  • Every StreamSubscription and listener stored as a field must be cancelled/removed in dispose().

Dart style

  • Follow the rules in flutter_app/analysis_options.yaml.
  • Prefer final. Use late final only when initialization is genuinely deferred.
  • Use const constructors wherever the analyzer allows.
  • Avoid dynamic. When interfacing with untyped data (JSON, JS interop), cast and validate at the boundary.

Flutter web build

  • Flutter web builds are handled separately as part of the release pipeline. Do not run flutter build web during normal development.
  • For local development use the flutter:run:web npm script, which includes the required --dart-define flags.

Naming Conventions

Context Convention
JS filenames snake_case
JS variables / functions camelCase
JS classes PascalCase
Dart filenames snake_case
Dart classes / types UpperCamelCase
Dart variables / functions lowerCamelCase
Environment variables SCREAMING_SNAKE_CASE
HTTP route paths kebab-case
SQLite tables / columns snake_case

Extension Recipes

New integration

  1. Add auth, token, or secret handling in server/services/integrations/.
  2. Route file at server/routes/<name>.js — HTTP only, delegate to service.
  3. Register the route in server/http/routes.js.
  4. Add env keys to .env.example with descriptive comments.
  5. Add presence logging for the new keys in logStartupConfig() when they are required at startup.

New AI tool

  1. Define the tool schema and handler in the MCP or AI service layer.
  2. Extract non-trivial mailbox logic into a mail or AI service module.
  3. Ensure large tool results are summarized before they are returned to models or clients.

Hard Rules

  • Do not import Anthropic, OpenAI, or Google AI SDKs outside of server/services/ai/providers/.
  • Do not create additional SQLite database files. One DB, one instance.
  • Do not commit .env, runtime data, or anything matched by .gitignore.
  • Do not bypass git hooks with --no-verify.
  • Do not run flutter build web outside the release pipeline.