by Arno L.

Mailspring is an open-source, Electron-based email client built with TypeScript and React, featuring a plugin architecture, unified inbox, snooze, templates, link tracking, and customizable themes.
This block provides the full Mailspring desktop email client UI source, a TypeScript/React/Electron application built on a plugin architecture. Each feature area is an independent internal package with activate/deactivate lifecycle hooks wired into a central component and extension registry. The typical buyer is a developer embedding or extending the Mailspring UI shell, building custom plugins, or learning the architecture to implement similar patterns in their own Electron application.
src/ - Core Mailspring application bootstrap, stores, actions, and framework internalsinternal_packages/ - Feature plugins (account-sidebar, composer, thread-list, send-later, etc.), each self-contained with lib/main.ts entryinternal_packages_disabled/ - Plugins excluded from the default build, available for opt-inkeymaps/ - Default keyboard shortcut definitionslang/ - Localization string filesmenus/ - Application menu definitions (macOS, Linux, Windows)script/ - Build, packaging, and release scriptsstatic/ - Static assets (images, fonts, stylesheets) bundled into the appdot-mailspring/ - Default user configuration skeleton (config.json, keymap.json, sample packages)package.json - Root npm manifest; defines the mailspring-root package and all dev/build dependenciestsconfig.json - TypeScript compiler configuration for the entire app sourceresult-counter.js - Utility script for test result aggregationnpm install electron react react-dom reflux rx-lite slate slate-react \
slate-html-serializer slate-base64-serializer slate-plain-serializer \
mousetrap moment moment-round lru-cache debug source-map-support \
better-sqlite3 event-kit
npm install --save-dev typescript @types/react @types/react-dom \
@types/reflux @types/rx-lite @types/slate @types/slate-react \
@types/slate-html-serializer @types/slate-base64-serializer \
@types/slate-plain-serializer @types/mousetrap @types/lru-cache \
@types/moment-round @types/better-sqlite3 @types/debug \
@types/event-kit @types/jasmine @types/optimist @types/proxyquire \
@types/react-color @types/react-test-renderer \
@types/react-transition-group @types/source-map-support \
@electron/packager @sentry/cli @playwright/test
Spin up an isolated sandbox and run it server-side — no local setup.
Tetrees AI Review of this version
This TypeScript, JavaScript cli / script completed archive review with strong static results. Structure, dependency manifests, documentation, functional source, and common risk patterns were checked by the Tetrees verification pipeline; runtime phases are stated separately.
Deterministic AVCP artifact review
Pipeline avcp-2026-08-04.1 · SHA-256 9dde2836cc77457e…
This version-scoped review deterministically inspects the submitted archive for structure, dependencies, documentation, functional source, and common malicious or high-risk signals. Build and test phases are reported as passed only after an isolated sandbox audition. It is not a guarantee of perfect security.
Reviewed Aug 4, 2026
Push this product straight into your AI IDE, web builder or cloud IDE.
Connect Tetrees to a compatible AI IDE, list products you own, and request the verified ZIP without exposing seller upload controls.
No reviews yet.
Sign in to join the discussion
Loading discussion…
Native modules: better-sqlite3 requires a native build. After npm install, run:
./node_modules/.bin/electron-rebuild -f -w better-sqlite3
This must be repeated any time the Electron version changes.
Copy the contents of source/ into your project root or a subdirectory (e.g., ./mailspring-app/).
From the directory containing package.json (the copied source/), install dependencies:
npm install
Wire tsconfig.json path aliases. The root tsconfig.json maps mailspring-exports and mailspring-component-kit to internal source paths. Verify these paths entries resolve correctly relative to your project root:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"mailspring-exports": ["src/mailspring-exports.ts"],
"mailspring-component-kit": ["src/mailspring-component-kit.ts"]
}
}
}
Required environment variables for the Electron main process:
MAILSPRING_DISABLE_GPU=1 # optional, headless CI
ELECTRON_ENABLE_LOGGING=1 # optional, verbose renderer logs
Start the app from the source root:
npm start
To build a distributable:
npm run build
export function activate(state?: any): void;
export function deactivate(state?: any): void;
Every internal package exports these two functions. activate is called by the package manager when the plugin is loaded; deactivate is called on teardown. Use activate to register components and extensions, and deactivate to unregister them so the app can hot-reload plugins cleanly.
// From mailspring-exports
ComponentRegistry.register(ComponentClass: React.ComponentType, descriptor: {
location?: WorkspaceStore.Location | WorkspaceStore.Location,
role?: string,
}): void;
ComponentRegistry.unregister(ComponentClass: React.ComponentType): void;
Mounts a React component into a named layout location (e.g., WorkspaceStore.Location.RootSidebar) or into a named role slot (e.g., { role: 'MessageAttachments' }). Use register in activate and always pair it with unregister in deactivate to avoid duplicate renders.
// From mailspring-exports
ExtensionRegistry.AccountSidebar.register(extension: {
name: string;
sidebarItem(accountIds: string[]): {
id: string;
name: string;
iconName: string;
perspective: MailboxPerspective;
};
}): void;
Adds a custom item to the account sidebar navigation. The sidebarItem callback receives the current account IDs and must return a descriptor including a MailboxPerspective instance that controls what thread list content is shown when the item is selected. Used by the Activity, Send Later, Snooze, and similar packages.
Add a React component to the root sidebar layout location. This mirrors the exact pattern used by account-sidebar/lib/main.ts.
import { ComponentRegistry, WorkspaceStore } from 'mailspring-exports';
import MyCustomSidebar from './components/my-custom-sidebar';
export function activate(state: any) {
ComponentRegistry.register(MyCustomSidebar, {
location: WorkspaceStore.Location.RootSidebar,
});
}
export function deactivate(state: any) {
ComponentRegistry.unregister(MyCustomSidebar);
}
Register an extension that provides a custom mailbox perspective and sidebar entry, following the pattern in activity/lib/main.ts.
import {
localized,
ExtensionRegistry,
WorkspaceStore,
ComponentRegistry,
MailboxPerspective,
} from 'mailspring-exports';
import MyView from './components/my-view';
class MyPerspective extends MailboxPerspective {
sheet() { return WorkspaceStore.Sheet.MyView; }
threads() { return null; }
canReceiveThreadsFromAccountIds() { return false; }
unreadCount() { return 0; }
}
const MySidebarExtension = {
name: 'MyFeature',
sidebarItem(accountIds: string[]) {
return {
id: 'MyFeature',
name: localized('My Feature'),
iconName: 'my-feature.png',
perspective: new MyPerspective(accountIds),
};
},
};
export function activate() {
ExtensionRegistry.AccountSidebar.register(MySidebarExtension);
WorkspaceStore.defineSheet('MyView', { root: true }, { list: ['RootSidebar', 'MyContent'] });
ComponentRegistry.register(MyView, { location: WorkspaceStore.Location.MyContent });
}
export function deactivate() {
ExtensionRegistry.AccountSidebar.unregister(MySidebarExtension);
ComponentRegistry.unregister(MyView);
}
Register a new tab in the Preferences window, mirroring category-mapper/lib/main.ts.
import { localized, PreferencesUIStore } from 'mailspring-exports';
let preferencesTab: any;
export function activate() {
preferencesTab = new PreferencesUIStore.TabItem({
tabId: 'MySettings',
displayName: localized('My Settings'),
componentClassFn: () => require('./my-preferences-panel').default,
});
PreferencesUIStore.registerPreferencesTab(preferencesTab);
}
export function deactivate() {
PreferencesUIStore.unregisterPreferencesTab(preferencesTab.sectionId);
}
src/ - Application core: Electron main/renderer bootstrap, mailspring-exports barrel, flux stores (WorkspaceStore, Actions, ComponentRegistry, etc.), and the package loader that calls activate/deactivate on each plugin.internal_packages/ - Self-contained feature plugins. Each has a package.json, a lib/main.ts with activate/deactivate, and optional styles/ and assets/ subdirectories.internal_packages_disabled/ - Plugins excluded from the default build; copy to internal_packages/ to enable.keymaps/ - CSON/JSON keymap files binding key combinations to named Mailspring commands.lang/ - JSON localization files keyed by locale code; strings are accessed via localized().menus/ - Platform-specific menu definition files consumed by the Electron main process.script/ - Node.js scripts for building, signing, and publishing releases; not imported at runtime.static/ - Bundled assets referenced by CSS and components: icons, default themes, fonts.dot-mailspring/ - Template for the user's ~/.mailspring directory, including default config.json and keymap.json.package.json - Defines user@example.com, scripts (start, build, test), and all dependencies.tsconfig.json - Strict TypeScript config with path aliases for mailspring-exports and mailspring-component-kit.result-counter.js - CLI utility that parses test runner output and summarizes pass/fail counts.better-sqlite3 crashes after Electron version change: Run ./node_modules/.bin/electron-rebuild -f -w better-sqlite3 to recompile the native addon against the new Electron headers.mailspring-exports module not found: Ensure tsconfig.json paths and your bundler alias config both map mailspring-exports to src/mailspring-exports.ts; Webpack/esbuild require a separate alias entry independent of TypeScript paths.activate called with wrong this: The package loader binds this to the plugin module object (used in category-mapper). If you port a plugin to a class, replace this.preferencesTab with a module-level variable to avoid binding issues.localized() returns raw key string: The lang/ directory must be loaded before any plugin activates. If you start the renderer in isolation (e.g., Storybook), mock localized as (s: string) => s.WorkspaceStore.Sheet.X is undefined at activate time: defineSheet must be called before any component references WorkspaceStore.Sheet.X. Call defineSheet at the top of activate, not in a deferred callback.require() in componentClassFn: PreferencesUIStore.TabItem's componentClassFn uses a lazy require() call. Under ESM builds this will fail; wrap it with createRequire(import.meta.url) or convert the tab panel to a static import.I have purchased the Mailspring Electron App Source block. The source is in the
`source/` directory of this project. There is also a `USAGE.md` file with real
API signatures, working code examples, and setup instructions.
The upstream npm package is `user@example.com` and the core import path
for framework APIs is `mailspring-exports`.
Please help me integrate this source into my existing Electron/TypeScript project
by doing the following steps:
1. Read `USAGE.md` fully before writing any code.
2. Identify which internal_packages I need for my feature: [DESCRIBE YOUR FEATURE].
3. Wire the tsconfig.json path aliases for `mailspring-exports` and
`mailspring-component-kit` into my project's tsconfig.
4. Create a new plugin under `source/internal_packages/my-plugin/` with a
`lib/main.ts` that exports `activate` and `deactivate`, using only symbols
visible in `USAGE.md`'s Public API section.
5. Register my component using `ComponentRegistry.register` in `activate` and
unregister it in `deactivate`.
6. If I need a sidebar entry, use `ExtensionRegistry.AccountSidebar.register`
with a `MailboxPerspective` subclass as shown in `USAGE.md`.
7. Show me any native rebuild commands needed for `better-sqlite3`.
8. Do not invent any API symbols not present in `USAGE.md`.
Mailspring is released under the GNU General Public License v3.0 (GPLv3). See source/LICENSE if present, or refer to the official repository for the full license text. The upstream package is user@example.com, maintained by Foundry376. Any derivative work that distributes the GPLv3-licensed source must also be released under GPLv3.
This product’s unique contribution is clean room implementation of valuable code block into AVCP compatible standard.
The full install guide and integration prompts unlock after purchase.
Automation, Utilities & Developer Tools
Free