(event: string, value: T): T;
}
```
### 11.2 Hook Pipeline (Interceptors)
Some hooks allow plugins to transform data flowing through them. For example, `email:compose:before-send` lets plugins modify the draft before it's sent:
```ts
// Plugin: auto-add disclaimer
ctx.hooks.on("email:compose:before-send", (draft) => {
return {
...draft,
htmlBody: draft.htmlBody + "Sent from Bulwark Webmail
",
};
});
```
Pipeline hooks execute in priority order. If any handler throws, the pipeline is aborted and the action is cancelled (with a notification to the user).
### 11.3 React Hooks for Addon Developers
```ts
// Available inside plugin components:
/** Access the plugin's scoped context */
usePluginContext(): PluginContext;
/** Access plugin-specific settings (reactive) */
usePluginConfig(key: string): [T, (value: T) => void];
/** Access plugin's i18n */
usePluginI18n(): { t: (key: string, params?: Record) => string };
/** Access host app theme info */
useHostTheme(): { mode: "light" | "dark"; resolvedMode: "light" | "dark" };
```
---
## 12. Migration Path
### Phase 1: Foundation
- [ ] Define the complete `addon.json` schema with JSON Schema validation.
- [ ] Create the `AddonManagerStore` (Zustand store for managing installed addons).
- [ ] Extend `ThemeStore` with `activeTheme` and `data-theme` attribute management.
- [ ] Implement CSS variable injection for themes.
- [ ] Add 3–5 bundled themes (Nord, Dracula, Solarized, Catppuccin, High Contrast).
- [ ] Add the theme selector to Settings → Appearance.
### Phase 2: Plugin Infrastructure
- [ ] Implement the `` component and `AddonErrorBoundary`.
- [ ] Add `` insertion points to the 12 defined locations in the UI.
- [ ] Build the `PluginContext` factory with permission-gated store access.
- [ ] Implement the hook event system (`emitHook`, `emitHookPipeline`).
- [ ] Create the Settings → Addons page with install/enable/disable/uninstall UI.
- [ ] Implement auto-generated settings UI from plugin settings schema.
### Phase 3: Developer Experience
- [ ] Create `@bulwarkmail/addon-api` — TypeScript type definitions package.
- [ ] Create `create-bulwark-addon` CLI scaffolding tool.
- [ ] Write addon developer documentation with examples.
- [ ] Build a sample plugin (e.g., email translation) as a reference.
- [ ] Add dev mode: hot-reload addons from local filesystem.
### Phase 4: Hardening
- [ ] Security audit of the sandboxing layer.
- [ ] CSP header configuration for addon CSS/JS.
- [ ] Rate limiting for hook events (prevent infinite loops).
- [ ] Performance budgets: measure and enforce bundle size + render time limits.
- [ ] Auto-disable addons that crash repeatedly.
---
## Appendix: Example Addons
### A. Theme: "Nord"
```
nord-theme/
├── addon.json
├── theme.css
└── preview.png
```
`addon.json`:
```json
{
"id": "org.nordtheme.bulwark-webmail",
"name": "Nord",
"version": "1.0.0",
"type": "theme",
"description": "An arctic, north-bluish color palette.",
"author": { "name": "Arctic Ice Studio" },
"license": "MIT",
"engine": { "webmail": ">=1.0.0" },
"theme": {
"variables": "theme.css",
"presets": ["light", "dark"],
"preview": "preview.png"
}
}
```
### B. Plugin: "Email Translator"
```
email-translator/
├── addon.json
├── dist/
│ └── index.js
└── locales/
├── en.json
└── fr.json
```
`addon.json`:
```json
{
"id": "com.example.email-translator",
"name": "Email Translator",
"version": "1.0.0",
"type": "plugin",
"description": "Translate emails with one click.",
"author": { "name": "JMAP Community" },
"license": "MIT",
"engine": { "webmail": ">=1.0.0" },
"main": "dist/index.js",
"permissions": [
"emails:read",
"notifications",
"viewer:action",
"external-fetch"
],
"allowedOrigins": ["https://api.deepl.com"],
"slots": ["viewer.actions"],
"settings": {
"apiKey": {
"type": "secret",
"label": "DeepL API Key"
},
"targetLanguage": {
"type": "select",
"label": "Target Language",
"options": "locales",
"default": "en"
}
},
"locales": "locales/"
}
```
### C. Plugin: "Send Later"
Adds a "Schedule Send" button to the composer:
```ts
export function activate(ctx: PluginContext) {
ctx.slots.register("compose.footer", {
component: ScheduleSendPicker,
priority: 10,
});
ctx.hooks.on("email:compose:before-send", (draft) => {
const scheduledTime = ctx.config.get("pendingSchedule");
if (scheduledTime) {
// Store the scheduled time — the host app handles deferred sending
return { ...draft, deliverAt: scheduledTime };
}
return draft;
});
}
```
---
## Open Questions
1. **Should plugins be able to define new routes (pages)?** Adding full pages (e.g., `/addons/my-plugin/dashboard`) would require deeper Next.js integration. Could use a `navigation:tab` slot that renders a full-pane view instead.
2. **Web Worker isolation?** Running plugin JS in a Web Worker would provide stronger isolation but prevents direct React rendering. A message-passing bridge is possible but adds complexity. Probably not worth it for v1.
3. **Server-side plugins?** Some use cases (email filtering, webhook integrations) need server execution. This is out of scope for v1 but could be explored as Sieve filter generation or JMAP push notification handlers.
4. **Addon signing?** For URL-installed addons, a signature verification system would prevent tampering. Worth considering for v2.
5. **Shared dependencies?** Should plugins be able to declare peer dependencies on the host app's packages (React, date-fns, Lucide icons)? This would reduce bundle sizes but creates coupling. Recommend providing these as globals via the plugin runtime. **(Addressed in §6.3 — shared deps are exposed via a scoped `require`.)**
6. **Admin-controlled addon allowlist?** In multi-user deployments, should the server admin be able to restrict which addons can be installed (e.g., via an environment variable `ALLOWED_ADDON_IDS`)? This would prevent users from loading untrusted plugins in managed environments.