Repository: Foundry376/Mailspring
Stars: 17395
CLAUDE.md
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Build and Development Commands
Install dependencies
npm installRun the app in development mode (uses --dev flag, data stored in Mailspring-dev folder)
npm startRun with specific language locale
npm start -- --lang=deRun linting (prettier + eslint)
npm run lintRun all tests
npm testRun window-specific tests
npm test-windowTypeScript type checking in watch mode
npm run tsc-watchBuild for production
npm run buildArchitecture Overview
Mailspring is an Electron-based email client written in TypeScript with React. It uses a plugin architecture where features are implemented as internal packages.
Key Directories
- app/src/ - Core application source code
- browser/ - Main process code (application lifecycle, window management, auto-updates)
- flux/ - Flux-based state management (actions, stores, models, tasks)
- components/ - Reusable React UI components
- services/ - Application services (search, sanitization, etc.)
- registries/ - Extension registries (components, extensions, database objects)
- global/ - Global exports (mailspring-exports, mailspring-component-kit)
- app/internal_packages/ - Built-in plugins implementing features (composer, message-list, thread-list, preferences, themes, etc.)
IMPORTANT: Application source code lives in bothapp/src/andapp/internal_packages/. When searching for usages of a module, symbol, or pattern, always search both directories. Searching onlyapp/src/will miss a large portion of the codebase and lead to incomplete changes.
- app/spec/ - Jasmine test specs
Core Modules
Global exports for plugins:
- mailspring-exports - Core APIs: Actions, Stores, Models, Tasks, Utils, database access
- mailspring-component-kit - Reusable UI components
Flux Architecture:
- Models (flux/models/) - Data models: Message, Thread, Contact, Account, Folder, Label, etc.
- Stores (flux/stores/) - Application state: DatabaseStore, DraftStore, AccountStore, etc.
- Tasks (flux/tasks/) - Async operations: SendDraftTask, ChangeFolderTask, etc.
- Actions (flux/actions.ts) - Application-wide action dispatcher
Plugin Structure
Each plugin in internal_packages/ has:
- package.json - Metadata with windowTypes specifying where plugin loads
- lib/main.ts - Entry point with activate() and deactivate() lifecycle hooks
- lib/ - Plugin source code
- styles/ - LESS stylesheets
- keymaps/ - Keyboard shortcut definitions
Core Data Flow: Sync Engine, Tasks, and Observable Database
Important: The UI is read-only with respect to the database. All database modifications happen in the C++ sync engine (Mailspring-Sync). The Electron app requests changes via Tasks, and the sync engine streams entity changes back to create a real-time UI.
Sync Engine Communication (mailsync-process.ts, mailsync-bridge.ts)
The sync engine is a separate C++ process spawned per account:
1. Electron β Sync Engine: JSON messages sent via stdin (task requests, commands)
2. Sync Engine β Electron: Newline-delimited JSON streamed via stdout (database change deltas)
βββββββββββββββββββ stdin (JSON) ββββββββββββββββββββ
β Electron UI β βββββββββββββββββββββββββββββββΆβ Mailspring-Sync β
β (TypeScript) β β (C++) β
β β βββββββββββββββββββββββββββββββ β β
βββββββββββββββββββ stdout (JSON deltas) ββββββββββββββββββββThe MailsyncBridge (in main window only) manages sync process lifecycle, listens to Actions.queueTask, and forwards tasks to the appropriate account's sync process.
Task System (flux/tasks/)
Tasks represent operations the user wants to perform (send email, star thread, move to folder). They are persisted models stored in the database.
Task Lifecycle:
1. UI calls Actions.queueTask(new SomeTask({...}))
2. MailsyncBridge._onQueueTask() validates and sends to sync engine via stdin
3. Sync engine executes the task (local changes + remote API calls)
4. Sync engine persists task status updates and emits deltas
5. Task completion triggers onSuccess() or onError() callbacks
Task States (flux/tasks/task.ts):
- local - Not yet executed
- remote - Local phase complete, waiting for remote
- complete - Finished successfully
- cancelled - Cancelled before completion
Key Task Classes:
- SendDraftTask, DestroyDraftTask - Email composition
- ChangeLabelsTask, ChangeFolderTask - Organization
- ChangeStarredTask, ChangeUnreadTask - Status flags
- SyncbackMetadataTask - Plugin metadata sync
- SyncbackEventTask - Calendar event sync
Undoable Tasks:
Tasks can support undo/redo by implementing canBeUndone and createUndoTask(). The UndoRedoStore automatically registers tasks with canBeUndone = true for undo.
Two patterns exist:
1. Toggle pattern (ChangeStarredTask): Undo simply flips a boolean flag
2. Snapshot pattern (SyncbackMetadataTask, SyncbackEventTask): Store original state in undoData, swap on undo
// Snapshot pattern example
const undoData = { ics: event.ics, recurrenceStart: event.recurrenceStart };
event.ics = newIcs; // Modify after capturing
Actions.queueTask(SyncbackEventTask.forUpdating({ event, undoData, description: 'Edit event' }));See docs/undo-redo-task-pattern.md for detailed implementation guide.
Task Queue (flux/stores/task-queue.ts)
The TaskQueue store observes Task model changes from the database and provides:
- queue() - Active tasks
- completed() - Finished tasks
- waitForPerformLocal(task) - Promise that resolves when task runs locally
- waitForPerformRemote(task) - Promise that resolves when task fully completes
Observable Database Pattern
Database is read-only in Electron (flux/stores/database-store.ts):
- DatabaseStore.inTransaction() throws - writes are not allowed
- Uses SQLite in WAL mode via better-sqlite3 for concurrent reads
- The sync engine exclusively handles writes
Change Records (flux/stores/database-change-record.ts):
When the sync engine modifies data, it emits JSON deltas that become DatabaseChangeRecord objects:
{
type: 'persist' | 'unpersist',
objectClass: 'Thread' | 'Message' | ...,
objects: Model[],
objectsRawJSON: object[]
}Reactive Queries (flux/models/query-subscription.ts):
QuerySubscription provides live-updating query results:
// Subscribe to all unread threads
const subscription = new QuerySubscription(
DatabaseStore.findAll(Thread).where({ unread: true })
);
subscription.addCallback((threads) => this.setState({ threads }));// Subscription automatically updates when DatabaseStore triggers
Observable Integration (Rx.Observable.fromQuery):
Wrap queries as RxJS observables for reactive UI updates:
Rx.Observable.fromQuery(DatabaseStore.findAll(Thread))
.subscribe(threads => this.updateUI(threads));ObservableListDataSource (flux/stores/observable-list-data-source.ts):
Adapts QuerySubscription for virtualized list components (MultiselectList), supporting:
- Windowed/paginated data loading
- Selection state management
- Automatic updates from database changes
Data Flow Summary
User Action β Actions.queueTask() β MailsyncBridge β stdin β Sync Engine
β
βΌ
UI Updates β QuerySubscription β DatabaseStore.trigger() β stdout deltasDevelopment Notes
- Hot reload is available via CTRL+R (Windows/Linux) or CMD+R (macOS)
- Dev tools accessible via Menu > Developer > Toggle Developer Tools
- In dev tools console, $m provides access to mailspring-exports for debugging
- Dev mode data is stored separately (e.g., ~/.config/Mailspring-dev/ on Linux)
Claude Hooks
after_edit
Run linting after modifying TypeScript or JavaScript files.
{
"hooks": {
"after_edit": [
{
"command": "npm run lint",
"file_paths": ["/.ts", "/.tsx", "/.js", "/.jsx"]
}
]
}
}README.md
π Mailspring
Mailspring is a new version of Nylas Mail maintained by one of the original authors. It's faster, leaner, and shipping today! It replaces the JavaScript sync code in Nylas Mail with a new C++ sync engine based on Mailcore2. It uses roughly half the RAM and CPU of Nylas Mail and idles with almost zero "CPU Wakes", which translates to great battery life. It also has an entirely revamped composer and other great new features.
Mailspring's UI is open source (GPLv3) and written in TypeScript with Electron and React - it's built on a plugin architecture and was designed to be easy to extend. Check out CONTRIBUTING.md to get started!
Mailspring's sync engine is spawned by the Electron application and runs locally on your computer. It is open source (GPLv3) and written in C++ and C. For convenience, however, when you set up your development environment, Mailspring uses the latest version of the sync engine we've shipped for your platform so you don't need to pull sources or install its compile-time dependencies.
Features
Mailspring comes packed with powerful features like Unified Inbox, Snooze, Send
Later, Mail Rules, Templates and more. Mailspring Pro, which you can unlock
with a monthly subscription, adds even more features for people who send a ton
of email: link tracking, read receipts, mailbox analytics, contact and company
profiles. All of these features run in the client - Mailspring does not send
your email credentials to the cloud. For a full list of features, check out
getmailspring.com.
Download Mailspring
You can download compiled versions of Mailspring for Windows, Mac OS X, and
Linux (deb, rpm and snap) from
https://getmailspring.com/download.
Getting Help
You can find community-based help and discussion with other Mailspring users on our
Discourse community.
Contributing
Mailspring is entirely open-source. Pull requests and contributions are
welcome! There are three ways to contribute: building a plugin, building a
theme, and submitting pull requests to the project itself. When you're getting
started, you may want to join our
Discourse so you can ask questions and
learn from other people doing development.

Running Mailspring from Source
To install all dependencies and run Mailspring from its source code,
run the following commands from the root directory of the Mailspring repository:
export npm_config_arch=x64 # If you are on an M1 / Apple Silicon Mac
npm install
npm startYou can attach command line parameters by separating them using a double hyphen:
npm start -- --helpBuilding Mailspring
To build Mailspring, you need to run the following command from the root directory
of the Mailspring repository:
npm run-script buildBuilding A Plugin
Plugins lie at the heart of Mailspring and give it its powerful features.
Building your own plugins allows you to integrate the app with other tools,
experiment with new workflows, and more. Follow the Getting Started
guide to write your first plugin in
five minutes.
- To create your own theme, check out the
Mailspring-Theme-Starter.
- To create your own plugin, check out the
Mailspring-Plugin-Starter.
A plugin "store" like the Chrome Web Store is coming soon, and will make it
easy for other users to discover plugins you create. (Right now, users need to
"sideload" the plugins into the app by downloading them and copying them into
place.)
You can share and browse Mailspring Plugins, and discuss plugin development
with other developers, on our
Discourse.
Building a Theme
The Mailspring user interface is styled using CSS, which means it's easy to
modify and extend. Mailspring comes stock with a few beautiful themes, and
there are many more which have been built by community developers. To start
creating a theme, clone the theme starter!
If you are updating an existing Nylas theme for Mailspring here is a
step by step tutorial.
Notice: as part of the update process you will probably need to import mailspring base variables.
You can share and browse Mailspring Themes, and discuss theme development with other developers, on our Discourse.
Localizing / Translating
Mailspring (1.5.0 and above) supports localization. If you're a fluent speaker of
another language, we'd love your help improving translations. Check out the
LOCALIZATION
guide for more information. You can discuss localization and translation with
other developers on our Discourse.
Contributing to Mailspring Core
Pull requests are always welcome - check out
CONTRIBUTING
for more information about setting up the development environment, running
tests locally, and submitting pull requests.